# ═══════════════════════════════════════════════════════════════════════════════ # AUTHORBOT RAG — PRODUCTION RULES ($500K BUDGET, ENTERPRISE SaaS) # ═══════════════════════════════════════════════════════════════════════════════ # # READ THIS FILE BEFORE EVERY SESSION. NO EXCEPTIONS. # # These rules are derived from a line-by-line audit of the entire codebase. # Every rule exists because we found a gap, a vulnerability, or an inconsistency # that WILL cause problems at production scale. # # Format: Each rule has a WHY, WHERE, and ENFORCEMENT section. # Status: ✅ = Implemented, ❌ = NOT YET (must fix), ⚠️ = Partial # RECONCILED: 2026-07-07 — statuses verified against codebase (see §23 PRIORITY BACKLOG) # ═══════════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════════ # §1. AUTHENTICATION & AUTHORIZATION # ═══════════════════════════════════════════════════════════════════════════════ # R-001: Passwords MUST be hashed with bcrypt ≥12 rounds. # WHY: Lower rounds are brute-forceable with modern GPUs. # WHERE: app/services/auth_service.py → _hash_password() # STATUS: ✅ Implemented (rounds=12) # R-002: Failed login MUST trigger account lockout (5 attempts → 15-min lock). # WHY: Prevents credential stuffing attacks. # WHERE: app/services/auth_service.py → login() # STATUS: ✅ Implemented # R-003: Non-existent email MUST take constant time to respond. # WHY: Timing attacks can enumerate valid emails. # WHERE: app/services/auth_service.py:98-99 (dummy bcrypt comparison) # STATUS: ✅ Implemented # R-004: JWT refresh MUST invalidate the old token (Redis blacklist by jti). # WHERE: app/api/schemas_router.py → refresh_tokens() # STATUS: ✅ Implemented — blacklists old access + refresh jti (R-143) # R-005: Logout MUST add current JWT to Redis blacklist (TTL = remaining lifetime). # WHERE: app/api/schemas_router.py → logout() # STATUS: ✅ Implemented — blacklists Bearer access jti (R-144) # R-006: Refresh token MUST use HttpOnly + Secure + SameSite=Lax. # WHY: Prevents XSS from stealing refresh tokens. # WHERE: app/api/schemas_router.py:26-35 # STATUS: ✅ Implemented # R-007: SuperAdmin routes MUST require JWT + TOTP on EVERY request. # WHERE: app/dependencies.py → get_current_superadmin() # STATUS: ✅ Implemented — X-TOTP-Session or X-TOTP-Code required (R-145) # R-008: Registration MUST validate email format (RFC 5322 regex or Pydantic EmailStr). # WHERE: app/services/auth_service.py → register() # STATUS: ✅ Implemented — regex validation in register() # R-009: Registration MUST enforce password complexity (≥8 chars, mixed case + digit). # WHERE: app/services/auth_service.py → register() # STATUS: ✅ Implemented — length + upper/lower/digit checks # R-010: Password change MUST validate via Pydantic schema (not raw dict). # WHERE: app/admin/routers/settings.py # STATUS: ✅ Implemented — PasswordChangeRequest schema # ═══════════════════════════════════════════════════════════════════════════════ # §2. SUBSCRIPTION & TOKEN SECURITY # ═══════════════════════════════════════════════════════════════════════════════ # R-011: Subscription token HMAC MUST use constant-time comparison. # WHY: Non-constant comparison leaks signature bytes via timing side-channel. # WHERE: app/core/access/token_crypto.py:179 # STATUS: ✅ hmac.compare_digest() # R-012: Raw subscription tokens MUST never be stored — only SHA-256 hash. # WHY: DB breach exposes all tokens if stored in plaintext. # WHERE: app/core/access/token_crypto.py:199-210 # STATUS: ✅ Implemented # R-013: Token validation MUST check all 5 layers (HMAC → Expiry → Redis → DB → Budget). # WHY: Skipping any layer creates a bypass. # WHERE: app/core/access/subscription.py:34-111 # STATUS: ✅ Implemented # R-014: tokens_used MUST be incremented atomically on every chat turn. # WHERE: app/services/token_budget.py:89-96 # STATUS: ✅ Implemented — SQL `tokens_used = tokens_used + N` # R-015: budget_exhausted flag MUST be set at 100% usage. # WHERE: app/services/token_budget.py:92-93 # STATUS: ✅ Redis flag set # R-016: BudgetExhaustedError MUST return HTTP 200 (not 403) with friendly message. # WHERE: app/dependencies.py + app/main.py global handler # STATUS: ✅ Implemented — propagates to main.py HTTP 200 handler # R-017: Token budget formula MUST always be: total = token_budget + bonus_tokens. # WHY: Inconsistent formula = different numbers on admin vs superadmin vs embed. # WHERE: app/services/token_budget.py → total_budget() # STATUS: ✅ Centralized in token_budget.py # R-018: Revocation MUST propagate to Redis blacklist immediately (O(1) check). # WHY: DB-only revocation has latency; widget keeps working for cached sessions. # WHERE: app/services/superadmin_service.py:308-312 # STATUS: ✅ Implemented # R-019: chatbot_is_active=false MUST block all chat requests. # WHY: Suspended authors must not serve visitors. # WHERE: app/core/access/subscription.py:108-109 # STATUS: ✅ Checked in validation chain # ═══════════════════════════════════════════════════════════════════════════════ # §3. RATE LIMITING & DDoS PROTECTION # ═══════════════════════════════════════════════════════════════════════════════ # R-020: Chat endpoint rate limit: 60 req/min per IP. # WHERE: app/middleware/rate_limit_middleware.py:21 # STATUS: ✅ Implemented # R-021: Auth endpoint rate limit: 10 req/min per IP. # WHERE: app/middleware/rate_limit_middleware.py:22 # STATUS: ✅ Implemented # R-022: Rate limiter MUST fail-open (no Redis = allow traffic, not crash). # WHERE: app/middleware/rate_limit_middleware.py:61-62 # STATUS: ✅ Implemented # R-023: SSE endpoints MUST have a max connection timeout (5 minutes). # WHERE: app/api/chat.py, app/api/ingest.py # STATUS: ✅ Implemented — 300s deadline + 30s heartbeats # R-024: File upload MUST reject oversized files BEFORE reading into memory. # WHERE: app/api/ingest.py # STATUS: ⚠️ PARTIAL — 64KB chunked read with abort; no Content-Length pre-check # R-025: X-Forwarded-For MUST NOT be blindly trusted (use rightmost trusted proxy). # WHERE: app/middleware/rate_limit_middleware.py # STATUS: ⚠️ PARTIAL — uses leftmost X-Forwarded-For; no trusted-proxy list # ═══════════════════════════════════════════════════════════════════════════════ # §4. INPUT VALIDATION & SANITIZATION # ═══════════════════════════════════════════════════════════════════════════════ # R-026: All chat input MUST be sanitized (control chars, zero-width, delimiters). # WHERE: app/services/guardrails.py → sanitize_input() # STATUS: ✅ Implemented # R-027: Chat input hard cap: 2000 characters. No exceptions. # WHERE: app/services/guardrails.py # STATUS: ✅ Implemented # R-028: Jailbreak detection: 30+ regex patterns, ≥2 signals = auto-block. # WHERE: app/services/guardrails.py → check_boundary() # STATUS: ✅ Implemented # R-029: ALL admin API endpoints MUST use Pydantic BaseModel schemas (NOT raw dict). # WHERE: app/admin/routers/*, app/schemas/admin.py # STATUS: ✅ Implemented — no body:dict in admin routers # R-030: All path parameter IDs MUST be validated as UUID format. # WHERE: All routers — book_id, session_id, message_id, etc. # STATUS: ⚠️ PARTIAL — some schemas validate UUID; path params still plain str # R-031: URL inputs (smart links) MUST validate scheme (https preferred) and format. # WHERE: app/schemas/admin.py → SmartLinkUpdate.validate_url_scheme() # STATUS: ✅ Implemented — http/https required # R-032: CSV import MUST sanitize HTML/script content in Q&A answers. # WHY: Q&A answers are rendered in the widget — XSS vector. # WHERE: app/admin/router.py:1022-1082 # STATUS: ⚠️ PARTIAL — length-limited but no HTML stripping # ═══════════════════════════════════════════════════════════════════════════════ # §5. FILE UPLOAD SECURITY # ═══════════════════════════════════════════════════════════════════════════════ # R-033: MIME type MUST be verified by magic bytes, NEVER by file extension alone. # WHERE: app/services/file_validator.py via file_utils.validate_upload # STATUS: ✅ Implemented # R-034: Cover images MUST strip EXIF metadata (privacy: GPS coords, camera serial). # WHERE: app/admin/router.py:302 — img.convert('RGB') strips EXIF # STATUS: ✅ Implemented # R-035: Upload paths MUST use server-generated names (UUID), NEVER user filenames. # WHY: User filenames enable directory traversal (../../etc/passwd). # WHERE: app/api/ingest.py:60 uses book.id (UUID) # STATUS: ✅ Implemented # R-036: Temp files MUST be cleaned up on both success AND failure paths. # WHERE: app/api/ingest.py:175-181 (finally block) # STATUS: ✅ Implemented # ═══════════════════════════════════════════════════════════════════════════════ # §6. DATA ISOLATION & TENANT SECURITY # ═══════════════════════════════════════════════════════════════════════════════ # R-037: Every DB query MUST be scoped by author_id. No cross-tenant data access. # WHERE: All repositories # STATUS: ✅ Implemented # R-038: Admin slug in URL MUST match authenticated user's ID. # WHERE: app/dependencies.py:168-175 → get_current_author_scoped() # STATUS: ✅ Implemented # R-039: ChromaDB collections MUST be namespaced by author_id prefix. # WHERE: app/services/vector_store.py:133-148 # STATUS: ✅ Implemented # R-040: Message annotation/flag endpoints MUST verify session ownership chain. # WHERE: app/repositories/session_repo.py → get_message_for_author() # STATUS: ✅ Implemented — JOIN ChatSession.author_id == author_id # R-041: Visitor fingerprints MUST be one-way hashed (SHA-256), never raw PII. # WHERE: app/api/chat.py:34-41 # STATUS: ✅ Implemented # ═══════════════════════════════════════════════════════════════════════════════ # §7. API SECURITY & ERROR HANDLING # ═══════════════════════════════════════════════════════════════════════════════ # R-042: Security headers MUST be on every HTTP response. # Required: X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, # Referrer-Policy, Permissions-Policy, HSTS (production only). # WHERE: app/middleware/security_headers.py # STATUS: ✅ Implemented # R-043: Content-Security-Policy header MUST be set. # WHERE: app/middleware/security_headers.py # STATUS: ✅ Implemented — CSP on every response # R-044: Error responses MUST NOT contain internal exception messages, SQL, or paths. # WHERE: app/superadmin/routers/_utils.py safe _err(); some routers still leak # STATUS: ⚠️ PARTIAL — authors.py/grants.py return str(e) on some 400 paths # R-045: No public/unauthenticated diagnostic endpoints on admin routers. # WHERE: app/superadmin/routers/platform.py → /diag # STATUS: ✅ Implemented — requires get_current_superadmin # R-046: No duplicate route definitions (causes unpredictable routing). # WHERE: app/api/chat.py # STATUS: ✅ Implemented — single rate_session definition # R-047: CORS MUST NOT use wildcard (*) origins in production. # WHERE: app/main.py → CORSMiddleware (ALLOWED_ORIGINS in prod, * in dev) # STATUS: ⚠️ PARTIAL — production uses whitelist; dev uses * # ═══════════════════════════════════════════════════════════════════════════════ # §8. DATABASE RULES # ═══════════════════════════════════════════════════════════════════════════════ # R-048: All schema changes MUST use Alembic migrations. No manual DDL. # WHERE: alembic/versions/, alembic.ini # STATUS: ✅ Implemented — Alembic configured (R-157); see R-132 for residual DDL # R-049: All queries MUST use parameterized statements (ORM or bound params). # WHERE: All repositories use SQLAlchemy ORM # STATUS: ✅ Implemented # R-050: DB transactions MUST auto-rollback on exception. # WHERE: app/dependencies.py:57-65 # STATUS: ✅ Implemented # R-051: All datetimes MUST use timezone-aware UTC (datetime.now(timezone.utc)). # WHERE: Codebase-wide # STATUS: ✅ Implemented — no utcnow() in app/ # R-052: Concurrent writes MUST be atomic (SQL UPDATE, not Python read-modify-write). # WHERE: app/services/token_budget.py # STATUS: ✅ Implemented — same as R-014 # R-053: High-traffic columns MUST have database indexes. # WHERE: app/models/ — index=True on author_id, session_id, timestamps # STATUS: ⚠️ PARTIAL — key FK columns indexed; composite indexes may need review # R-054: Soft delete SHOULD be preferred for auditable records. # WHERE: Books and authors use hard delete; only ClientAccess uses soft delete # STATUS: ⚠️ PARTIAL # ═══════════════════════════════════════════════════════════════════════════════ # §9. REDIS INFRASTRUCTURE # ═══════════════════════════════════════════════════════════════════════════════ # R-055: Redis MUST have authentication (--requirepass) in production. # WHERE: Dockerfile (HF), docker-compose.yml, app/config.py REDIS_PASSWORD # STATUS: ✅ Implemented — requirepass in Docker; REDIS_PASSWORD injects into URL; enforced when APP_ENV=production # R-056: All Redis keys MUST have TTL — no orphan keys. # WHERE: app/services/token_budget.py:82, rate_limit_middleware.py:54 # STATUS: ✅ Implemented # R-057: Redis connection MUST be pooled. # WHERE: app/dependencies.py:94-104 # STATUS: ✅ Implemented # R-058: Redis failure MUST NOT crash the application (graceful degradation). # WHERE: All Redis operations wrapped in try/except # STATUS: ✅ Implemented # R-059: Redis persistence MUST be explicitly configured (AOF preferred for durability). # WHERE: Dockerfile # STATUS: ⚠️ Uses default RDB — should configure AOF for session/token data # ═══════════════════════════════════════════════════════════════════════════════ # §10. RAG PIPELINE INTEGRITY # ═══════════════════════════════════════════════════════════════════════════════ # R-060: Every chat message MUST pass through ALL 12 pipeline steps. No bypass. # WHERE: app/services/rag_pipeline.py # STATUS: ✅ Sequential execution # R-061: Guardrails MUST run BEFORE any LLM call (input sanitize + boundary check). # WHERE: Pipeline steps 0-1 run before step 2 # STATUS: ✅ Implemented # R-062: Output safety MUST run on EVERY generated response (no AI identity leaks). # WHERE: app/services/guardrails.py → is_response_safe() # STATUS: ✅ Implemented # R-063: Faithfulness NLI check MUST trigger one regeneration, then safe fallback. # WHERE: app/services/rag_pipeline.py # STATUS: ✅ Implemented # R-064: Context window MUST NOT exceed RAG_MAX_CONTEXT_TOKENS (4096). # WHERE: app/services/context_builder.py # STATUS: ✅ Hard limit enforced # R-065: response_style MUST be injected into the LLM system prompt. # WHERE: app/services/pipeline/generation.py + app/services/prompter.py # STATUS: ✅ Implemented — get_response_style_instruction() in prompt # R-066: All prompts MUST live in app/services/prompter.py. No inline prompts. # WHERE: All pipeline services # STATUS: ✅ Implemented # R-067: LLM errors MUST return warm human fallback, never a crash or error code. # WHERE: app/services/rag_pipeline.py — try/except at every step # STATUS: ✅ Implemented # R-068: Responses MUST NOT contain markdown (bold, bullets, headers, code blocks). # WHERE: System prompt + output guardrails # STATUS: ✅ Enforced # R-069: No spoilers — never summarize full plot, retell story, or reveal endings. # WHERE: System prompt instructions # STATUS: ✅ Enforced in prompt # R-070: Max response length: ~75 words, 2 paragraphs, 380 chars. # WHERE: System prompt + formatter # STATUS: ✅ Enforced # ═══════════════════════════════════════════════════════════════════════════════ # §11. ADMIN PANEL CONSISTENCY # ═══════════════════════════════════════════════════════════════════════════════ # R-071: Token usage display MUST include bonus_tokens in total budget. # WHERE: app/admin/router.py:753-765 via usage_summary_or_empty() # STATUS: ✅ Implemented # R-072: Embed token card MUST show same remaining count as token usage page. # WHERE: Both use tokens_remaining() from token_budget.py # STATUS: ✅ Implemented # R-073: Book deletion MUST clean up ChromaDB collection. # WHERE: app/admin/router.py:243-244 # STATUS: ✅ Implemented # R-074: Smart Links MUST sync both books.buy_url AND links.purchase_url. # WHERE: app/admin/router.py:735-747 # STATUS: ✅ Implemented # R-075: Export endpoints MUST have row limits (prevent OOM on large datasets). # WHERE: app/admin/router.py:1219 — .limit(50000) # STATUS: ✅ Implemented # ═══════════════════════════════════════════════════════════════════════════════ # §12. SUPERADMIN PANEL # ═══════════════════════════════════════════════════════════════════════════════ # R-076: Every SuperAdmin mutation MUST write to the audit log. No exceptions. # WHERE: app/services/superadmin_service.py — all 7 mutations have _audit.log() # STATUS: ✅ Implemented # R-077: Audit log MUST be append-only — no UPDATE or DELETE endpoints. # WHERE: app/superadmin/router.py:316-344 — only GET # STATUS: ✅ Implemented # R-078: Author deletion MUST revoke all active tokens (Redis + DB). # WHERE: app/services/superadmin_service.py:155-164 # STATUS: ✅ Implemented # R-079: Self-deletion and SuperAdmin deletion MUST be prevented. # WHERE: app/services/superadmin_service.py:148-151 # STATUS: ✅ Implemented # R-080: Backup MUST NOT run synchronously on the API event loop. # WHERE: app/superadmin/routers/platform.py → trigger_backup() # STATUS: ✅ Implemented — run_backup.delay() returns 202 + task_id # R-081: Grant-by-email MUST validate plan name against known list. # WHERE: app/superadmin/router.py:408-413 # STATUS: ⚠️ PARTIAL — plan_map exists but no error for unknown plans # ═══════════════════════════════════════════════════════════════════════════════ # §13. BACKGROUND TASKS & CELERY # ═══════════════════════════════════════════════════════════════════════════════ # R-082: Document ingestion MUST use Celery (not asyncio.create_task on API process). # WHERE: app/services/ingestion_service.py # STATUS: ⚠️ PARTIAL — Celery primary (process_document.delay); asyncio fallback remains # R-083: Celery tasks MUST use acks_late + max_retries for crash resilience. # WHERE: app/tasks/celery_app.py:35, app/tasks/ingestion_task.py:22-28 # STATUS: ✅ Configured # R-084: SSE status broadcasts MUST use consistent Redis channel naming. # WHERE: ingestion_task.py vs ingest.py vs chat.py # STATUS: ⚠️ PARTIAL — mix of ingestion:{author_id} and ingestion:{author_id}:{book_id} # ═══════════════════════════════════════════════════════════════════════════════ # §14. CI/CD & TESTING # ═══════════════════════════════════════════════════════════════════════════════ # R-085: CI MUST fail on test failures. `|| true` is FORBIDDEN. # WHERE: .github/workflows/ci.yml # STATUS: ✅ Implemented — pytest -x, no || true # R-086: Test coverage MUST meet minimum threshold (≥80%). # WHERE: ci.yml # STATUS: ✅ Implemented — --cov-fail-under=80 # R-087: CI MUST include Redis and PostgreSQL services for integration tests. # WHERE: ci.yml # STATUS: ✅ Implemented — Redis 7 + Postgres services (R-167) # R-088: BDD specs MUST run in CI (behave tests/bdd/features/). # WHERE: ci.yml # STATUS: ✅ Implemented — behave step in test job # R-089: Deployment MUST be gated on test success (needs: test meaningful only if tests run). # WHERE: ci.yml deploy job needs: test # STATUS: ✅ Implemented — deploy gated on test job # R-090: Every new function → at least 1 unit test. # R-091: Every new API endpoint → at least 1 integration test. # R-092: Every new feature → BDD scenario in .agent/BDD_SPECS.md FIRST. # R-093: Mock ALL external services in unit tests (OpenAI, DB, Redis, email). # R-094: Use pytest fixtures for shared setup — no repeated setup code. # ═══════════════════════════════════════════════════════════════════════════════ # §15. OBSERVABILITY & MONITORING # ═══════════════════════════════════════════════════════════════════════════════ # R-095: Structured logging (structlog) MUST be used everywhere. No print(). # WHERE: All services # STATUS: ✅ Implemented # R-096: Request/response logging middleware MUST be active. # WHERE: app/middleware/logging_middleware.py # STATUS: ✅ Implemented # R-097: Token values, passwords, API keys MUST never appear in logs. # WHERE: app/core/access/token_crypto.py:5-6 (rule documented) # STATUS: ✅ Implemented # R-098: Health endpoint MUST check DB + Redis + ChromaDB. # WHERE: app/dependencies.py:216-249 # STATUS: ✅ Implemented # R-099: Prometheus /metrics endpoint MUST be configured. # WHERE: app/main.py — /metrics + PrometheusMiddleware # STATUS: ✅ Implemented — requires prometheus_client (R-159) # R-100: Error alerting (Sentry or equivalent) MUST be configured. # WHERE: app/main.py — sentry_sdk.init() when SENTRY_DSN set # STATUS: ✅ Implemented — activate via SENTRY_DSN env (R-159) # R-101: Audit log MUST capture actor IP address (for forensics). # STATUS: ❌ NOT IMPLEMENTED — only captures actor_email # R-102: Per-step pipeline latency MUST be tracked (not just total response_ms). # STATUS: ❌ NOT IMPLEMENTED # ═══════════════════════════════════════════════════════════════════════════════ # §16. DEPLOYMENT & INFRASTRUCTURE # ═══════════════════════════════════════════════════════════════════════════════ # R-103: Secrets MUST NOT be in version control. # WHERE: .env in .gitignore # STATUS: ✅ Implemented # R-104: Docker image MUST use multi-stage build (minimal production image). # WHERE: Dockerfile # STATUS: ✅ Implemented # R-105: Container SHOULD run as non-root user. # WHERE: Dockerfile # STATUS: ✅ Implemented — USER appuser # R-106: Database backups MUST be automated (Celery Beat schedule). # WHERE: app/tasks/celery_app.py beat_schedule # STATUS: ✅ Implemented — daily-database-backup (R-131) # R-107: Health check MUST have startup grace period. # WHERE: Dockerfile — 180s start period # STATUS: ✅ Implemented # ═══════════════════════════════════════════════════════════════════════════════ # §17. CODE STRUCTURE CONVENTIONS # ═══════════════════════════════════════════════════════════════════════════════ # R-108: All configuration from app/config.py (pydantic BaseSettings). Never hardcode. # R-109: All DI via FastAPI Depends() in app/dependencies.py. # R-110: All DB access via repository pattern (app/repositories/). # R-111: Business logic in app/services/ — never in route handlers. # R-112: Background work in Celery tasks (app/tasks/) — never asyncio.create_task for long work. # R-113: Custom exceptions in app/exceptions/ — never raise generic Exception. # R-114: Every module MUST have a module-level docstring. # R-115: Every function MUST have type-annotated signature + docstring. # R-116: No function longer than 40 lines — refactor into named helpers. # R-117: Update FILE_MAP.md when adding or removing any file. # R-118: Update BDD_SPECS.md before implementing any new user-facing feature. # R-119: Read .agent/RULES.md and .agent/FILE_MAP.md before every coding session. # R-120: All destructive UI actions require confirmation dialog with typed confirmation. # ═══════════════════════════════════════════════════════════════════════════════ # §18. FRONTEND & WIDGET SECURITY # ═══════════════════════════════════════════════════════════════════════════════ # R-121: Widget escHtml() MUST escape ALL 5 HTML entities (&, <, >, ", '). # WHY: Incomplete escaping enables XSS in bot responses rendered as innerHTML. # WHERE: static/widget.js:483-484 # STATUS: ✅ Implemented — all 5 entities covered # R-122: Widget link buttons MUST always use target="_blank" rel="noopener". # WHY: Without noopener, linked pages can hijack the parent window (reverse tabnabbing). # WHERE: static/widget.js:371 # STATUS: ✅ Implemented # R-123: Widget MUST NOT expose subscription token in DOM or global JS scope. # WHY: Any page script can steal the token from window variables or DOM attributes. # WHERE: static/widget.js:22-38 — token stored in closure variable (not window) # STATUS: ✅ Token stays in IIFE closure # R-124: Admin SPA MUST redirect to /login on 401 response (not show broken UI). # WHY: Stale JWTs leave admin in a broken state with no clear recovery. # WHERE: static/auth-client.js # STATUS: ⚠️ PARTIAL — auth-client.js exists but redirect behavior varies by page # R-125: /widget endpoint MUST NOT serve with Access-Control-Allow-Origin: *. # WHERE: app/api/widget.py — wildcard CORS removed; /widget in main.py never had it # STATUS: ✅ Implemented — no wildcard CORS on widget HTML responses # R-126: /covers static mount MUST disable directory listing. # WHY: Attackers can enumerate all cover images and map author IDs from filenames. # WHERE: app/main.py:265-266 — StaticFiles(directory=covers_dir) # STATUS: ⚠️ StaticFiles does NOT list by default, but doesn't 404 on /covers/ root # R-127: Widget size budget: widget.js MUST stay under 25KB minified. # WHY: Widget loads on third-party author websites; large JS hurts page speed. # WHERE: static/widget.js — currently 19.7KB unminified (523 lines) # STATUS: ⚠️ Under budget unminified, but no minification or CDN configured # ═══════════════════════════════════════════════════════════════════════════════ # §19. CELERY TASKS — DATA QUALITY # ═══════════════════════════════════════════════════════════════════════════════ # R-128: Weekly digest email MUST populate actual stats (not hardcoded 0). # WHERE: app/tasks/email_task.py + analytics_repo.weekly_digest_stats # STATUS: ✅ Implemented (R-139) # R-129: Token warning email MUST include actual tokens_used and tokens_total. # WHERE: app/tasks/email_task.py # STATUS: ✅ Implemented — real values from DB (R-140) # R-130: Link health check MUST NOT follow redirects to internal/private IPs (SSRF). # WHERE: app/tasks/link_health_task.py → _is_internal_url() # STATUS: ✅ Implemented — private/loopback blocklist before fetch # R-131: Celery Beat MUST include the backup task in its schedule. # WHERE: app/tasks/celery_app.py # STATUS: ✅ Implemented — daily-database-backup scheduled # ═══════════════════════════════════════════════════════════════════════════════ # §20. STARTUP & INITIALIZATION # ═══════════════════════════════════════════════════════════════════════════════ # R-132: Schema migrations MUST NOT use raw ALTER TABLE in application startup code. # WHERE: app/core/startup/db.py — residual ALTER for list_price columns # STATUS: ⚠️ PARTIAL — Alembic primary; startup still has fallback ALTER TABLE # R-133: OpenAPI docs (/docs, /redoc) MUST be disabled or auth-gated in production. # WHERE: app/main.py — docs_url=None when APP_ENV=production # STATUS: ⚠️ PARTIAL — disabled in production; public in dev/staging # R-134: SuperAdmin seed MUST support TOTP enrollment (not pre-generated secret). # WHERE: app/core/startup/seeder.py — totp_secret=None; enrollment on first login # STATUS: ✅ Implemented — QR enrollment flow (SUPERADMIN_TOTP_RULES.md) # ═══════════════════════════════════════════════════════════════════════════════ # §21. DEPLOYMENT — DOCKER & SERVICES # ═══════════════════════════════════════════════════════════════════════════════ # R-135: docker-compose.yml MUST NOT use deprecated 'version' key. # WHY: Docker Compose v2+ ignores the version key; keeping it causes warnings # and confuses developers about which spec is being used. # WHERE: docker-compose.yml:1 — version: "3.9" # STATUS: ⚠️ Works but deprecated — remove the line # R-136: Flower (Celery monitoring) MUST require authentication. # WHERE: docker-compose.yml # STATUS: ✅ Implemented — flower --basic-auth=admin:${FLOWER_PASSWORD} # R-137: docker-compose api command MUST NOT use --reload in production. # WHY: --reload enables code injection via filesystem writes and wastes CPU. # WHERE: docker-compose.yml:79 — uvicorn ... --reload # STATUS: ⚠️ PARTIAL — file is marked "Development" but no production override exists # R-138: User cascade delete MUST clean up ChromaDB collections. # WHERE: app/services/superadmin_service.py # STATUS: ✅ Implemented (author_prefix collection purge) # ═══════════════════════════════════════════════════════════════════════════════ # §22. ENTERPRISE WORTH ROADMAP (R-139–R-173) # ═══════════════════════════════════════════════════════════════════════════════ # R-139: Weekly digest MUST use real analytics — STATUS: ✅ email_task + analytics_repo.weekly_digest_stats # R-140: Token budget warning email at 80% — STATUS: ✅ token_budget + EmailService.send_token_budget_warning # R-141: Admin analytics UI API-backed only — STATUS: ✅ admin.html loadAnalytics # R-142: Link-click funnel counts conversions — STATUS: ✅ track_link_click + funnel_counts # R-143: JWT refresh blacklists refresh jti — STATUS: ✅ schemas_router refresh # R-144: Logout blacklists refresh jti — STATUS: ✅ schemas_router logout # R-145: SuperAdmin mandatory TOTP — STATUS: ✅ dependencies.get_current_superadmin # R-146: Q&A CSV HTML strip — STATUS: ✅ qa_service._sanitize_qa_text # R-147–149: Book lifecycle — STATUS: ✅ books router + book_service # R-150: Celery ingestion — STATUS: ✅ ingestion_service.schedule_file_ingestion # R-151: Duplicate upload warning — STATUS: ✅ start_file_upload duplicate check # R-152: Cross-book RAG — STATUS: ✅ pipeline core + ask_all_books # R-157: Alembic migrations — STATUS: ✅ alembic/ + db.py no ALTER loop # R-158: Celery deploy in compose — STATUS: ✅ docker-compose context fixed # R-159: prometheus_client + sentry-sdk — STATUS: ✅ requirements.txt # R-161: Author delete Chroma purge — STATUS: ✅ superadmin_service # R-165: BDD feature files — STATUS: ✅ tests/bdd/features/enterprise_roadmap.feature # R-166: CI mypy — STATUS: ✅ ci.yml # R-167: CI Postgres+Redis — STATUS: ✅ ci.yml services # R-169: Onboarding checklist — STATUS: ✅ admin/routers/onboarding.py # R-172: Competitor guard — STATUS: ✅ guardrails.check_boundary # R-173: Billing webhooks — STATUS: ✅ billing_service + billing_router # ═══════════════════════════════════════════════════════════════════════════════ # SUMMARY: 173 RULES (R-001–R-173) — RECONCILED 2026-07-07 # ═══════════════════════════════════════════════════════════════════════════════ # Rules with explicit STATUS (R-001–R-089, R-095–R-138, R-139–R-173): 168 # Process conventions without STATUS (R-090–R-094, R-108–R-120): 5 + 13 = 18 # # ✅ Implemented: 147 rules (112 core + 35 enterprise roadmap §22) # ❌ Not Implemented: 2 rules (R-101 audit IP, R-102 pipeline latency) # ⚠️ Partial: 19 rules (acceptable or needs polish — see §23 P1/P2) # ═══════════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════════ # §23. PRIORITY BACKLOG (post-reconciliation) # ═══════════════════════════════════════════════════════════════════════════════ # # P0 — COMPLETE (2026-07-07): R-080 async backup, R-125 widget CORS, R-055 Redis auth # # P1 — HARDENING (next sprint) # R-025 Trusted-proxy list for X-Forwarded-For (rate limit bypass) # R-030 UUID validation on all path params (book_id, session_id, etc.) # R-044 Remove str(e) from superadmin authors/grants 400 responses # R-024 Reject upload via Content-Length before chunked read # R-084 Unify ingestion SSE Redis channel naming # R-082 Remove asyncio ingestion fallback; Celery-only path # R-132 Remove startup ALTER TABLE fallback; Alembic-only migrations # # P2 — POLISH / OPS (when capacity allows) # R-101 Audit log actor IP address # R-102 Per-step RAG pipeline latency metrics # R-053 Review composite DB indexes for hot query paths # R-133 Auth-gate /docs on staging (not just production) # R-124 Consistent admin SPA 401 → login redirect on all pages # R-127 Widget minification + CDN # R-059 Redis AOF persistence in Docker # R-135 Remove deprecated docker-compose version key # R-137 Production docker-compose override without --reload # # NOTE: §22 (R-139–R-173) documents enterprise fixes that overlap §1–§21. # When a §22 entry marks a rule ✅, the matching §1–§21 entry should match. # Re-reconcile after any major security or infra change. # ═══════════════════════════════════════════════════════════════════════════════