Property PrismDev Hub

Risk Backlog

Known risks, gaps, and improvement items identified across the codebase, security audits, and SOC 2 readiness assessment. Items here are not emergencies — they are things to work through over time.

Updated Apr 3, 2026

Owner: Backend Lead Last Edited: March 26, 2026 Last Reviewed: March 26, 2026


How to Use This

Each item has a severity, category, and effort estimate. Work through these in severity order. When an item is resolved, mark it resolved with a date rather than deleting it — the history is useful.

Severity:

  • High — meaningful security or operational risk; address in the next sprint
  • Medium — real risk but limited blast radius or requires specific conditions
  • Low — quality/hygiene issue or SOC 2 evidence gap with no direct attack surface

Security Risks

HIGH

RISK-S01: Branch protection not enforced (GitHub plan limitation)

Category: Change management Detail: Branch protection rules (required reviews, required CI checks, no force push) cannot be enforced on private org repos without a paid GitHub org plan (Team/Enterprise). The protect script exists at .github/scripts/enforce-branch-protection.sh but cannot be applied until the org plan is upgraded. Impact: PRs can be merged without review or passing CI; force pushes to main/qa are possible. Fix: Upgrade the GitHub org (Magi-Reseach) to Team/Enterprise plan, then run the protect script. See docs/ops/go-backend-runbook.md Section 13. Status: Open


RISK-S02: No frontend org-isolation regression tests

Category: Test coverage / data isolation Detail: The Go backend has cross-org denial regression tests (org_isolation_test.go) for comps, TIMs, and reports. However, the frontend has no equivalent tests verifying that API responses don't leak data from wrong orgs. This is primarily a backend concern, but the frontend also constructs API requests with org context from Clerk — bugs there could result in wrong-org requests. Impact: If a frontend bug sends the wrong org context, there is no automated test catching it. Fix: Add frontend integration tests that mock the API and verify only the correct org's data is rendered. Alternatively, expand backend handler tests to cover tenant/owner/membership cross-org denial. Status: Open


RISK-S03: ~70% of handlers and ~75% of services lack tests

Category: Test coverage Detail: As of February 2026 (BACKEND_RISK_AND_TEST_REVIEW.md), the following handlers had no tests: export, admin, report, apikey, membership, building, comp, tim, geography, lookup, vacancy, labor blocks, broker assignment, building org override, leasing company, broker, audit log, building logistics, key point, tenant, owner, building park. Many have since had tests added, but coverage is still partial. Impact: Regressions — including security regressions — in untested handlers go undetected until manual QA or production. Fix: Work through the handler list and add at minimum: scope contract tests, happy-path tests, and 404/403 for cross-org and missing resources. Prioritize handlers that touch sensitive org-scoped data. Status: Resolved 2026-03-26 — all previously untested handlers now covered: audit_log, broker, leasing_company, building_org_override, broker_assignment, labor_block_group. Each has scope contract, happy-path, 404/403, and org-isolation tests.


RISK-S04: All PostgreSQL repositories lack direct tests

Category: Test coverage / data layer Detail: 31 Postgres repository files have no direct tests. The mock repositories used in handler/service tests simulate behavior but do not test the actual SQL. Impact: SQL bugs (wrong WHERE clause, missing org filter, incorrect JOIN) are not caught until integration or production. Fix: Add integration tests that run against a real (test) database. This requires a test DB setup — either a local Docker Postgres or a GitHub Actions Postgres service container. Status: Open


MEDIUM

RISK-S05: API key prefix may not be uniquely identifying

Category: Operational security Detail: API key generation uses key_prefix := plainKey[:8] (see go-backend/internal/service/apikey_service.go). Because keys are prefixed with prism_live_ (9 chars), the stored prefix is always prism_li, which does not help humans identify which key is which. Impact: If a key is accidentally logged or exposed in a partial form, the prefix provides no identification value. Operators cannot tell keys apart from the stored prefix alone. Fix: Derive the stored prefix from the random segment rather than the full key (e.g. plainKey[9:17] to skip the prism_live_ prefix). Status: Resolved 2026-03-26 — changed to plainKey[11:19] to skip the full prism_live_ prefix (11 chars); stored prefix now reflects the random segment.


RISK-S06: Import staging files not cleaned up on job completion

Category: Data hygiene / disk usage Detail: CSV files uploaded for import jobs are staged at IMPORT_UPLOAD_DIR. There is no evidence in the codebase of a cleanup step that removes processed files after an import job completes or fails. Impact: Over time, the import directory accumulates CSV files on disk. On a long-running server, this could exhaust disk space. The files also contain customer data at rest in plaintext on the server filesystem. Fix: Add a cleanup step in the import job completion path to delete the staged file after processing. Log the deletion. Status: Resolved (pre-existing) — Process() in import_job_service.go already calls os.Remove(filePath) after job completion. Confirmed 2026-03-26 during audit review.


RISK-S07: Audit log coverage gaps for PATCH/partial update operations

Category: Auditability Detail: The audit middleware covers POST, PUT, and DELETE on sensitive route prefixes. If any handlers use PATCH for partial updates, those are not currently captured. Impact: Partial updates to sensitive records may not appear in the audit log. Fix: Audit the handler list for any PATCH routes on sensitive resources. Either add PATCH to the audited methods, or confirm no sensitive PATCH routes exist. Status: Resolved 2026-03-26 — added PATCH to isSensitiveOperation in audit.go; mapped to action partial_update. One sensitive PATCH route confirmed: PATCH /api/v1/tims/{id}/status.


RISK-S08: No log retention policy enforced

Category: SOC 2 / compliance Detail: Application logs and audit logs are produced but there is no documented or enforced retention period. SOC 2 typically expects at least 1 year of log retention. Impact: Logs needed for incident investigation or audit evidence may be purged before they are needed. Fix: Define retention periods (application logs: 90 days minimum; audit logs: 1 year minimum). Configure the managed logging service or DB to enforce these. Status: Open


RISK-S09: No vendor inventory or DPA tracking

Category: SOC 2 / vendor risk Detail: The SOC 2 readiness assessment identified the following likely in-scope vendors with no documented review: Clerk, Supabase/RDS, Redis provider, Cloudflare, Slack, GitHub, monitoring vendors. No vendor inventory, DPAs, or security review records exist in the repo. Impact: SOC 2 Type I requires demonstrating vendor risk management. Customer contracts may also require a subprocessor list. Fix: Create docs/security/vendor-inventory.md listing each vendor, their data access, contract/DPA status, and last review date. Status: Open


RISK-S10: No formal risk register or control ownership model

Category: SOC 2 / governance Detail: SOC 2 requires a named control owner for each control domain and a formal risk assessment. Neither exists in a structured form. Fix: Create a lightweight control library with named owners. Assign owners for: security, ops, app engineering, access administration, compliance evidence. Conduct an initial risk assessment and record it. Status: Open


LOW

RISK-S11: No current data import runbook

Category: Documentation / operations Detail: Legacy import docs exist under docs/legacy/ but they describe a prior Python/Supabase workflow. The current Go-based import job system has no non-legacy runbook. Fix: Write docs/data-import/README.md describing the current import path (upload CSV via /api/v1/import-jobs, queue for processing, monitor status). Status: Open


RISK-S12: Marketing app has no deploy/security runbook

Category: Documentation / operations Detail: docs/marketing/README.md covers quick start and env vars, but there is no staging/prod deploy runbook, no Turnstile key rotation procedure, and no security hardening guidance for the marketing app. Fix: Write docs/marketing/deploy-runbook.md covering prod env vars, Turnstile keys, analytics IDs, caching strategy, and security headers. Status: Open


RISK-S13: No monorepo env-var matrix

Category: Documentation / operations Detail: No single document lists all environment variables across frontend/, go-backend/, and marketing/ with their purpose, required/optional status, and whether they are safe to expose. Fix: Write docs/config/env-vars.md as a single reference table. Status: Open


RISK-S14: Duplicate schema files risk future divergence

Category: Documentation / schema hygiene Detail: database/PRISM vNext.sql and database/PRISM_vNext_FINAL.sql were identical as of February 14, 2026 (docs/audit/documentation-audit-2026-02-14.md). Having two files increases the risk that they diverge silently in the future. Fix: Delete the duplicate file or add a CI check that asserts both files are identical. Document which is canonical. Status: Open


RISK-S15: No export refresh scheduling runbook

Category: Documentation / operations Detail: docs/backend/export-layer.md covers the export schema objects and refresh functions, but there is no runbook for scheduling export refreshes (pg_cron vs external scheduler) per environment. Fix: Add a section to the export layer doc or production runbook describing how and when export refreshes are triggered in each environment. Status: Open


Operational Evidence Gaps (SOC 2 Type I prerequisite)

These are not code or doc gaps — they are evidence that must be generated by operating the system. Listed here so nothing gets missed.

GapTargetOwner
Monthly access review recordsdocs/audit/access-reviews/Admin
Restore drill records (quarterly)docs/audit/restore-drills/Ops
Vulnerability log entriesdocs/security/vulnerability-management.mdBackend Lead
Incident tabletop exercisedocs/audit/incidents/tabletop-YYYY-MM-DD.mdBackend Lead
MFA enforcement screenshots (GitHub, Clerk, Cloudflare)docs/audit/screenshots/Admin
Branch protection config exportdocs/audit/screenshots/Admin
Backup job records exportdocs/audit/screenshots/Ops
Secret rotation logdocs/security/secrets-management.md Section 5Ops

Resolved Items (for reference)

IDDescriptionResolved date
SEC-001Import job status mass assignment / workflow bypass2026-02-17
SEC-002BI credentials returned in API response2026-02-17
SEC-003Dashboard SQL string concatenation (injection risk)2026-02-17
SEC-004JWKS URL SSRF risk2026-02-17
SEC-005Public /ready endpoint2026-02-17
SEC-006Swagger and metrics unprotected in non-production2026-02-17
SEC-007Default ALLOWED_ORIGINS in productionDeferred to go-live config
SEC-008Search ILIKE wildcard abuse2026-02-17
Frontend CI (no automated typecheck/lint/test on PRs)2026-03-26
No CI security automation (Dependabot, CodeQL, secret scanning, dep review)2026-03-26
Audit log missing sensitive routes (memberships, imports, exports, operations, admin)2026-03-26
No cross-org denial regression tests2026-03-26
RISK-S05API key prefix always prism_li (non-identifying)2026-03-26
RISK-S06Import staging files not cleaned up (pre-existing fix confirmed)2026-03-26
RISK-S07PATCH routes not captured by audit middleware2026-03-26
actor_user_id filter in audit log handler had no length cap (unbounded string passed to query)2026-03-26
radius_miles in labor block group handler had no upper bound (could trigger oversized spatial queries); lat/lng had no range validation2026-03-26
RISK-S03Handler test coverage gaps (6 handlers untested)2026-03-26