CLAUDE.md rewritten (708 -> ~320 lines): four overlapping release sections collapsed to one, stale v1->v35 schema history dropped (it lives in CHANGELOG), marketplace endpoint internals and verbose process sections moved out or tightened. New focused docs: - docs/RELEASING.md - release process, deploy workflows, CI quirks (RELEASE_TEMPLATE.md folded in as an appendix) - docs/marketplace.md - marketplace ingestion + re-serving internals - docs/README.md - documentation index by audience, linked from README.md and CLAUDE.md Archived under docs/archive/: docs/superpowers/ (52 historical planning artifacts), HACKATHON.md, pd-ps-comments.md, security-audit-2026-04.md, future/NOTIFICATIONS.md. Removed the docs/auto-install.md stub. Fixed dangling links in connectors/jira/README.md and dev_docs/README.md, repointed code/doc references to archived paths.
8.9 KiB
Security Hardening — Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Fix all critical/high security vulnerabilities found by audit before merging to main. Also update Python version, sync deps, fix Docker prod config.
Architecture: 7 independent fix tasks targeting specific vulnerabilities. Each is self-contained.
Tech Stack: Python 3.13, FastAPI, DuckDB, Docker
Task 1: SQL Query Hardening
Files:
-
Modify:
app/api/query.py -
Modify:
src/db.py(add read-only analytics getter) -
Test: existing
tests/test_security.pyshould still pass -
Step 1: Add read-only analytics DB connection
In src/db.py, add after get_analytics_db():
def get_analytics_db_readonly() -> duckdb.DuckDBPyConnection:
"""Read-only connection to analytics DB. Blocks writes and external access."""
db_path = _get_data_dir() / "analytics" / "server.duckdb"
if not db_path.exists():
db_path.parent.mkdir(parents=True, exist_ok=True)
conn = duckdb.connect(str(db_path), read_only=True)
conn.execute("SET enable_external_access = false")
return conn
- Step 2: Harden query endpoint
In app/api/query.py, replace get_analytics_db() with get_analytics_db_readonly(). Add to blocklist:
blocked = [
"drop ", "delete ", "insert ", "update ", "alter ", "create ",
"copy ", "attach ", "detach ", "load ", "install ",
"export ", "import ", "pragma ",
"read_csv", "read_json", "read_parquet(", "read_text",
"write_csv", "write_parquet",
"read_blob", "glob(", "read_ndjson",
";",
"'/", '"/", # Block absolute file paths in FROM clause
]
- Step 3: Run tests
pytest tests/test_security.py tests/test_e2e_api.py -v --tb=short
- Step 4: Commit
git commit -m "security: query endpoint — read-only DB + disable external access + block file paths"
Task 2: Upload Path Traversal Fix
Files:
-
Modify:
app/api/upload.py -
Step 1: Sanitize filenames
Replace lines 30-31 and 47-48 with:
# Sanitize filename — strip directory components to prevent traversal
raw_name = file.filename or f"session_{uuid.uuid4().hex[:8]}.jsonl"
filename = Path(raw_name).name # strips ../../../ etc
if not filename or filename.startswith("."):
filename = f"upload_{uuid.uuid4().hex[:8]}"
target = sessions_dir / filename
Same pattern for artifact upload.
- Step 2: Commit
git commit -m "security: sanitize upload filenames — prevent path traversal"
Task 3: Script Sandbox Hardening
Files:
-
Modify:
app/api/scripts.py -
Step 1: Add AST-based validation
Add before the blocklist check:
import ast
# AST-based validation — catches obfuscated imports
try:
tree = ast.parse(source)
except SyntaxError as e:
raise HTTPException(status_code=400, detail=f"Script syntax error: {e}")
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
if alias.name.split(".")[0] in BLOCKED_MODULES:
raise HTTPException(status_code=400, detail=f"Blocked import: {alias.name}")
elif isinstance(node, ast.ImportFrom):
if node.module and node.module.split(".")[0] in BLOCKED_MODULES:
raise HTTPException(status_code=400, detail=f"Blocked import: {node.module}")
elif isinstance(node, ast.Call):
if isinstance(node.func, ast.Name) and node.func.id in BLOCKED_FUNCTIONS:
raise HTTPException(status_code=400, detail=f"Blocked function: {node.func.id}")
BLOCKED_MODULES = {"os", "sys", "subprocess", "shutil", "ctypes", "importlib", "socket",
"requests", "urllib", "http", "signal", "pathlib", "builtins"}
BLOCKED_FUNCTIONS = {"exec", "eval", "compile", "open", "globals", "locals",
"getattr", "setattr", "delattr", "breakpoint", "__import__"}
Keep the string blocklist as a secondary check.
- Step 2: Commit
git commit -m "security: AST-based script validation — catches obfuscated imports"
Task 4: Password Hashing + Auth Fixes
Files:
-
Modify:
app/auth/providers/password.py(remove SHA256 fallback) -
Modify:
app/auth/providers/google.py(add secure cookie flag) -
Modify:
app/auth/dependencies.py(fix get_optional_user bug) -
Modify:
app/auth/jwt.py(add secret length validation) -
Modify:
pyproject.toml(add missing deps) -
Step 1: Remove SHA256 fallback in password.py
Replace the try/except ImportError block with:
from argon2 import PasswordHasher
ph = PasswordHasher()
hashed = ph.hash(request.password)
Remove all except ImportError: import hashlib blocks. Same in app/auth/router.py if present.
- Step 2: Add secure flag to Google OAuth cookie
In app/auth/providers/google.py, change set_cookie to:
is_https = os.environ.get("HTTPS", "").lower() in ("1", "true") or request.url.scheme == "https"
response.set_cookie(
key="access_token", value=jwt_token,
httponly=True, max_age=86400 * 30, samesite="lax",
secure=is_https,
)
- Step 3: Fix get_optional_user argument bug
In app/auth/dependencies.py, change line 68:
# Before (wrong):
return await get_current_user(authorization, conn)
# After (correct):
return await get_current_user(request=None, authorization=authorization, conn=conn)
- Step 4: Add JWT secret validation
In app/auth/jwt.py, add after SECRET_KEY:
if len(SECRET_KEY) < 32 and not os.environ.get("TESTING"):
import warnings
warnings.warn("JWT_SECRET_KEY is less than 32 characters — insecure for production", stacklevel=2)
- Step 5: Sync pyproject.toml deps
Add to pyproject.toml dependencies:
"authlib>=1.3.0",
"argon2-cffi>=23.1.0",
- Step 6: Commit
git commit -m "security: fix password hashing, OAuth cookie, JWT validation, optional_user bug"
Task 5: CORS + Session Middleware
Files:
-
Modify:
app/main.py -
Step 1: Fix CORS
Replace wildcard CORS with environment-configured origins:
cors_origins = os.environ.get("CORS_ORIGINS", "http://localhost:3000,http://localhost:8000").split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=[o.strip() for o in cors_origins],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
- Step 2: Fix SessionMiddleware secret
session_secret = os.environ.get("SESSION_SECRET", os.environ.get("JWT_SECRET_KEY", ""))
if not session_secret:
import secrets
session_secret = secrets.token_hex(32)
app.add_middleware(SessionMiddleware, secret_key=session_secret)
- Step 3: Commit
git commit -m "security: CORS from env config + session secret validation"
Task 6: Docker Production Config
Files:
-
Modify:
docker-compose.yml(remove --reload) -
Modify:
Dockerfile(upgrade Python) -
Step 1: Remove --reload from docker-compose.yml
# Before:
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
# After:
command: uvicorn app.main:app --host 0.0.0.0 --port 8000
Create docker-compose.override.yml for dev:
services:
app:
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
volumes:
- .:/app
- Step 2: Upgrade Dockerfile to Python 3.13
FROM python:3.13-slim
- Step 3: Commit
git commit -m "chore: Docker production config — no reload, Python 3.13"
Task 7: Stale Docs Cleanup + datetime.utcnow Fix
Files:
-
Modify:
CLAUDE.md(fix test count) -
Delete or update: stale docs referencing Flask
-
Modify:
app/api/jira_webhooks.py,src/profiler.py(utcnow → now(UTC)) -
Step 1: Fix CLAUDE.md test count
Update test count to current number.
- Step 2: Delete stale docs
rm docs/superpowers/plans/*.md # agent plans, not needed in repo
- Step 3: Fix datetime.utcnow deprecation
Replace datetime.utcnow() with datetime.now(timezone.utc) in:
-
app/api/jira_webhooks.py -
src/profiler.py(lines 1213, 1379) -
Step 4: Commit
git commit -m "chore: fix stale docs, test count, datetime deprecation"
Execution order
Task 1 (SQL) ─┐
Task 2 (Upload) ├─ 3 parallel agents
Task 3 (Scripts) ─┘
Task 4 (Auth) ─┐
Task 5 (CORS) ├─ 3 parallel agents
Task 6 (Docker) ─┘
Task 7 (Docs) ── sequential after above
Verification
# All tests pass
pytest tests/ --ignore=tests/test_cli.py -v
# Security spot checks
python3 -c "from app.api.query import *" # no errors
curl -X POST http://localhost:8000/api/query -d '{"sql":"SELECT * FROM \"/etc/passwd\""}' # should fail
# Docker build
docker build -t test .