Proprietary — Uued Viljapuuaiad OÜ / Code Zero Group — All Rights Reserved
Nexus — Engineering
v1.0.0 2026-03-25 Baseline: 461 tests passing — c0072f4

Production Readiness

Gap register produced against codebase HEAD c0072f4, branch feature/pfp-pyrometrix. 10 gaps, 2 at P0, 4 at P1. P0 items must close before first external API access. P1 items must close before EC2 production deployment.

10 Gaps Open 2 Critical (P0)
01 Gap Summary
ID Severity Title Effort Status
GAP-01P0 — Critical18 unauthenticated routes2hOpen
GAP-02P1 — HighSingle shared key, no RBAC1dOpen
GAP-03P0 — CriticalPath traversal in rule file editorFixed
GAP-04P1 — HighRule source-of-truth ambiguity4hOpen
GAP-05P1 — HighNo rule audit trail1dOpen
GAP-06P1 — HighNo production deployment path4h + UATBlocked
GAP-07P2 — MediumNo structured logging4hOpen
GAP-08P2 — MediumNo rate limiting2hOpen
GAP-09P2 — MediumSQLite no backup or HA2h (short)Open
GAP-10P3 — LowNo load or penetration testing1 weekOpen
GAP-01 Unauthenticated Route Exposure P0 — Critical

Affected files: nexus/api/app.py, nexus/api/routes_rule_editor.py

18 routes are reachable without an API key. Any caller who discovers an endpoint can read all profiles and rules, execute the calculation engine with arbitrary inputs, trigger ingestion pipeline runs against arbitrary data sources, and read raw rule JSON files from disk.

Affected Routes

GET  /api/profiles                    — exposes full rule profile index



GET  /api/profiles/active             — exposes active rule set + formulas



GET  /api/profiles/{profile_id}       — same



GET  /rules                           — all loaded rules from JSON files



GET  /types                           — internal domain model



POST /validate                        — runs arbitrary attacker input through engine



POST /pipeline/score                  — scoring pipeline, unbounded input



POST /execute                         — generic rule executor



POST /edr/validate                    — EDR validation, unbounded input



POST /edr/validate/batch              — batch — potential DoS: 5000 entity cap



POST /ingest                          — accepts inline datasource config



GET  /ingest/datasources              — exposes datasource config filenames on disk



GET  /ingest/schema/{datasource_name} — connects to external DBs on attacker request



GET  /rules/files                     — lists all rule file paths on disk



GET  /rules/files/{path}              — reads any rule file from disk



GET  /rules/{rule_id}                 — reads individual rule by ID



POST /rules                           — creates new rule record



PUT  /rules/{rule_id}                 — overwrites rule record

Remediation

Apply dependencies=[Depends(require_api_key)] to every route listed above. The require_api_key dependency already exists in nexus/api/app.py and is used by PFP, admin, and profile-write endpoints. Add 18 regression tests asserting HTTP 401 on unauthenticated requests.

GAP-02 Single Shared API Key — No RBAC P1 — High

Affected files: nexus/auth.py, admin/admin.db

Current require_api_key compares the X-API-Key header against a single NEXUS_API_KEY environment variable. Any valid key can call any endpoint. No per-key scoping, no expiry, no revocation record, no client identity.

Remediation

  • Create _tbl_api_keys table (see Data Model for DDL)
  • Store keys as bcrypt hashes — never plaintext
  • Add scopes column: read, compute, admin
  • Update require_api_key to hash-compare against DB, check scopes, check expires_at
  • Gate POST /execute and POST /ingest on admin scope
  • CLI or admin panel endpoint to issue, rotate, and revoke keys
GAP-03 Path Traversal in Rule File Editor Fixed

Affected files: nexus/api/routes_rule_editor.py

Historical gap — fixed in HEAD c0072f4. GET /rules/files/{path} previously allowed ../ sequences to read arbitrary filesystem paths outside the rules directory.

Fix Applied

_resolve_path() function validates that the resolved path starts with RULES_DIR.resolve(). Path traversal sequences raise HTTPException(403). Verified in test suite.

GAP-04 Rule Source-of-Truth Ambiguity P1 — High

Affected files: nexus/rules/**/*.json, admin/admin.db, seeding scripts

Rules live in two locations simultaneously: JSON files on disk (seeding source) and _tbl_calcrule_rules rows in the DB (runtime query target). If JSON files are modified after seeding, the DB is stale. If DB is updated via admin panel, JSON files are stale. The TCO profile was contaminated with 65 freelance/EDR rules because a seeding script globbed without domain filtering — and this can happen again.

Remediation

  • Add header comment to all JSON files: // IMPORT FORMAT ONLY — runtime source is admin.db
  • Add test: no seeding script uses glob("*.json") without domain filter
  • Add last_seeded_from and last_seeded_at columns to _tbl_calcrule_profiles
  • Replace _ensure_schema() ad-hoc ALTER TABLE calls with a numbered migration chain
GAP-05 No Rule Audit Trail P1 — High

Affected files: nexus/api/routes_rule_editor.py

PATCH, DELETE, and POST operations on rules leave no record of what changed, when, or which key changed it. No rollback is possible. In a fire-safety clearance domain (Pyrometrix), a contaminated or silently-mutated rule set is a regulatory risk.

Target Table DDL

CREATE TABLE IF NOT EXISTS _tbl_calcrule_rule_audit (



    id          INTEGER PRIMARY KEY AUTOINCREMENT,



    rule_id     TEXT    NOT NULL,



    changed_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,



    changed_by  TEXT,           -- API key identifier (hashed key prefix)



    operation   TEXT    NOT NULL CHECK(operation IN ('INSERT','UPDATE','DELETE')),



    field       TEXT,           -- field name changed (NULL for INSERT/DELETE)



    old_value   TEXT,           -- JSON-serialised previous value



    new_value   TEXT            -- JSON-serialised new value



);

Write one row per changed field in every PATCH handler before applying the update. Expose GET /rules/{id}/history to surface rule lineage.

GAP-06 No Production Deployment Path P1 — Blocked on UAT

Affected files: scripts/deploy/, nexus/api/lambda_handler.py

EC2 deployment plan is documented in docs/aws-hosting-estimate.html but is blocked on two outstanding Pyrometrix UAT questions: (1) SZA scope alignment, (2) multi-element geometry handling. The Lambda zip does not bundle ui/index.html — the StaticFiles mount is conditional on _UI_DIR.exists() which evaluates false in a Lambda environment.

Remediation

  • Resolve two UAT questions with Pyrometrix client — human action required
  • Lambda: bundle ui/ into deployment zip, or serve from S3 + CloudFront with CORS headers
  • Write tests/e2e/test_smoke.py: hit /health and /ui/index.html, fail CI if non-200
  • TLS: terminate at ALB with ACM cert — do not terminate in FastAPI
  • Create docker-compose.prod.yml with all secrets injected as environment variables — no .env on EC2
GAP-07 No Structured Logging P2 — Medium

print() statements throughout the engine and API. No correlation IDs, no log levels, no JSON format, no CloudWatch shipping. Debugging production failures requires log scraping.

Remediation

  • Replace all print() with logging.getLogger(__name__)
  • Add FastAPI middleware generating X-Request-ID UUID per request, injected into log context
  • Set LOG_FORMAT=json in production for structured CloudWatch ingestion
  • CloudWatch log group: /nexus/api/{env}
GAP-08 No Rate Limiting P2 — Medium

No throttle on compute endpoints. A single caller can saturate the engine with repeated POST /pfp/clearance or POST /edr/validate/batch requests. The 5 000 entity cap on batch EDR helps but does not prevent sustained bombardment.

Remediation

Add slowapi ASGI rate limiter:

  • Compute endpoints (/execute, /pfp/clearance, /edr/validate): 100 req / min per API key
  • Ingest endpoints (/ingest): 10 req / min per API key
  • Read endpoints (/rules, /profiles): 500 req / min per API key
GAP-09 SQLite — No Backup or HA P2 — Medium

admin/admin.db is the only copy of all profile and rule data. EC2 instance termination results in total data loss. No WAL replication, no backup, no replica.

Remediation

  • Short term: Scheduled daily S3 backup via cron + aws s3 cp admin/admin.db s3://nexus-db-backup/$(date +%Y-%m-%d).db
  • Medium term: Migrate to RDS PostgreSQL. profile_store.py uses standard SQL — migration is mechanical
  • Long term: DeltaPrism as rule store (see Architecture — DataSource)
GAP-10 No Load or Penetration Testing P3 — Low

461 unit tests cover rule logic and structural correctness. Nothing covers concurrent request handling, DoS resilience, auth bypass attempts, or malformed formula payload injection.

Remediation

  • Load test: locust against /pfp/clearance, 50 concurrent users, 5 min run, target P95 < 200ms
  • Static analysis: bandit -r nexus/ -ll, zero HIGH findings to pass
  • Fuzz: hypothesis or atheris against POST /execute formula field
  • Path traversal regression: automated test against _resolve_path() with known payloads