Skip to content
Last updated: Sep 25, 2026

Control Register ​

OwnerClassificationVersionEffectiveNext reviewStatus
Sekhar PrakashInternal0.12026-08-272026-11-27Draft

Purpose. The canonical control set for the security programme.

  • Each security domain is decomposed into discrete, individually testable controls; compliance frameworks are mapped onto these controls as lenses.
  • Authoritative source for what controls exist, who owns them, how they are tested, and their current state.
  • A control in a failed state corresponds to an entry in the Findings Register.
  • Excluded controls carry a rationale in the Statement of Applicability.

Basis. The controls consolidated here derive from security assessments conducted continuously since January 2026 (see Programme History); this register formalises that prior work into a single versioned reference.

SCAFFOLD — HAND-MAINTAINED

This register is intended to be generated from controls.yml once the data loader exists (see _MIGRATION-PLAN.md step 2). Neither the file nor the loader exists yet, so this page is hand-maintained — edit it directly, and keep the schema below as the target shape. The earlier "do not edit directly" notice described the planned end state as though it were current. The domains below are the seeded grain sample (6–10 controls per domain, ~150–200 total).

Control record schema ​

yaml
- id: CTL-D7-01
  domain: 7
  statement: "..."             # the condition the control asserts
  type: preventive             # preventive | detective | corrective
  owner: backend-platform
  maps_to:
    soc2: [CC6.1]  soc1: []  iso: [A.8.4]  gdpr: []  dpdp: []
    iso42001: []  euaiact: []  pci: []  hipaa: []  cis: [3,4]  owasp: [A01]
  test: "how design and operation are verified"
  cadence: continuous
  evidence: "artifact and location (link)"
  status: fail                 # pass | partial | fail | planned | risk-accepted | n-a
  applicability: applicable
  finding: F-01

Status legend (status column only):

SymbolMeaning
🟢pass
🟠partial
🔴fail
⚪planned / n-a
✅resolved

Register — Domain 4: Identity & Access Management ​

IDControl statementMaps toStatusFinding
D4-01Server-side RBAC, deny-by-default on sensitive routesCC6.1-3 · A.8.5 · A01✅F-02
D4-02Multi-factor authentication required (mandatory for admin/approver)CC6.1 · A.5.17 · A07🔴F-23
D4-03Enterprise SSO / SAMLCC6.1 · A.5.16⚪—
D4-04Session tokens httpOnly + Secure + SameSite with refresh rotationCC6.1 · A.8.5 · A07🔴F-07
D4-05No authentication token transported via query parameterCC6.6 · A09🔴F-28
D4-06Passwords hashed with a work-factor KDF (argon2/bcrypt/scrypt)CC6.1 · A.8.5🔴F-40
D4-07Brute-force / credential-stuffing protection on authenticationCC6.6🔴F-41
D4-08Least-privilege machine identitiesCC6.1 · A.8.2🟠F-12
D4-09Periodic access reviews and joiner/mover/leaver processCC6.2-3 · A.5.18🔴—
D4-10No secrets in source or version-control history; rotation enforcedCC6.1 · A.8.24🟠F-34, F-35
2026-09-15: the exposed GCP key was regenerated (confirmed), so the rotation half of this control is demonstrated. Score held at 🟠 — not raised — because (a) the blobs remain reachable in both git histories, (b) no .gitleaks.toml or push protection exists in either repo, so recurrence is unprevented, and (c) the LinkedIn client id/secret pairs from F-35 have no confirmed rotation. Rotation happened; it is not yet enforced.
D4-11Authorisation role derived from verified identity, not client inputCC6.1 · A.8.5 · A01/A07🔴F-42

Register — Domain 7: Tenant Isolation ​

IDControl statementMaps toStatusFinding
D7-01Request tenant bound to the JWT claim; mismatched header rejectedCC6.1 · A.8.4 · A01🔴F-01
D7-02PostgreSQL row-level security as defence-in-depth backstopCC6.1 · A.8.4🔴F-01
D7-03Tenant-namespaced cache keysCC6.1 · A.8.4🟢—
D7-04Tenant-scoped object-storage prefixesCC6.1 · A.8.4🟢—
D7-05Tenant-scoped asynchronous/workflow visibilityCC6.1🟠F-38 (risk-accepted 2026-08-09 — see Findings)
D7-06Automated cross-tenant access tests in CICC7.1 · A.8.29 · CIS 4🔴—
D7-07Conversation/checkpoint state tenant-scoped (Finni)CC6.1 · A.8.4 · A01🔴(F-43, pending)

Register — Domain 14: Secure SDLC & DevSecOps ​

IDControl statementMaps toStatusFinding
D14-01Threat modeling performed on changes to auth, tenant boundaries, financial data, or AICC8.1 · A.8.25 · A04🔴—
D14-02Automated security scanning gated in CI (SCA, SAST, secret, IaC, image)CC7.1 · A.8.28🟠F-31
D14-03Branch protection + required review on all repositoriesCC8.1 · A.8.32🟠F-37
2026-09-24: branch protection is live via the org-level ruleset DefaultBranchProtection on blitz, blitz-ui, fin-infra and fin-internal-docs (PR required, no force-push, no deletion; fin-infra#55 closed 2026-09-09). Held at 🟠 because (a) required review is 0 approvals, kept by owner decision 2026-09-24, (b) required status checks were removed 2026-09-15, (c) finwebsite and fin-works are outside the ruleset, and (d) org admins bypass always.
D14-04Independent penetration test + vulnerability-disclosure programmeCC7.1 · A.8.29🔴F-24

CI control state — authoritative ​

THE SINGLE SOURCE FOR THIS FACT

Other pages link here; none restate it. Per _MAINTAINING.md: "no page restates another. If a fact appears twice, one copy is a bug." This fact previously appeared in nine places, which is why one CI change invalidated sixteen documents.

As of 2026-09-15, what runs in CI, and what does not:

CheckStateTriggerBlocking?
bun audit (SCA)✅ liveweekly, in each repo's Dependency Health workflow❌ no — reports only
Lockfile guard (--frozen-lockfile, lockfile committed, manifest↔lock sync)⛔ withdrawn 2026-09-15——
Import guard (unresolved relative imports)⛔ withdrawn 2026-09-15——
SAST · secret scanning · IaC scan · image scan🔴 not implemented——

Nothing blocks a merge on a security or dependency check. The org ruleset DefaultBranchProtection no longer carries a required_status_checks rule. Branch protection itself is unchanged and active on blitz, blitz-ui, fin-infra and fin-internal-docs: pull request required, no force-push, no branch deletion, review threads must resolve.

Why the guards were withdrawn. Neither was a vulnerability scanner — both asserted consistency, not safety. Measured over their full lifetime:

GuardRunsFailuresGenuine catchesHit rate
Lockfile guard5045110.2%
Import guard312000%

Of the lockfile guard's 51 failures, 47 were release-bot noise on manifest/* branches and 1 was the PR that introduced the guard testing itself — leaving one real catch (a package.json↔bun.lock mismatch) in about a year. Cost was ~750 billed minutes/month across three repos, at a 13–17s median billed as full minutes, on PRs where 75–84% touched no manifest, lockfile, Dockerfile or workflow.

Accepted residual risk. A manifest↔lockfile mismatch is no longer caught pre-merge; it now surfaces as a failed build or deploy instead of a blocked PR. The failure is self-announcing within the hour and diagnosable in ~0.3s with bun install --frozen-lockfile --dry-run. The guards are disabled, not deleted — re-enabling is one API call per repo.

Direction of travel. D14-02 moved 🔴 → 🟠 rather than staying red: SCA genuinely runs now, weekly, where before it ran nowhere. The maturity ladder in Coverage & Methodology has no symbol for scheduled but non-blocking, which is the actual state — worth adding rather than forcing this into "gated" or "absent".

Threat modeling (D14-01) also runs as a scheduled activity — see the Assessment & Evidence Cadence — and its output feeds the Risk Register.

Coverage notes ​

  • D4-09, D7-06 — defined target-state controls with no corresponding finding; they represent untracked gaps this register surfaces.
  • D4-11, D7-07 — originate from the AI-governance assessment; pending entry into the Findings Register (renumbered to clear the existing F-01 sequence).
  • Domains 16 and 17 (financial-data integrity and business logic — the straight-through-processing / ICFR controls) are the next to be seeded.

Source of detail ​

Full remediation detail for each failed control resides in the linked GitHub issue via the Findings Register.

Revision history ​

VersionDateAuthorChange
0.12026-08-27Sekhar PrakashInitial register; Domains 4 and 7 seeded.
0.22026-09-24Sekhar PrakashD14-03 annotated with the live ruleset state; status held at 🟠.

Finaisse Internal — Confidential. Not for external distribution.

Finaisse Internal — Confidential. Access-restricted; not for external distribution.