inou/scripts/check-db-access.sh

230 lines
7.7 KiB
Bash
Executable File

#!/bin/bash
# check-db-access.sh - Verify no direct database access to inou.db outside db_queries.go
# Run: make check-db or ./scripts/check-db-access.sh
#
# This checks the CORE CODEBASE that accesses inou.db:
# - lib/
# - portal/
# - api/
# - viewer/
# - mcp-client/
#
# Excluded (have their own separate databases):
# - snpedia-genotypes/ (genotypes.db)
# - scrape_mychart/ (scrape.db)
# - main.go at root (old/unused)
# - import-genome/ (reads genotypes.db, uses lib for inou.db)
# - docs/, backups/
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
ERRORS=0
# Core directories that access inou.db
CORE_DIRS="lib portal api viewer mcp-client"
echo "=== Database Access Check (inou.db) ==="
echo "Checking: $CORE_DIRS"
echo ""
# Check for lib.DB() usage (should not exist anywhere in core)
echo -n "Checking for lib.DB() usage... "
MATCHES=""
for dir in $CORE_DIRS; do
if [ -d "$dir" ]; then
FOUND=$(grep -r "lib\.DB()" --include="*.go" "$dir" 2>/dev/null || true)
MATCHES="$MATCHES$FOUND"
fi
done
if [ -n "$MATCHES" ]; then
echo -e "${RED}FAILED${NC}"
echo "$MATCHES"
ERRORS=$((ERRORS + 1))
else
echo -e "${GREEN}OK${NC}"
fi
# Check for db.Exec outside allowed files (in lib only)
echo -n "Checking for db.Exec() outside allowed lib files... "
MATCHES=$(grep -r "db\.Exec" --include="*.go" lib 2>/dev/null | grep -v "lib/db_queries.go" | grep -v "lib/db_schema.go" | grep -v "lib/dbcore.go" | grep -v "lib/stubs.go" | grep -v "lib/migrate" | grep -v "_test.go" || true)
if [ -n "$MATCHES" ]; then
echo -e "${RED}FAILED${NC}"
echo "$MATCHES"
ERRORS=$((ERRORS + 1))
else
echo -e "${GREEN}OK${NC}"
fi
# Check for db.Query outside allowed files (in lib only)
echo -n "Checking for db.Query() outside allowed lib files... "
MATCHES=$(grep -r "db\.Query" --include="*.go" lib 2>/dev/null | grep -v "lib/db_queries.go" | grep -v "lib/db_schema.go" | grep -v "lib/dbcore.go" | grep -v "lib/stubs.go" | grep -v "lib/migrate" | grep -v "_test.go" || true)
if [ -n "$MATCHES" ]; then
echo -e "${RED}FAILED${NC}"
echo "$MATCHES"
ERRORS=$((ERRORS + 1))
else
echo -e "${GREEN}OK${NC}"
fi
# Check for db.QueryRow outside allowed files (in lib only)
echo -n "Checking for db.QueryRow() outside allowed lib files... "
MATCHES=$(grep -r "db\.QueryRow" --include="*.go" lib 2>/dev/null | grep -v "lib/db_queries.go" | grep -v "lib/db_schema.go" | grep -v "lib/dbcore.go" | grep -v "lib/stubs.go" | grep -v "lib/migrate" | grep -v "_test.go" || true)
if [ -n "$MATCHES" ]; then
echo -e "${RED}FAILED${NC}"
echo "$MATCHES"
ERRORS=$((ERRORS + 1))
else
echo -e "${GREEN}OK${NC}"
fi
# Check for sql.Open in portal/api/viewer (should only be rateDB exception in portal)
echo -n "Checking for sql.Open() in portal/api/viewer... "
MATCHES=$(grep -r "sql\.Open" --include="*.go" portal api viewer 2>/dev/null | grep -v "rateDB" | grep -v "ratelimit" | grep -v "snpDB" || true)
if [ -n "$MATCHES" ]; then
echo -e "${RED}FAILED${NC}"
echo "$MATCHES"
ERRORS=$((ERRORS + 1))
else
echo -e "${GREEN}OK${NC}"
fi
# Check for deprecated DB wrapper usage in core dirs
echo -n "Checking for deprecated DB wrapper usage... "
MATCHES=""
for dir in $CORE_DIRS; do
if [ -d "$dir" ]; then
FOUND=$(grep -rE "\b(DBExec|DBInsert|DBUpdate|DBDelete|DBQuery|DBQueryRow)\b" --include="*.go" "$dir" 2>/dev/null || true)
MATCHES="$MATCHES$FOUND"
fi
done
if [ -n "$MATCHES" ]; then
echo -e "${RED}FAILED${NC}"
echo "$MATCHES"
ERRORS=$((ERRORS + 1))
else
echo -e "${GREEN}OK${NC}"
fi
echo ""
echo "=== Schema Safety Check ==="
echo ""
# Check for CREATE TABLE statements in code (schema is documented in docs/schema.sql)
# Allowed: lib/db_queries.go (ORM layer), rateDB (separate rate-limiting database)
echo -n "Checking for CREATE TABLE in code... "
MATCHES=""
for dir in $CORE_DIRS; do
if [ -d "$dir" ]; then
FOUND=$(grep -ri "CREATE TABLE" --include="*.go" "$dir" 2>/dev/null | grep -v "lib/db_queries.go" | grep -v "rateDB" | grep -v "_test.go" || true)
MATCHES="$MATCHES$FOUND"
fi
done
if [ -n "$MATCHES" ]; then
echo -e "${RED}FAILED${NC}"
echo "$MATCHES"
echo " Schema creation is forbidden. Schema is documented in docs/schema.sql"
ERRORS=$((ERRORS + 1))
else
echo -e "${GREEN}OK${NC}"
fi
echo ""
echo "=== Unexported DB Function Check ==="
echo ""
# Now that Query/Save/Load/Delete/Count are unexported (dbQuery, dbSave, etc.),
# no code outside lib/ should reference them. The Go compiler enforces this,
# but this check catches it earlier (before build).
ALL_DIRS="portal api viewer mcp-client import-genome cmd find_dossiers tools test-prompts doc-processor"
for fn in "lib\.Query(" "lib\.Save(" "lib\.Load(" "lib\.Delete(" "lib\.Count("; do
name=$(echo "$fn" | sed 's/lib\\.//;s/($//')
echo -n "Checking for $fn outside lib/... "
MATCHES=""
for dir in $ALL_DIRS; do
if [ -d "$dir" ]; then
FOUND=$(grep -rn "$fn" --include="*.go" "$dir" 2>/dev/null || true)
# Filter out comments and string literals (rough heuristic: lines with // before the match)
if [ -n "$FOUND" ]; then
REAL=$(echo "$FOUND" | grep -v "^\s*//" | grep -v "^\s*\*" || true)
MATCHES="$MATCHES$REAL"
fi
fi
done
if [ -n "$MATCHES" ]; then
echo -e "${RED}FAILED${NC}"
echo "$MATCHES"
echo " lib.$name() is unexported — use RBAC-checked functions instead"
ERRORS=$((ERRORS + 1))
else
echo -e "${GREEN}OK${NC}"
fi
done
echo ""
echo "=== RBAC Enforcement Check ==="
echo ""
# Check that internal checkAccess() is only called within lib/
# The checkAccess function is internal to lib/access.go and should only be called from lib/v2.go
echo -n "Checking for checkAccess() calls outside lib/... "
MATCHES=""
for dir in portal api viewer mcp-client; do
if [ -d "$dir" ]; then
FOUND=$(grep -r "checkAccess(" --include="*.go" "$dir" 2>/dev/null || true)
MATCHES="$MATCHES$FOUND"
fi
done
if [ -n "$MATCHES" ]; then
echo -e "${RED}FAILED${NC}"
echo "$MATCHES"
echo " checkAccess() is an internal function - use data layer functions that enforce RBAC"
ERRORS=$((ERRORS + 1))
else
echo -e "${GREEN}OK${NC}"
fi
# Check that lib.CheckAccess is only called from lib/ or allowed API files
# Allowed: api/auth.go (RBAC helper functions), api/api_rbac.go (RBAC management API)
echo -n "Checking for lib.CheckAccess() outside lib/ and RBAC helpers... "
MATCHES=""
for dir in portal viewer mcp-client; do
if [ -d "$dir" ]; then
FOUND=$(grep -r "lib\.CheckAccess" --include="*.go" "$dir" 2>/dev/null || true)
MATCHES="$MATCHES$FOUND"
fi
done
# Also check api/ but exclude auth.go and api_rbac.go
if [ -d "api" ]; then
FOUND=$(grep -r "lib\.CheckAccess" --include="*.go" api 2>/dev/null | grep -v "api/auth.go" | grep -v "api/api_rbac.go" || true)
MATCHES="$MATCHES$FOUND"
fi
if [ -n "$MATCHES" ]; then
echo -e "${RED}FAILED${NC}"
echo "$MATCHES"
echo " RBAC checks should be in data layer (lib/) or RBAC helpers (api/auth.go, api/api_rbac.go)"
ERRORS=$((ERRORS + 1))
else
echo -e "${GREEN}OK${NC}"
fi
echo ""
# Summary
if [ $ERRORS -eq 0 ]; then
echo -e "${GREEN}All checks passed!${NC}"
echo "No direct database access detected in core codebase."
exit 0
else
echo -e "${RED}$ERRORS check(s) failed!${NC}"
echo ""
echo "Direct database access is FORBIDDEN without Johan's express consent."
echo "All DB operations must go through RBAC-checked lib functions."
echo "Raw DB functions (dbQuery, dbSave, etc.) are unexported — only callable from lib/."
exit 1
fi