SOC 1 / SOC 2 readiness
43% toward the Type I audit·43% including Type II
Every gap from Kyle's SOC 2 roadmap (27 Oct 2026), checked against the SCOPE code on 27 Oct. Done and Partial say how much of the fix is in the code, Not started means none of it is, and Org task is an account, setting, document or contract. The SOC 2 column is the AICPA's own numbering of the Trust Services Criteria (CC = common criteria, A = Availability, C = Confidentiality); the ID is ours, one per gap or task. Click a column heading to sort, or By priority to put both tables soonest first. Tick an item when it is done: the tick is saved for everyone with your name and the date, and unticking puts our assessment back. SOC 1 is not planned; SCOPE touches nobody's financial reporting. The items that are not code have drafts and owners on SOC 2: the work outside the code.
Loading ticks…
Gaps against SOC 2
Step 0, the 39 gaps to close before the Type I audit, and the 12 for the Type II phase.
| Done | ID | Gap and fix | Evidence | ||||
|---|---|---|---|---|---|---|---|
| OW-1 | Code lives in developers' personal GitHub accounts Fix: Transfer every SCOPE repo into the lucent-axis GitHub org; developers get access through teams only Step 0: Lucent Axis-owned accounts | Compliance owner | Step 0 | Done | Boundary (28 Oct): only Lucent Axis accounts are audited. The code is in lucent-axis/scope, private, the origin remote since 28 Oct; access through the org. Dan's personal repo daniel-sgalia/scope-safety-poc is a mirror outside the boundary that Paul demos from, archived after his switch | ||
| OW-2 | Vercel projects sit on a developer's account Fix: Move them to a Lucent Axis Vercel team on company billing; developers invited with least-privilege roles Step 0: Lucent Axis-owned accounts | App developer | Step 0 | Done | Boundary (28 Oct): only Lucent Axis accounts are audited. The Lucent Axis Vercel team lucent-axis (created by Kyle 27 Oct, Pro, company billing, MFA enforced) holds scope-web, built from lucent-axis/scope, functions in iad1; Kyle Owner, Dan Member (read 28 Oct). What is left on it is not this item: Production env vars (OW-3, ORG-11), a second owner (OW-6), billing after the trial (Kyle). The personal scope-safety-poc and scope-novvia are outside the boundary | ||
| OW-3 | Supabase projects sit in a developer's organization Fix: Transfer the projects to a Lucent Axis Supabase organization on company billing; members by role Step 0: Lucent Axis-owned accounts | Data / platform engineer | Step 0 | Done | Boundary (28 Oct): scope-web's own Supabase project (ref zvxabodonzynkxmcpbji, us-east-1, Postgres 17, SSL available but not yet enforced, DB-7), connected by Kyle's integration on the Lucent Axis team. It began as a copy of the personal demo database as of 27 Oct, and on 28 Oct (Dan's decision to keep the copy) it was brought current: a full backup first; the 10 pending migrations applied (the first needed set constraints all immediate before its ALTER, because the copy holds data); app.demo_baseline_drift restored to its newest body; the personas add-on installed; and **every table's grants restored**, because the copy had reset 83 tables and 26 column grants to Supabase's defaults (signed-in users could, for example, update action and incident directly). All 23 verify_*.sql scripts pass against it, and its permissions match the personal database's plus the intended tightening. Read in the dashboard 28 Oct: the project (named scope-dev in the dashboard) is the only one in the **Lucent Axis** Supabase organization, on the **Pro** plan, Nano compute, linked to lucent-axis/scope; members Kyle (Owner) and Dan (Developer), both with MFA on. scope-web signs in against it with the rotated password (checked as the VP persona, 28 Oct). What remains on the project is DB-7, ID-1, ID-7 and AV-2 | ||
| OW-4 | Domain and DNS for the SCOPE app may be on a personal account Fix: Move them to the company registrar and DNS Step 0: Lucent Axis-owned accounts | Compliance owner | Step 0 | Done | Boundary (28 Oct): only Lucent Axis accounts are audited. No domain is used: scope-web is served on *.vercel.app under the Lucent Axis team, so nothing sits on a personal registrar. A company domain, if bought, goes in a company registrar account (Kyle or the Business lead) | ||
| OW-5 | Other services (email sending, error tracking, any others) may be on personal accounts Fix: Inventory every service SCOPE uses and move each to a company account Step 0: Lucent Axis-owned accounts | App developer | Step 0 | Partial | Boundary (28 Oct): only Lucent Axis accounts are audited. In it: GitHub, Vercel and Supabase under Lucent Axis; client builds' AI on AWS Bedrock in a Lucent Axis AWS account, which does not exist yet. No email or error-tracking service is installed. The personal demo's Anthropic key is outside the boundary. Inventory: docs/compliance/system-inventory.md | ||
| OW-6 | Owner rights and recovery codes held by individuals Fix: Lucent Axis holds owner on every account, at least two owners, recovery codes in the company vault Kyle decides · Owner switch: Make Dan a second Owner on the GitHub org, the Vercel team and the Supabase organization, and accept the vault invite (Bitwarden free) Step 0: Lucent Axis-owned accounts | Compliance owner | Step 0 | Org task | Boundary (28 Oct): only Lucent Axis accounts are audited. Still one owner on each: the lucent-axis GitHub org (Kyle; Dan a member, re-read 28 Oct) and the lucent-axis Vercel team (Kyle; Dan is Member, re-read from the API 28 Oct). No company vault yet for recovery codes. Owner: Kyle. Supabase too: Kyle is the only Owner of the Lucent Axis organization, Dan is Developer | ||
| OW-7 | No list of the systems and accounts SCOPE runs on Fix: System inventory: each account, owner, billing and who has access; feeds the system description Step 0: Lucent Axis-owned accounts | Compliance owner | Step 0 | Done | Boundary (28 Oct): only Lucent Axis accounts are audited. Drafted: docs/compliance/system-inventory.md and docs/compliance/system-description.md, now describing the Lucent Axis accounts. The Supabase organization, plan, region and members were read in its dashboard on 28 Oct and are in the inventory; it is reviewed each quarter with the access review | ||
| OW-8 | Keys and secrets were created under the old accounts Fix: Regenerate every key and secret as the last step of each move (this also closes SC-1) Step 0: Lucent Axis-owned accounts | Data / platform engineer | Step 0 | Partial | Boundary (28 Oct): scope-web's keys were made by its own Supabase project. On 28 Oct the copied sign-ins got a fresh 24-character demo password (the six personas by gen_demo_auth.sql, the two View-as logins the same), every copied session and refresh token was deleted, and scope-web's Production NEXT_PUBLIC_DEMO_PASSWORD set to match; the password lives in the git-ignored .env.company and Vercel only. The hub's shared sales login keeps its password (a team password people type; rotate it with create_sales_login.sql when the team switches). Left: the database password into the vault and the unused service-role, secret and JWT values removed from the project (DB-7) | ||
| SC-1 | Shared demo password in the code and git history; Supabase anon and service-role keys not rotated Fix: Rotate both keys, purge the history, treat everything as compromised 1. Source code and CI (GitHub) | App developer | Before customer data | Partial | The demo password is NEXT_PUBLIC_DEMO_PASSWORD, so every build ships it to the browser (apps/web/lib/personas.ts); rotation cannot be seen from the repo. Gitleaks in CI finds nothing in history. 28 Oct (5b77b6d): the persona picker, the demo password and View as anyone are now a demo-only add-on (apps/web/addons/personas, supabase/addons/personas), and apply-tenant refuses NEXT_PUBLIC_DEMO_PASSWORD for a tenant without it, so a client build cannot carry the password. Left: the demo build in the Lucent Axis team still ships it by design, with a fresh password of its own (OW-8), and a client build needs its own sign-in (ID-2, ID-3) | ||
| SC-2 | Repo was public Fix: Make it private and move it into the lucent-axis org 1. Source code and CI (GitHub) | Compliance owner | Before customer data | Done | Boundary (28 Oct): only Lucent Axis accounts are audited. lucent-axis/scope is private and in the lucent-axis org | ||
| SC-3 | No secret scanning; no check of the built code for keys Fix: GitHub secret scanning + push protection; CI scan of the build output 1. Source code and CI (GitHub) | App developer | Before customer data | Done | 28 Oct, the free route (ORG-03, done): gitleaks over the whole history in the required secrets CI job, and gitleaks before every commit through the pre-commit hooks installed by default. GitHub's own secret scanning and push protection stay off by decision (paid on Team). 29 Oct: the built output is scanned too. scripts/security/scan-bundle.mjs reads apps/web/.next/static, what every visitor downloads, after the build CI job, and fails on a Supabase JWT whose role is not anon, seven key shapes (Supabase secret, Anthropic, AWS, GitHub, Stripe, private key blocks, database URLs with a password), and the VALUE of any non-NEXT_PUBLIC_ variable named like a secret present at build time; a canary secret is set for the build so that check always has one to find. Proved on a production build (64 files, clean) and on planted fakes (all three caught). The demo password is in the demo bundle by design and stays SC-1 | ||
| SC-4 | Nothing stops a new table going live without RLS Fix: CI test fails the build if any API-exposed table has RLS off 1. Source code and CI (GitHub) | App developer | Before customer data | Done | 28 Oct: three layers. The new-table guard (scripts/guards/new_table.py) refuses a migration whose table lacks RLS or audit columns, and runs in the pre-commit CI job, which the ruleset makes a required check, so an RLS-less table blocks a merge. scripts/db/verify_rls.sql fails on a public table with RLS off or a view or materialized view that reads around it, and scripts/db/verify_anon.sql on anything callable or writable signed out; the nightly database job runs both on a database built from every migration, green at run 40725900178 (28 Oct) with the anon migrations | ||
| SC-5 | Main branch not protected; changes can merge without review Fix: Branch protection for admins too, 1 approving review, required checks, emergency path 1. Source code and CI (GitHub) | Compliance owner | Before Type I | Done | Applied 28 Oct: ruleset main (id 26059181) on lucent-axis/scope, active, from .github/rulesets/main.json: pull request required, 0 approvals, merge commits only, no force-push or deletion, NO bypass for anyone, required checks check, secrets, pre-commit, build, data. A direct push to main is refused (GH013, seen 28 Oct); PRs #2 and #3 merged only after all five passed. 0 approvals instead of the roadmap's 1 is deliberate (one developer): the compensating control is Kyle's monthly review of merged PRs, in docs/compliance/change-management-policy.md. Emergency path: an owner sets the ruleset to Disabled, which the audit log records | ||
| SC-6 | No single migration chain across dev, staging and prod Fix: Supabase CLI migrations in Git, applied only by CI 1. Source code and CI (GitHub) | Data / platform engineer | Before customer data | Partial | Built, switched off. Every migration is in Git (supabase/migrations/), and the deploy workflows (4d0c3e8: deploy.yml, called by deploy-dev.yml on a green main, deploy-demo.yml on a published release, deploy-release.yml for a client's staging then production) apply them with supabase db push inside the deploy run, only on a commit whose five required checks passed, one run per database at a time, skipped on a rollback. They run only on lucent-axis/scope with the variable DEPLOYS_ENABLED, which is not set, and no GitHub environment or SUPABASE_DB_URL secret exists yet (read 28 Oct). So today every migration is still applied by hand: the Lucent Axis database was brought current that way on 28 Oct and its history matches the repository. Left: create the environments and their secrets, and set DEPLOYS_ENABLED; Novvia's clone gets the same workflows | ||
| SC-7 | No dependency or code scanning Fix: Dependabot alerts and updates, code scanning; critical fixes within 7 days 1. Source code and CI (GitHub) | App developer | Before Type I | Done | 28 Oct: Dependabot alerts on for lucent-axis/scope, .github/dependabot.yml on main, and its first update (PR #1, nine minor and patch bumps) merged through the required checks and brought into the personal repo; Semgrep CE runs as the code-scan CI job (45566fb), with its one finding silenced inline with the reason, not required because registry rules can change. The 7-day rule for criticals is in the information security policy (draft, ORG-26); the monthly review checks it is kept | ||
| VE-1 | One shared database serves every demo; no separate customer production or staging Fix: Separate Supabase projects for production, staging and demo, each with its own Vercel environment 2. Environments and hosting (Vercel) | Data / platform engineer | Before customer data | Partial | The code half is built; the accounts half is not. Decided and written (docs/internal/release-and-deploy-plan.md, 28 Oct): dev, demo, and per client a staging and a production, each its own Supabase project, the client's staging on synthetic data only. The deploy workflows (SC-6) already deploy by environment (dev, demo, <client>-staging, <client>-production), each holding its own database secret, and production only after the same release went to that client's staging, run by someone else. Not yet: in the Lucent Axis accounts there is still one Vercel project (scope-web) and one Supabase project behind it (re-read 28 Oct), so previews, dev and the demo share it, and no client project exists (ORG-11). The one shared database in CLAUDE.md is the personal demo's, outside the boundary | ||
| VE-2 | No secrets inventory; the service-role key must never reach the browser Fix: Service-role key server-side only, Vercel sensitive env vars per environment, a secrets inventory with a rotation schedule 2. Environments and hosting (Vercel) | App developer | Before customer data | Partial | No service-role key anywhere in apps/ or packages/: the app runs on the anon key and RLS. The secrets inventory, with a rotation rule per secret, is in docs/compliance/system-inventory.md (28 Oct). The deploy workflows keep each environment's database URL as that GitHub environment's own secret (SC-6). Left: the demo password is still a public env var on the demo (SC-1). The Vercel Supabase integration put SUPABASE_SERVICE_ROLE_KEY and SUPABASE_SECRET_KEY into scope-web's Preview and Development variables: server-side, not NEXT_PUBLIC_, unused by the app; removed with the DB-7 rotation | ||
| VE-3 | Preview deployments may reach production data and be publicly reachable Fix: Previews use staging only; Vercel Deployment Protection on previews 2. Environments and hosting (Vercel) | App developer | Before customer data | Partial | Boundary (28 Oct): the Lucent Axis Vercel team puts Vercel Authentication on every deployment except custom domains (read from the API), and a scope-web deployment URL redirects to Vercel's login (checked 28 Oct), so previews are not public. Left: previews still point at the same Supabase project as production (VE-1); the release plan puts them on scope-dev, sample data only, once that project exists. Note for Paul's switch: if the production address is behind the same login, a custom domain or a changed setting opens it | ||
| VE-4 | Vercel team access not controlled or reviewed Fix: Enforce MFA on the Vercel team, least-privilege roles, quarterly review Kyle decides · Owner switch: Vercel team roles, and the first quarterly review of its members 2. Environments and hosting (Vercel) | Compliance owner | Before Type I | Partial | After OW-2. MFA steps: docs/compliance/this-week.md; quarterly review template: docs/compliance/onboarding-offboarding.md. Owner: Dan. 28 Oct: MFA is enforced on the Lucent Axis Vercel team (set by Kyle 27 Oct). Roles and the first quarterly review remain; Dan is Member, Kyle the only Owner (OW-6, read 28 Oct) | ||
| ID-1 | Supabase organization access not locked down for our team Fix: Enforce MFA on the Supabase org, least-privilege member roles, quarterly review Kyle decides · Owner switch: Switch on "Require MFA to access organization" in Supabase 3. Identity and sign-in (Supabase Auth) | Compliance owner | Before customer data | Partial | After OW-3. MFA steps: docs/compliance/this-week.md; quarterly review template: docs/compliance/onboarding-offboarding.md. Owner: Dan. Read in the dashboard 28 Oct: both members of the Lucent Axis Supabase organization have MFA on, but "Require MFA to access organization" is off, and only the Owner (Kyle) can switch it on. Blocked: Kyle (Owner switch), or Dan once made Owner | ||
| ID-2 | No SSO for customer users Fix: OneLogin via SAML/OIDC, sign-in configured per tenant 3. Identity and sign-in (Supabase Auth) | App developer | Before customer data | Partial | 30 Oct: novvia's SSO Phases 0-3 are in this repository, SWITCHED OFF (features.sso in the tenant, app.feature_enabled('sso') in the database; no tenant or migration turns either on, so every demo signs in through the persona picker exactly as before). In place: SCIM as the only way a client account is created (/scim/v2), an identity keyed on the IdP's immutable id, and app.bind_login(), the database half of SAML sign-in that refuses anyone not provisioned, pending, suspended or deactivated (20261030120000-20261030150000, verify_identity.sql 0-28, verify_scim.mts, docs/sso-access-plan.md). Not built: Phase 5, the SAML handshake itself, on hold until Patrick decides the client platform (decision S14); then Phase 6, the OneLogin guide (docs/client/onelogin-setup.md, draft) and the end-to-end pass. SAML only: OIDC is out of scope (decision S2) | ||
| ID-3 | Employee number + PIN: sequential numbers let anyone guess or lock out a whole shift Fix: Rate limits per account, device and IP; alert on lockout spikes 3. Identity and sign-in (Supabase Auth) | App developer | Before customer data | Not started | The PIN sign-in is not built yet, and the code has no rate limit or lockout alert; build both with it | ||
| ID-4 | Sessions don't end with the shift; leavers stay signed in Fix: Driver sessions end at shift end; leaver sessions expire within 1 working day 3. Identity and sign-in (Supabase Auth) | App developer | Before customer data | Partial | 30 Oct: supabase/config.toml time-boxes every session at 12 hours for the local stack and CI; a client's hosted project is set the same in its dashboard (docs/client/provisioning.md), the demo's left as it is. Suspension or a SCIM deactivation removes the access rows in the same transaction, so RLS returns nothing to a session still open (verify_identity.sql 17, verify_scim.mts 8); ending that session at the identity provider waits for Phase 5. Both switched off until a client is provisioned (ID-2). Sessions are still supabase-js in localStorage (apps/web/lib/supabase.ts). Ending a driver's session at the shift's own end, rather than 12 hours on, needs the shift times | ||
| ID-7 | Password rules for non-SSO accounts not set Fix: Minimum length, leaked-password protection and MFA option in Supabase Auth Kyle decides · Owner switch: Supabase sign-in: passwords of 12 or more with letters and digits, leaked-password protection on 3. Identity and sign-in (Supabase Auth) | App developer | Before Type I | Partial | 28 Oct: supabase/config.toml now asks for 12 characters with letters and digits; 30 Oct: sign-up through the Auth API off and re-authentication before a password change (local stack and CI; the hosted settings are in docs/client/provisioning.md). The hosted project's settings and leaked-password protection are clicks for the company copy (docs/compliance/this-week.md). Owner: Dan, with OW-3. Read in the dashboard 28 Oct: the company project still allows 6-character passwords with no requirements and leaked-password protection off. Setting 12, letters and digits, and leaked-password protection was refused for Dan's Developer role ("You need additional permissions to update provider settings"); nothing was changed. Blocked: Kyle | ||
| DB-1 | RLS is the only boundary; the public API exposes every table in the exposed schema Fix: Move internal tables out of the exposed schema; review every policy; RLS check in CI (SC-4) 4. Database (Supabase Postgres) | Data / platform engineer | Before customer data | Partial | Every public table has RLS (scripts/db/verify.sql), internal helpers live in the unexposed app schema, and scripts/db/verify_raw_writes.sql proves a raw token cannot do what the RPCs refuse. Every table in public is still reachable through the API. 28 Oct: found that 56 of the 104 functions in public could be called with no sign-in, because Supabase's project default grants anon directly and the migrations' revoke ... from public never reached it; one, next_incident_number, told a stranger how many incidents a database holds on any date. 20261028120000_anon_cannot_execute.sql revokes them all, scripts/db/verify_anon.sql fails on any function anon can call and on any authenticated lost; proved together in a rolled-back transaction on the shared database. And 20261028130000_anon_no_table_writes.sql: anon held INSERT, UPDATE, DELETE and TRUNCATE on 33 tables and authenticated TRUNCATE on 53, none reachable (RLS refuses anon's writes; TRUNCATE cannot go through the API), all revoked except the hub's own four tables' writes, and the default closed for tables made later. Both are applied to the Lucent Axis database and verify_anon.sql passes there (28 Oct); the nightly job proves them on a fresh database each night. A stricter pass (keep a table grant only where a policy covers that role and command, with a drift check) was written in the set-aside lucent-axis/novvia repo (20261015110000_revoke_unused_grants.sql), to port. 29 Oct: 20261029140000_anon_no_table_reads.sql takes SELECT from anon on every table and view in public but the hub's four (36 had it; 32 had no policy naming anon, so a signed-out read already returned nothing, but the grant left their columns readable through REST and GraphQL), and closes the default for new tables; verify_anon.sql now fails on any anon read grant beyond the four, and proved failing before and passing after on a fresh local database. Left: signed in, every table in public is still reachable through the API behind RLS; moving tables behind an API tier is NP-1 | ||
| DB-2 | RLS can't hide columns: a driver can read an investigation's root cause Fix: Column grants or security_invoker views for investigation data; check every sensitive table 4. Database (Supabase Postgres) | Data / platform engineer | Before customer data | Not started | No column grants in any migration; a driver's token can read root_cause on any incident they were involved in (CLAUDE.md, next step 3). 28 Oct: column grants cannot do it, since every login is the one authenticated role; the plan (move the three columns to an incident_finding table with its own RLS, switch five readers, a check as a driver) is in docs/compliance/data-classification-and-retention.md. The company database exists now (OW-3), but the code on main also serves Paul's demo, whose frozen database depends on today's reads; so this waits for Paul's switch, or for the change to be made backward-compatible, or for Novvia's clone, which has no frozen demo behind it | ||
| DB-3 | No tenant_id: nothing separates one customer's rows from another's Fix: tenant_id NOT NULL on every table, in every policy and index; composite foreign keys; cross-tenant CI test 4. Database (Supabase Postgres) | Data / platform engineer | Before customer data | Not started | No tenant_id in any migration (re-checked 28 Oct). The release plan separates clients by giving each its own Supabase project (VE-1, NP-4), which separates their rows without it; this row stays open only if clients are ever to share a database | ||
| DB-4 | No audit trail: modified_by keeps only the last change, and who/when are not set by triggers Fix: Triggers fill who and when, with a named system actor; append-only history (trigger table or pgaudit) 4. Database (Supabase Postgres) | Data / platform engineer | Before customer data | Done | 20261015200000_audit_columns.sql gives every table created and modified at and by, set by the zz_audit_stamp trigger on the real clock, and it is applied to the Lucent Axis database (OW-3; verify_audit_columns.sql among the 23 that pass there). Append-only history exists for incidents (incident_event), training (training_event), investigation requests and the rule mapping. 29 Oct, for every table: 20261029120000_row_history.sql adds app.row_change, one row per UPDATE or DELETE of any row in public, app and sales (and per INSERT on the two access tables): when (real clock), who (the person, or system / anon named as such rather than left blank), the record by its business key, and each changed column as old and new, or the whole row on a delete. Append-only from the owner too, unreachable through the API, read by a VP through report_change_log(). A new table gets it through app.add_audit_columns(); scripts/db/verify_row_history.sql (12 checks, as real logins) fails on a table without it. Applied to the Lucent Axis database 29 Oct, where verify_row_history.sql passes 12 of 12 (evidence: docs/compliance/evidence/2026/2026-10-29-change-history-applied.md) | ||
| DB-5 | Sensitive fields not classified (CDL, DOT medical cards, audiograms, injury details) Fix: Classify data, mark Confidential columns, limit them to the roles that need them 4. Database (Supabase Postgres) | Data / platform engineer | Before Type I | Partial | 28 Oct: classes and every Confidential column named in docs/compliance/data-classification-and-retention.md (draft). Left: limiting those columns to the roles that need them (DB-2), which waits for the company database | ||
| DB-6 | No retention, soft delete, legal hold or disposal process Fix: deleted_at soft deletes, 5-year OSHA 300/301 retention, legal hold, secure deletion at contract end 4. Database (Supabase Postgres) | Data / platform engineer | Before Type I | Partial | 28 Oct: retention periods (OSHA, FMCSA), legal hold and disposal at contract end written in docs/compliance/data-classification-and-retention.md (draft). 29 Oct, in the schema: 20261029130000_legal_hold.sql. A VP places a hold on an incident, a person, a site or everything, by business key, with who asked and why; another VP lifts it, with a reason, once; a hold is never edited or deleted; while it is live the database refuses to delete what it covers (incident and person, whose children go only by cascade), from the owner too. report_retention() shows per class (OSHA injury records, FMCSA qualification files, DVIRs) how many records are past their legal minimum and how many of those are held. Nothing deletes on its own: disposal stays the client's written request. scripts/db/verify_legal_hold.sql (10 checks), all passing on the Lucent Axis database, where it was applied 29 Oct (evidence: docs/compliance/evidence/2026/2026-10-29-change-history-applied.md). Left: deleted_at soft deletes, which would change every reader, and a screen for holds (today a VP calls the functions) | ||
| DB-7 | Direct database connections not restricted Fix: Enforce SSL, Supabase network restrictions, a rotated database password held only in the vault Kyle decides · Owner switch: Enforce SSL and network restrictions on the Supabase project 4. Database (Supabase Postgres) | Data / platform engineer | Before Type I | Org task | Steps (SSL, network restrictions, password in the vault): docs/compliance/this-week.md. Owner: Dan, after OW-3 and the vault. 28 Oct: also waits on the vault (Bitwarden's free organization, which Dan can create now). The Vercel integration also copied the new database password and service-role key into scope-web's Preview and Development variables; they are removed when the password is rotated. Read in the dashboard 28 Oct: on the company project SSL is NOT enforced and the database accepts connections from any address; Dan's Developer role cannot change either (the switches are greyed out). Blocked: Kyle (Owner switch), or Dan once made Owner or Administrator. Before restricting addresses, keep the ones the release workflow will migrate from (GitHub Actions) in mind | ||
| ST-1 | Storage paths not prefixed by tenant; bucket policies not tied to tenant and role Fix: Tenant-prefixed paths; storage policies per tenant and role 5. File storage (Supabase Storage) | Data / platform engineer | Before customer data | Partial | Storage policies on all five buckets check the reader's role and the record a file belongs to; there is no tenant prefix because there is no tenant yet (DB-3) | ||
| ST-2 | Buckets not confirmed private; files may be reachable by URL Fix: Private buckets only, short-lived signed URLs 5. File storage (Supabase Storage) | App developer | Before customer data | Done | All five buckets are created public = false (incident-attachments, training-material, person-documents, training-evidence, site-documents), and files are served only through signed URLs that expire within an hour (packages/db/src/attachment.ts, site-documents.ts) | ||
| AV-1 | No restore has been proven; RPO and RTO not measured Fix: A timed full restore; measured RPO/RTO in the contract; restore tests on a schedule 6. Backups, availability and recovery | Data / platform engineer | Before customer data | Org task | Restore test record and targets: docs/compliance/bc-dr-plan.md. Owner: Dan; restore into a new project, never the live one | ||
| AV-2 | Point-in-time recovery not confirmed on the Supabase plan Fix: Confirm the plan, the PITR add-on and backup retention days Kyle decides · Paid service: Buy Supabase point-in-time recovery before client data, or write "up to 24 hours of data can be lost" into the plan? 6. Backups, availability and recovery | Data / platform engineer | Before customer data | Partial | A plan setting; blank in docs/compliance/system-inventory.md until Dan reads the dashboard. Owner: Dan. Read in the dashboard 28 Oct: the company project (Pro) takes a daily physical backup (latest 28 Oct 06:46 UTC); Point-in-Time Recovery is a paid add-on and is OFF. So today's RPO is up to 24 hours, not the 5 minutes in the BC/DR plan. Decision: buy PITR before client data, or write 24 hours into the plan for the demo (Free first) | ||
| AV-3 | No business continuity or disaster recovery plan Fix: Written BC/DR plan covering a Supabase or Vercel outage; yearly tabletop 6. Backups, availability and recovery | Compliance owner | Before Type I | Org task | Draft: docs/compliance/bc-dr-plan.md (targets, outage scenarios, tabletop). Owner: Kyle | ||
| AV-4 | No uptime monitoring, status page, support hours or SLA Fix: External uptime monitor, status page, support hours and after-hours response time in the contract Kyle decides · Outside service: Which uptime monitor and status page: UptimeRobot or Better Stack (free tiers), under a Lucent Axis account 6. Backups, availability and recovery | App developer | Before Type I | Partial | /api/health (28 Oct) answers 200 when the app and the database's auth service are up and 503 when the database cannot be reached, for an external monitor to call. Left: the monitor and status page themselves (UptimeRobot or Better Stack free tier, Free first), support hours and the SLA in the contract | ||
| AV-5 | No capacity monitoring Fix: Alerts on database disk, connections and CPU; keep reporting off production 6. Backups, availability and recovery | Data / platform engineer | Before Type I | Partial | No capacity alerts, and the reports and /cofi read the production database. 28 Oct, free route until alerts exist: a monthly look at Supabase's database usage (disk, CPU, connections) is in docs/compliance/control-calendar.md; Supabase also emails the owners when a project nears a plan limit. 29 Oct: scripts/compliance/capacity.sql reads the same from the database itself, per client project: disk against the plan's included size, connections against the limit, the cache hit rate, the ten largest tables with their dead rows, anything running over five minutes; each line OK or WATCH at 70%. Left: alerts, and CPU, which only the dashboard shows | ||
| LM-1 | Supabase and Vercel log retention depends on plan and may be only days Fix: Send app, auth and database logs to a store kept for 12 months Kyle decides · Paid service: Pay for log drains to keep logs 12 months, or accept what Pro keeps? 7. Logging, monitoring and incident response | Data / platform engineer | Before Type I | Org task | Both accounts are now on Pro (OW-2, OW-3), and both offer drains on Pro, so this is a spending decision, not a plan change: Vercel Pro keeps runtime logs 1 day (30 with Observability Plus) and drains are billed per GB; Supabase Pro keeps 7 days and a drain is a paid add-on per project (vendor pricing pages, read 28 Oct; confirm on the billing page the day it is bought). The database's own audit trail (audit columns, incident_event, training_event) is already kept with the backups. Owner: Dan | ||
| LM-2 | No alerts on security events Fix: Alerts on failed sign-ins, lockout spikes, admin and role changes, and service-key use 7. Logging, monitoring and incident response | App developer | Before Type I | Partial | 28 Oct, the free route: a monthly review in place of real-time alerts. scripts/compliance/app_access.sql lists every app login with its level and flags new ones and ones needing attention (its first run, on the personal demo database outside the boundary, found one login with no person); sign-in failures are read in Supabase's auth logs, which the database does not hold. Both in docs/compliance/control-calendar.md. app_access.sql counts the hub's shared sales login apart, so the first company run reads 0 unexplained logins. 29 Oct: the append-only log of admin and role changes exists. app.row_change (DB-4) logs every insert, update and delete on app.user_account and app.user_site, so a login granted, a level changed, a GM's site added or removed are each a row with who did it; app_access.sql now ends with every such change in the last 35 days, for the monthly review. 30 Oct: app.access_event is in this repository (ID-5), append-only from the owner too: on a client database it records every approval, level or site change, suspension, admin grant, vendor grant, SCIM authentication failure and rate-limit, refused sign-in and token change, read on /admin/users and exported with "Prepared by". Left: real-time alerts; lockout-spike alerts come with the PIN sign-in (ID-3) | ||
| LM-3 | No incident response plan or breach-notification process Fix: IR plan, on-call contact, customer notice within the contract window (24-72 hours), yearly tabletop 7. Logging, monitoring and incident response | Compliance owner | Before Type I | Org task | Draft: docs/compliance/incident-response-plan.md (roles, severity, steps, customer notice, tabletop). Owner: Kyle | ||
| LM-4 | No pen test or vulnerability scan of the app Fix: Third-party pen test before the Type I; findings tracked to closure 7. Logging, monitoring and incident response | Compliance owner | Before Type I | Org task | Pen test scope drafted in docs/compliance/vendors-and-counsel.md. Owner: Kyle; many compliance platforms resell one. 28 Oct, decided: for the Type I an automated scan (OWASP ZAP) plus the RLS proofs (scripts/db/verify_*.sql); a third-party pen test before the first enterprise client go-live | ||
| VN-1 | Vendor SOC 2 reports not collected or reviewed Fix: Collect Supabase, Vercel, GitHub and Google reports yearly; record the review in the platform Kyle decides · Paid service: How to get Supabase's SOC 2 report, which Pro does not include: ask under NDA, a month on Team each year, or Team throughout? 8. Vendors and subprocessors | Compliance owner | Before Type I | Org task | Which report from whom, and where: docs/compliance/subprocessors.md. Owner: Kyle. 28 Oct: where each report is got, recorded in docs/compliance/subprocessors.md. Supabase gives its SOC 2 report only on Team and Enterprise plans, so on Pro the free route cannot reach the one report the carve-out relies on; options there (ask under NDA, a month on Team each year, or Team throughout), decided with OW-3 | ||
| VN-2 | Complementary user entity controls (CUECs) not mapped Fix: Each vendor report lists controls we must run (MFA, RLS, key handling); map them to ours 8. Vendors and subprocessors | Compliance owner | Before Type I | Org task | Expected CUECs mapped to our controls in docs/compliance/subprocessors.md; replace with each report's wording. Owner: Kyle | ||
| VN-3 | No subprocessor list Fix: List Supabase (on AWS), Vercel, the email provider and any AI tooling that touches code or data 8. Vendors and subprocessors | Compliance owner | Before customer data | Org task | Draft: docs/compliance/subprocessors.md (Supabase on AWS, Vercel, AWS Bedrock in client builds, Anthropic in the demo only). Owner: Kyle | ||
| VN-4 | Data region not confirmed Fix: Confirm the Supabase project and Vercel functions run in the US 8. Vendors and subprocessors | Data / platform engineer | Before customer data | Done | Boundary (28 Oct): scope-web's Vercel functions run in iad1 (Washington, D.C.) and its Supabase project in us-east-1 (N. Virginia), both US, read from the Vercel API and the project's connection host on 28 Oct | ||
| VN-5 | No data processing agreement Fix: DPA from counsel covering subprocessors, breach notice and deletion Kyle decides · Outside service: The DPA, once counsel is named (ORG-36) 8. Vendors and subprocessors | Compliance owner | Before customer data | Org task | Brief for counsel drafted in docs/compliance/vendors-and-counsel.md. Owner: Kyle or Business lead to name counsel (ORG-36) | ||
| VE-5 | No confirmed security headers or abuse protection Fix: HSTS, CSP and frame-ancestors headers; Vercel firewall (WAF) rules Next phase: SOC 2 Type II and enterprise | App developer | Type II | Partial | apps/web/next.config.ts sets headers only for /sw.js; no HSTS, CSP or frame-ancestors. 28 Oct: HSTS, same-origin framing (X-Frame-Options and CSP frame-ancestors), nosniff, a referrer policy and a permissions policy on every response (apps/web/next.config.ts). Later on 28 Oct: a full Content-Security-Policy sent REPORT-ONLY (own origin, Supabase, Google Fonts for a tenant that names one, Mermaid by its exact CDN path for the hub, vercel.live for previews; object-src 'none', base-uri and form-action self). Walked on a local production build as the VP across 17 screens with the browser's violation reports read: one finding (the hub's Mermaid) fixed, then none. Left: switch it from report-only to enforced after a week on the deployed demo, Vercel firewall rules | ||
| ID-5 | No admin pages or SCIM: leavers are removed only by a script we run Fix: Customer admin console; SSO per tenant (OneLogin, Okta, Entra, Google) with SCIM Next phase: SOC 2 Type II and enterprise | App developer | Type II | Done | 30 Oct, ported from novvia and switched on per client (ID-2): SCIM 2.0 at /scim/v2 on its own narrow database role, the client admin console /admin/users (approve, levels and sites, suspend, admin as a separate permission, vendor access that lapses by itself, the append-only access log and its export), the HR roster import with access rules, and scripts/db/provision_client.sh. Tests: verify_identity.sql, verify_roster.sql (nightly), verify_scim.mts and rehearse_onboarding.mts (lab), four unit suites. Operating evidence begins when the first client's directory is connected; on the demo it stays off and users are linked by scripts/db/link_user.sh | ||
| ID-6 | Our team's standing access to client data is not logged or time-limited Fix: Log every support access; time-boxed elevation; disclose it in the contract Next phase: SOC 2 Type II and enterprise | Data / platform engineer | Type II | Partial | Not logged; the demo's "View as anyone" lets a demo login act as any person (20261023170000_demo_view_as.sql), which must stay out of client builds. 28 Oct: a support-access procedure and log (reason first, narrowest route, ends with the task, disclosed in the contract) in docs/compliance/information-security-policy.md, section 5a. View as anyone is now in the demo-only personas add-on (5b77b6d): a client database never has its functions (20261028150000 drops them where the add-on is not installed), and in a demo database it also refuses when the add-on is switched off. 30 Oct, ported from novvia (ID-5): time-boxed elevation is in the tooling for a client database. A client admin grants one of our logins access for at most 30 days with a reason (admin_grant_vendor_access), it lapses by itself (app.user_account.valid_until), and the grant, its use and its end are on the append-only access log (verify_identity.sql 21-22). Off until a client is provisioned (ID-2). Left: our own standing access to the database itself (the owner connection) is not time-boxed or logged by the app | ||
| ST-3 | Uploads not checked Fix: Allowed file types and size limits; strip location data from photos Next phase: SOC 2 Type II and enterprise | App developer | Type II | Done | Every bucket has a 25 MB limit and an allowed file-type list. 28 Oct: GPS coordinates are removed from JPEG photos before every upload of an incident photo, a DQ document, an outside-training certificate or a sign-in sheet, losslessly and keeping Orientation (packages/core/src/photo-location.ts, packages/db/src/photo-location.ts, test photo-location.test.mts). HEIC, PNG and WebP pass through; a phone browser hands over JPEG | ||
| LM-5 | Logs and error reports may capture personal data Fix: Scrub CDL, medical and injury details from logs and error tracking Next phase: SOC 2 Type II and enterprise | App developer | Type II | Partial | Reviewed 28 Oct: the app has eight console calls, all error messages or status lines, none writing a record's content; no error-tracking service is installed. The rule for it is in docs/compliance/data-classification-and-retention.md (Confidential data never in logs). Left: scrubbing in the error tracker the day one is added | ||
| NP-1 | Supabase's API layer (PostgREST) checks only the sign-in token: it can reach every table in `public`, leaves every permission to RLS, and has no per-user rate limit on data Fix: Narrow what the API can reach and check every request inside Supabase: an `api` schema holding only the functions and views the screens use, the tables in a schema the API does not expose (closes DB-1), and a pre-request check (rate limit per user, refuse a disabled login); RLS stays as the wall behind it. A separate API server only if a client integration needs one Next phase: SOC 2 Type II and enterprise | App developer | Type II | Not started | 29 Oct, reworded: SCOPE is not without an API tier. Every screen goes through PostgREST, which checks the token and runs the request as authenticated, so the database's own rules decide everything. SOC 2 asks for that wall to be proven, not doubled, and it is: verify_rls.sql, verify_raw_writes.sql and verify_anon.sql run against a fresh database every night. Writes already go through SECURITY DEFINER functions, so the api schema is mostly about reads. The pre-request check is a PostgREST setting (db-pre-request); that it can be set on Supabase's hosted project is to confirm before relying on it. A separate API server (Next.js route handlers or Edge Functions) would mean rewriting every read and the offline outbox, so it is kept for a client integration such as SCIM or an export API | ||
| NP-2 | Customers can't see who changed their records Fix: Customer-facing audit-log export, built on the audit trail Next phase: SOC 2 Type II and enterprise | Data / platform engineer | Type II | Done | An incident's own audit trail can be exported from the incident record. 29 Oct, customer-wide: every change is kept (app.row_change, DB-4), and the VP's Reports screen has a Change log (/reports?type=change-log): when, the record by its business key, what it is, Updated / Deleted / Granted, by whom, and the fields before and after, filtered by period and by kind (login access among them), with the same Download CSV as every report. No uuid is shown; on a database without the migration the report says it is not set up yet. Checked in the browser as the VP on a fresh local database | ||
| NP-3 | Reporting would run against production Fix: Read replica; COFI change feed that includes deletes Next phase: SOC 2 Type II and enterprise | Data / platform engineer | Type II | Not started | /cofi and the reports read the production database | ||
| NP-4 | Every client shares one production project Fix: Dedicated tenant projects as a premium option; scripted tenant setup Next phase: SOC 2 Type II and enterprise | Data / platform engineer | Type II | Partial | The model is one project per client, not one shared project (docs/internal/release-and-deploy-plan.md, VE-1), so the premium option is the default, and the deploy workflows already deploy by client (<client>-staging, <client>-production). Not scripted: creating the Supabase and Vercel projects and setting up a client database (a setup script was written in the set-aside lucent-axis/novvia repo, to port) | ||
| NP-5 | Logs and alerts spread across several tools Fix: Central logging (SIEM) with one alert view, kept 12+ months Next phase: SOC 2 Type II and enterprise | Data / platform engineer | Type II | Not started | No central logging | ||
| NP-6 | No contractual uptime commitment Fix: Uptime SLA, on-call rota, yearly DR drill Next phase: SOC 2 Type II and enterprise | Compliance owner | Type II | Org task | Rota and drill in docs/compliance/bc-dr-plan.md; the SLA is a contract term in the counsel brief (docs/compliance/vendors-and-counsel.md). Owner: Kyle, Dan | ||
| NP-7 | Buyers ask for security evidence one request at a time Fix: Trust page with the Type II report and pen-test letter; ISO 27001 only if a buyer requires it Next phase: SOC 2 Type II and enterprise | Compliance owner | Type II | Org task | Once the Type II report and pen-test letter exist; most platforms host one (docs/compliance/vendors-and-counsel.md). Owner: Kyle | ||
| TS-1 | No code of conduct and no named security roles Fix: A code of conduct and the roles (security, platform and app owner) in the information security policy, signed on joining and yearly Kyle decides · Sign-off: Approve the code of conduct and the named security roles Found against the criteria | Compliance owner | Before Type I | Partial | Drafted 28 Oct: docs/compliance/information-security-policy.md, sections 2 and 3. Left: Kyle's approval and everyone's signature (ORG-14) | ||
| TS-2 | Nothing shows management overseeing security Fix: A quarterly security review by both owners, with minutes: missed controls and their fixes, open risks, incidents, system changes Kyle decides · Sign-off: Hold the first quarterly security review with Dan Found against the criteria | Compliance owner | Before Type I | Partial | In docs/compliance/control-calendar.md. Left: the first meeting, when Kyle is back | ||
| TS-3 | No published way to report a vulnerability or reach us about security Fix: A security contact address and `/.well-known/security.txt`; commitments in the contract and DPA Found against the criteria | Compliance owner | Before customer data | Not started | Needs a company security address, which needs a company domain or mailbox (OW-4). Owner: Kyle | ||
| TS-4 | No calendar of recurring controls and no evidence log; without a platform nothing shows a control ran Fix: A control calendar (monthly, quarterly, yearly, on an event) and dated evidence files Kyle decides · Sign-off: Sign October's monthly review Found against the criteria | Compliance owner | Before Type I | Partial | docs/compliance/control-calendar.md and docs/compliance/evidence/ (28 Oct); first entries 27 and 28 Oct. First monthly review prepared 28 Oct (evidence/2026/2026-10-28-monthly-review-october.md: PRs #1 to #8, 0 open alerts), awaiting Kyle's sign-off | ||
| TS-5 | The risk register had no fraud or insider risks and no trigger for a new review Fix: Fraud and insider risks in the register; reviewed again on a new client, subprocessor or add-on Kyle decides · Sign-off: Review the risk register Found against the criteria | Compliance owner | Before Type I | Partial | R13 to R15 and the triggers added 28 Oct (docs/compliance/risk-register.md). Left: Kyle's review | ||
| TS-6 | No rule for disposing of data or cleaning a leaver's laptop Fix: Disposal at contract end, laptops cleaned at leaving, exports deleted Found against the criteria | Compliance owner | Before Type I | Partial | Drafted 28 Oct: docs/compliance/data-classification-and-retention.md; the leaving checklist already asks for the written confirmation | ||
| TS-7 | Malware protection not stated Fix: Laptop OS protection on (BYOD policy); uploads typed, size-limited, never executed, location stripped Found against the criteria | App developer | Before Type I | Partial | information-security-policy.md section 8, byod-policy.md rule 5, ST-3 done 28 Oct. Left: the policies signed | ||
| TS-8 | Physical security not addressed Fix: Name Supabase (on AWS) and Vercel as carved-out subservice organisations in the system description; read their SOC 2 reports yearly Found against the criteria | Compliance owner | Before Type I | Partial | Stated in docs/compliance/system-inventory.md and the criteria matrix (28 Oct). Left: the reports read (VN-1) | ||
| TS-9 | Changes made in a dashboard leave no record Fix: A dashboard change log, filled the same day Found against the criteria | Data / platform engineer | Before Type I | Partial | The table in docs/compliance/change-management-policy.md (28 Oct), started with Kyle's Step 0 changes and the Dependabot switch | ||
| TS-10 | No way to raise a concern that goes around the person it is about Fix: A named route in the information security policy: concerns go to Kyle, or to the Business lead when they concern Kyle; no retaliation; each one logged Found against the criteria | Compliance owner | Before Type I | Not started | Point of focus "Provides Separate Communication Lines" (29 Oct) | ||
| TS-11 | Segregation of duties is not stated: one developer writes, merges, migrates and deploys Fix: Say so in the change-management policy and the risk register, with the compensating controls: required CI checks, Kyle's monthly review of merged PRs and the dashboard log, and a second person on any High-risk change once there is one Kyle decides · Scope: Accept one developer writing, merging and deploying, with Kyle's monthly review of merged PRs as the compensating control? Found against the criteria | Data / platform engineer | Before Type I | Partial | Point of focus "Addresses Segregation of Duties". 0 approvals on a PR is a decision (Dan, 28 Oct), so the auditor will look for this paragraph. 2 Nov: written in docs/compliance/change-management-policy.md (Segregation of duties): each thing one person can do alone, the control that catches it and where it shows; nobody reviews their own work (Dan reviews the dashboard changes Kyle makes); one approval required once a second developer joins. R16 added to the risk register, accepted while there is one developer. Left: Kyle's decision (Kyle decides, above), which is also his acceptance of R16 and approval of the policy (docs/compliance/approvals.md); then Done | ||
| TS-12 | The access review lists people, not machine credentials Fix: Add the non-human accounts to the quarterly access review: GitHub Actions secrets, GitHub Apps and Dependabot, Vercel and Supabase tokens and keys, the AWS role behind Bedrock; each with an owner, its scope and when it was last rotated. Starts from VE-2's secrets inventory Found against the criteria | Data / platform engineer | Before Type I | Done | The 2022 points of focus name system, service and vendor accounts as well as people (29 Oct). 29 Oct: scripts/compliance/access_review.sh now lists every machine credential it can read, by name and date, never a value: GitHub repo, environment and Dependabot secrets and deploy keys (none on either repo), org secrets and installed GitHub Apps (owners only, so the script says so rather than reading as none), every Vercel project's variables with who added them (27 on scope-web, 23 of them from the Supabase integration), the team's integrations and the reviewer's own tokens; Supabase keys and AWS are read where they live. The review table and its rules (an owner by name, narrowest scope, rotated yearly and on any departure, personal tokens expire, unused means removed) are in docs/compliance/onboarding-offboarding.md. Its first run found a personal Vercel token that never expires; the first full pass is the next quarterly review (ORG-20) | ||
| TS-13 | No written baseline for the security settings of GitHub, Vercel and Supabase, so drift cannot be seen Fix: One baseline file: the settings each account must have (the main ruleset, MFA, SSL, network restrictions, password rules, preview protection, log retention), compared against the dashboards in the monthly review Kyle decides · Owner switch: Approve the security-settings baseline, and switch off "members can create public repositories" on the GitHub org (GH-6); switch on the Vercel team's sensitive-variable policy (VC-6, VE-2) Found against the criteria | Data / platform engineer | Before Type I | Partial | GitHub's ruleset is already a file. Points of focus "Uses Defined Configuration Standards" and "Creates Baseline Configuration of IT Technology" (29 Oct). 29 Oct: the baseline is written, docs/compliance/security-baseline.md (25 settings, each with why and who can change it), and scripts/compliance/baseline_check.py reads 19 of them back from GitHub, Vercel and Supabase; the other six are read by hand. A monthly control in docs/compliance/control-calendar.md; first run saved, docs/compliance/evidence/2026/2026-10-29-security-baseline.md: 8 differ, 7 of them already open items. Left: Kyle's approval of the baseline, and GH-6 (members can create public repos) switched off, an Org Owner's switch | ||
| TS-14 | Every backup lives inside the same Supabase project Fix: A scheduled, encrypted copy of the database and the Storage buckets to a place Supabase cannot reach (a Lucent Axis AWS bucket with object lock), restore-tested with ORG-23. A deleted project, a taken-over Supabase account or ransomware would otherwise take the backups with it Kyle decides · Paid service: Where an off-site copy of the database lives, outside the Supabase project (for example a bucket in a Lucent Axis AWS account) Found against the criteria | Data / platform engineer | Before customer data | Not started | Point of focus "Addresses Offsite Storage", and the 2022 addition on threats to data recoverability (29 Oct). Independent of the PITR decision (AV-2), which shortens the gap but keeps the copy in the same place | ||
| TS-15 | No exit plan for a critical vendor Fix: One paragraph per critical vendor (Supabase, Vercel, GitHub, AWS) in the subprocessor list: how the data comes out, what would replace it, how long it would take Found against the criteria | Compliance owner | Type II | Not started | Point of focus "Implements Procedures for Terminating Vendor and Business Partner Relationships" (29 Oct) | ||
| TS-16 | The PR template does not ask how a change is undone, or whether it could take the app down Fix: Two lines in the template's Risk section: the rollback, and the availability impact (a migration that locks `inspection` or `route_day`, a change to the offline outbox) Found against the criteria | App developer | Before Type I | Done | 2022 point of focus on resilience requirements in change management, since Availability is in scope (29 Oct). 29 Oct: two lines in the Risk section of .github/pull_request_template.md, how the change is undone and what it could take down |
To do outside the code
The roadmap's month-by-month tasks and open questions: accounts, settings, documents, contracts and exercises. To be worked through in their own session.
| Done | ID | Task | Evidence | |||
|---|---|---|---|---|---|---|
| ORG-01 | Move SCOPE onto Lucent Axis-owned GitHub, Vercel, Supabase and other accounts before any other change (Step 0) | Compliance owner | Now | Partial | Boundary (28 Oct): only Lucent Axis accounts are audited. Done: GitHub repo (OW-1), Vercel team (OW-2), no personal domain (OW-4). Done 28 Oct: the Supabase project confirmed in the Lucent Axis organization and brought current (OW-3), the inventory (OW-7). Left: two owners and a vault (OW-6), Bedrock's AWS account (OW-5), the vault half of OW-8. Owner: Kyle | |
| ORG-02 | Rotate the Supabase anon and service-role keys, purge the demo password from git history, make the repo private | App developer | Now | Partial | Boundary (28 Oct): lucent-axis/scope is private, gitleaks finds nothing in its history, and scope-web's keys were created by its own Supabase project, never under a personal account (OW-8). Left: the demo password in the bundle (SC-1) and the database password into the vault (DB-7). The personal demo's keys are outside the boundary. Owner: Dan | |
| ORG-03 | Turn on secret scanning and push protection in the GitHub org | Compliance owner | Now | Done | Decided (Dan, 28 Oct): the free route. gitleaks scans the whole history in the required secrets CI job on every PR, and before every commit on each laptop through the pre-commit hooks, which are on main and installed by default since 28 Oct (349251f). The paid GitHub Secret Protection is the upgrade if a second committer joins or the platform bundles it. Weakness and reasoning: docs/compliance/README.md, Free first. Owner: Dan | |
| ORG-04 | Get quotes from 2-3 compliance platforms (Vanta, Drata, Secureframe or Sprinto) and pick one, with its partner auditor Kyle decides · Paid service: Which compliance platform (Vanta, Drata, Secureframe), or a Type I run ourselves with a CPA firm hired directly? | Compliance owner | Month 1 | Not started | Quote request and comparison table: docs/compliance/vendors-and-counsel.md. Owner: Kyle and Dan. Free first (docs/compliance/README.md) records the fallback: a Type I run by ourselves with a CPA firm hired directly is defensible, a Type II without a platform likely is not, so the platform is signed before the Type II window (ORG-30). No quote request has been sent | |
| ORG-05 | Connect GitHub, Supabase, Vercel and Google Workspace to the platform | Compliance owner | Month 1 | Not started | After ORG-04 and Step 0: connect company accounts only (docs/compliance/vendors-and-counsel.md). Owner: Dan | |
| ORG-06 | Enforce MFA on every account that touches SCOPE: Google, GitHub, Supabase, Vercel, domain registrar Kyle decides · Owner switch: Require two-factor sign-in on the GitHub org and 2-step verification in Google Workspace | Compliance owner | Month 1 | Partial | Per-account settings in docs/compliance/this-week.md. Owners: Kyle (GitHub org), Dan (Vercel, Supabase), Workspace admin (Google). 28 Oct: Vercel team done (enforced 27 Oct by Kyle). GitHub org: Kyle (the setting is visible to owners only). Supabase org: Dan. Google Workspace: Kyle or the Workspace admin. Read in the dashboard 28 Oct: both members of the Lucent Axis Supabase organization have MFA on; enforcement is the Owner's switch (ID-1) | |
| ORG-07 | Protect main: pull request required, no direct pushes, status checks must pass | Compliance owner | Month 1 | Done | Applied 28 Oct (ruleset id 26059181, SC-5), proven by PRs #2 and #3 and a refused direct push. Shipping is scripts/git/ship.sh: the personal repo as before, origin HEAD:personal-main, a standing PR merged when the checks pass; build on personal/main. The monthly review of merged PRs is in the control calendar. Owner: Dan. 0 approvals by decision (Dan, 28 Oct); the compensating control is Kyle's monthly review of merged PRs | |
| ORG-08 | Sign contractor agreements with confidentiality and security clauses for every contractor | Compliance owner, Business lead | Month 1 | Not started | Clauses for counsel listed in docs/compliance/vendors-and-counsel.md. Owner: Kyle or Business lead | |
| ORG-09 | Adopt and edit policy templates down to what the team will actually do Kyle decides · Sign-off: Approve the policy set, removing anything we will not actually do | Compliance owner | Month 1 | Partial | Draft: docs/compliance/policies.md (the policy set, what each must say, which are drafted). Owner: Kyle. 28 Oct: written ourselves under Free first: information-security-policy.md (with acceptable use, access, encryption, secure development, malware), change-management-policy.md, data-classification-and-retention.md, beside the BYOD, incident response and BC/DR drafts. Awaiting Kyle's approval. 28 Oct: every draft carries a sign-off line, and the approvals are recorded in one place, docs/compliance/approvals.md | |
| ORG-10 | Write the system description: what SCOPE is, its boundary, data flows, subprocessors Kyle decides · Sign-off: Review the system description | Compliance owner, Data / platform engineer | Month 1 | Partial | Starts from docs/compliance/system-inventory.md (boundary, services, data flows) and docs/compliance/subprocessors.md. Owner: Kyle, with Dan. 28 Oct: drafted in the order of the description criteria (DC 200), with commitments, components, boundaries, carve-outs, CSOCs and CUECs: docs/compliance/system-description.md; the Step 0 facts (the Lucent Axis GitHub org, Vercel team and Supabase organization, plans, region, members) are in, read 28 Oct. Left: Kyle's review, and the client sign-in added to the components once it is built (ID-2) | |
| ORG-11 | Stand up client-one production and staging, separate from demo, and load realistic synthetic data Kyle decides · Paid service: When to create each client's own staging and production projects (each a paid Supabase and Vercel project) | Data / platform engineer | Month 1 | Partial | Boundary (28 Oct): no client production or staging project exists in the Lucent Axis accounts yet (VE-1); each is a paid project, so creating them waits for the plan decision with AV-2. What is ready for the day they are made: the environments are decided (docs/internal/release-and-deploy-plan.md: per client a staging project on synthetic data and a production project); the deploy workflows deploy to <client>-staging then <client>-production (SC-6). 30 Oct: the client database setup script (scripts/db/provision_client.sh: refuses a demo, imports the HR roster, switches SSO on), its rehearsal on a synthetic 300-person roster (scripts/db/rehearse_onboarding.mts, passed in this repository) and the hosted settings runbook (docs/client/provisioning.md) are ported from lucent-axis/novvia; the data request for Novvia stayed there. A new client database also needs the three reference catalogs, which scope loads with the sample data (docs/decisions.md, 2026-10-30). Owner: Dan | |
| ORG-12 | Confirm in writing with the auditor that the synthetic-data period counts toward the window | Compliance owner | Month 1 | Not started | Wording for the auditor in docs/compliance/vendors-and-counsel.md. Owner: Kyle, once the auditor is chosen | |
| ORG-13 | Background checks for everyone with production access Kyle decides · Paid service: Which background-check service: pay per check (Checkr or Certn), or the platform's partner? | Compliance owner | Month 2 | Not started | In the joining checklist, docs/compliance/onboarding-offboarding.md; run through a pay-per-check service (Checkr or Certn), the platform's partner as the alternative (Free first, 28 Oct). Owner: Kyle | |
| ORG-14 | Security awareness training and policy acceptance for all staff and contractors Kyle decides · Sign-off: Everyone reads and signs the policies, once they are approved | Everyone | Month 2 | Partial | In the joining checklist (docs/compliance/onboarding-offboarding.md) and the policy set (docs/compliance/policies.md); delivered as our own training (docs/compliance/security-training.md), the platform's course as the alternative. Owner: everyone, tracked by Kyle. 28 Oct, free first: our own training in docs/compliance/security-training.md (accounts, phishing, client data, laptops, reporting) with a signature record. Left: everyone reads and signs it with the policies. 28 Oct: the signature record for everyone is section 2 of docs/compliance/approvals.md | |
| ORG-15 | Install the platform's device agent on every personal laptop; confirm disk encryption, screen lock and OS updates | Everyone | Month 2 | Not started | Rules the agent checks: docs/compliance/byod-policy.md. Owner: everyone, after ORG-04. 28 Oct, Free first: a quarterly device check at the access review instead, Google Workspace endpoint verification where our edition reports it, else a dated screenshot per laptop (disk encryption, screen lock, OS version), in byod-policy.md and the control calendar; the platform's agent is the upgrade if a platform is chosen | |
| ORG-16 | Write the BYOD policy, since all work happens on personal laptops Kyle decides · Sign-off: Approve the BYOD and laptop policy | Compliance owner | Month 2 | Partial | Drafted 27 Oct: docs/compliance/byod-policy.md (disk encryption, screen lock, updates, no client data on the laptop, what happens on leaving). Left: Kyle's approval and everyone's signature with the other policies (ORG-09, ORG-14). Owner: Kyle | |
| ORG-17 | First risk assessment and risk register | Compliance owner, Data / platform engineer | Month 2 | Partial | Draft: docs/compliance/risk-register.md (sixteen risks, each tied to readiness items). Owner: Kyle, Dan. 28 Oct: fraud and insider risks (R13, R14) and the free-tools risk (R15) added; review triggers written in. 2 Nov: R16, no segregation of duties (TS-11) | |
| ORG-18 | Vendor reviews: collect SOC 2 reports from Supabase, Vercel, GitHub, Google | Compliance owner | Month 2 | Not started | Which report from whom, and where: docs/compliance/subprocessors.md. Owner: Kyle | |
| ORG-19 | Onboarding and offboarding checklists, with access removed within 24 hours of departure Kyle decides · Sign-off: Approve the joining and leaving checklists | Compliance owner | Month 2 | Partial | Drafted 27 Oct: docs/compliance/onboarding-offboarding.md (joining before any access, leaving within 24 hours, each a checklist kept per person as the evidence). Left: Kyle's approval, and the first real use of each. Owner: Kyle | |
| ORG-20 | First quarterly access review; limit business users to app-level admin, no direct database access | Compliance owner | Month 2 | Partial | Template in docs/compliance/onboarding-offboarding.md. Owner: Kyle, Dan. 28 Oct: scripts/compliance/access_review.sh produces the member lists; the first saved to docs/compliance/evidence/2026/2026-10-28-access-review.md. The first full review is the two owners filling the table. Re-run 28 Oct against the Lucent Axis accounts only, with the Supabase members read in its dashboard and the company database's sign-ins; ready for the two owners to fill the table and sign. 30 Oct: a client's business users now have an app-level admin and nothing below it: /admin/users (ID-5) approves, changes and removes access, and its access log export is the client's own review evidence; switched on per client | |
| ORG-21 | Logging and alerting: Supabase and Vercel logs retained, alerts on errors, auth failures and admin actions Kyle decides · Paid service: The same log-drain decision (LM-1) | Data / platform engineer, App developer | Month 3 | Partial | LM-1, LM-2. Running today on the free route: the monthly review of every app login (scripts/compliance/app_access.sql) and of Supabase's auth logs, in docs/compliance/control-calendar.md. 29 Oct: admin actions are logged in the database, every change to every row and every access change (app.row_change, DB-4, LM-2). Left: logs kept 12 months (a drain on each Pro plan, LM-1) and real-time alerts. Owner: Dan | |
| ORG-22 | Uptime monitoring and a public status page Kyle decides · Outside service: The same monitor and status page (AV-4) | App developer | Month 3 | Partial | AV-4. The endpoint for a monitor to call is built (/api/health on lucent-axis/scope, 28 Oct). Left: the monitor and status page (UptimeRobot or Better Stack free tier). Owner: Dan | |
| ORG-23 | Backup and restore test with documented RPO/RTO; Supabase point-in-time recovery on Kyle decides · Paid service: The same PITR decision (AV-2); the first timed restore follows it | Data / platform engineer | Month 3 | Partial | AV-1, AV-2. Read 28 Oct: the company project takes a daily backup (Pro), PITR is off, so RPO is up to 24 hours today. The restore test record and targets are written (docs/compliance/bc-dr-plan.md). Left: the PITR decision, and the first timed restore into a new project. Owner: Dan | |
| ORG-24 | Disaster recovery and business continuity plan, and a tabletop exercise | Compliance owner, Data / platform engineer | Month 3 | Partial | AV-3. Draft: docs/compliance/bc-dr-plan.md. Owner: Kyle, Dan. 28 Oct: a ready-to-run DR tabletop script with a record form, docs/compliance/tabletop-exercises.md. Left: running it | |
| ORG-25 | Incident response plan and a tabletop exercise | Compliance owner | Month 3 | Partial | LM-3. Draft: docs/compliance/incident-response-plan.md. Owner: Kyle. 28 Oct: a ready-to-run IR tabletop script with a record form, docs/compliance/tabletop-exercises.md. Left: running it | |
| ORG-26 | Dependency scanning (Dependabot) with a patch SLA, critical fixes within 7 days | App developer | Month 3 | Done | 28 Oct: alerts on, dependabot.yml on main, the first Dependabot update merged through the required checks (PR #1), and the rule (criticals in 7 days, highs in 30, or a dated entry in the risk register) written into information-security-policy.md section 7, awaiting Kyle's approval with the rest of the policies (ORG-09). The monthly review in control-calendar.md is where the rule is shown to be kept. Owner: Dan | |
| ORG-27 | Third-party pen test of the SCOPE app; fix highs and criticals, keep the summary letter Kyle decides · Paid service: Which pen-test firm, and its date | Compliance owner | Month 3 | Not started | LM-4. Scope: docs/compliance/vendors-and-counsel.md. Owner: Kyle | |
| ORG-28 | Readiness check inside the compliance platform; close failing tests | Compliance owner | Month 4 | Not started | After everything above, in the chosen platform. Owner: Kyle | |
| ORG-29 | Type I audit fieldwork with the auditor | Compliance owner | Month 4 | Not started | Booked once ORG-04 is signed (docs/compliance/vendors-and-counsel.md). Owner: Kyle. 28 Oct: the auditor is the platform's partner, or a CPA firm hired directly (Free first) | |
| ORG-30 | Type II observation window starts the day after the Type I date, 3-6 months | Compliance owner | Month 4 | Not started | Calendar date set once the Type I date is known (docs/compliance/vendors-and-counsel.md). Owner: Kyle | |
| ORG-31 | Does Lucent Axis already carry cyber insurance, and at what cover? | Compliance owner | Open question | Not started | Question for the broker in docs/compliance/vendors-and-counsel.md. Owner: Business lead | |
| ORG-32 | Which Supabase and Vercel plans and regions is SCOPE on, and do they include point-in-time recovery, log retention and SAML SSO? | Data / platform engineer | Open question | Partial | Read 28 Oct. Vercel (API): the Lucent Axis team is on Pro (trial), scope-web's functions in iad1 (Washington, D.C.). Supabase (dashboard): Lucent Axis organization on Pro, project on Nano compute in us-east-1, daily backups, PITR off (AV-2). From the vendors' pricing pages the same day, to confirm on each billing page: Vercel Pro keeps runtime logs 1 day (30 with Observability Plus) and offers drains; SAML SSO for the Vercel team is an Enterprise feature; Supabase Pro keeps logs 7 days, offers a drain as a paid add-on, and SSO for its dashboard is on Team. SAML for our own team is not required (MFA plus the access review, Free first). Left: those three facts confirmed in the dashboards. Owner: Dan | |
| ORG-33 | Will the Novvia pilot run on the shared Supabase project or its own, given tenant_id is not built? | Data / platform engineer | Open question | Done | Decided (Dan, 28 Oct): Novvia gets its own Supabase projects (staging and production) in the Lucent Axis organization and its own Vercel project, set up by cloning lucent-axis/scope (docs/internal/release-and-deploy-plan.md), never the shared project. So the pilot does not wait for tenant_id (DB-3). Owner: Dan, Kyle | |
| ORG-34 | Does Novvia accept a Type I report plus a bridge letter for go-live, with Type II to follow? Kyle decides · Scope: Ask Novvia whether a Type I report plus a bridge letter is enough for go-live | Compliance owner | Open question | Not started | Owner: Kyle, to ask Novvia. 28 Oct: the question is written, ready to send, in docs/compliance/vendors-and-counsel.md (Novvia: is a Type I enough to go live?); not sent | |
| ORG-35 | Should COFI join the audit boundary at Type II, or stay out until a COFI client asks? Kyle decides · Scope: COFI in the audit boundary at Type II, or out until a COFI client asks? | Compliance owner | Open question | Not started | Default in docs/compliance/system-inventory.md: out, until a COFI client asks. Owner: Dan, Kyle. 28 Oct: what changes if it comes in is written in docs/compliance/vendors-and-counsel.md (COFI in the boundary); the decision is on the sign-off pack, docs/compliance/approvals.md | |
| ORG-36 | Who outside the team (counsel) drafts the DPA and the contractor security clauses? Kyle decides · Outside service: Who counsel is, to draft the DPA, the contractor clauses and the availability terms | Compliance owner | Open question | Not started | Brief for counsel (DPA, contractor clauses, availability terms) in docs/compliance/vendors-and-counsel.md. Owner: Kyle or Business lead to name counsel |