Secure Development Policy
This policy governs how security is built into stratify's software development lifecycle. It covers the widest span of controls of any policy in this set (twelve), reflecting that stratify's actual security posture today is overwhelmingly an engineering-process posture: a required CI gate, ADRs, and TypeScript/OpenAPI discipline are real, but they stop short of the two gaps this policy names as the most actionable near-term fixes — dependency scanning and any form of SAST/DAST.
1. The required CI gate — the concrete implementation of secure SDLC (8.25, 8.28)
.github/workflows/dos-ci.yml defines the CI pipeline, and one job — verify — is the single required status check gating every merge to main. verify depends on four jobs, all of which must pass:
- `static` — lint and TypeScript typecheck.
- `test` — the test suite (
pnpm run test). - `secret-scan` — gitleaks, run against the PR diff (
base..HEAD), failing the build on any
detected secret.
- `build` — a production build must succeed.
This is a real, non-bypassable-in-CI gate: a PR cannot merge to main without all four jobs passing (subject to the caveat in §2 about whether branch-protection settings are actually applied server-side, which isn't verifiable from repo contents alone). Control 8.28 (Secure coding) is scored implemented on this basis — lint and typecheck enforcement on every PR is a genuine, structural secure-coding control. Control 8.25 (Secure development life cycle) is scored partial, not implemented, for a specific reason: the gate is real but incomplete — it has no dependency/ SCA scanning step (§3) and no SAST/DAST step of any kind. A required gate that catches lint errors, type errors, and committed secrets but not known-vulnerable dependencies or exploitable code patterns is a real control with a known ceiling, and this policy states that ceiling rather than implying the gate covers more than it does.
Locally, .pre-commit-config.yaml mirrors the CI secret scan (gitleaks, detect-private-key, large-file check) — a real convenience control, bypassable via --no-verify and self-documented as such in CONTRIBUTING.md.
2. Source-code access (8.4)
Source-code access is controlled through GitHub repository permissions. CONTRIBUTING.md describes a branch ruleset — PR required, verify check required, linear history, no force-push — but whether that ruleset is actually configured server-side on GitHub is not verifiable from repo contents alone (register/facts.md); this policy states the described intent without overclaiming its enforcement status.
The known, current gap: `CODEOWNERS` exists in the repository but is fully commented out and inert. CONTRIBUTING.md names this directly: "At Tier 1 (solo) this file routes nothing." This is not a bug to silently work around — at stratify's current single-committer stage, there is no second reviewer for CODEOWNERS to route to, so an inert file is an honest reflection of team size rather than a broken feature. It becomes a real gap the moment a second engineer or contractor joins and CODEOWNERS is not correspondingly activated. Control 8.4 is scored partial for exactly this reason.
3. Management of technical vulnerabilities — the single most actionable gap (8.8)
Scored not_started in register/controls.yaml. No dependency or software-composition-analysis (SCA) scanning exists in either CI pipeline — confirmed absent, not merely undocumented: dos-ci.yml has no pnpm audit step, there is no Dependabot configuration file, and there is no Renovate configuration file anywhere in the repository. The vestigial Jenkinsfile (typecheck, lint, test, build, OpenAPI lint, plus a mobile EAS build on tag, with deploy stages explicitly commented out) likewise has no SCA step. This is tracked as register/risks.yaml RISK-003 (likelihood 3, impact 3, inherent score 9): a known-vulnerable npm/pnpm dependency can ship to production today with nothing in the pipeline to catch it.
This policy names 8.8 as the priority near-term fix in this control cluster, ahead of any other gap in this document, for a specific reason stated in the risk register: it is cheap to close (adding Dependabot or a pnpm audit CI step is a small, well-understood change) relative to its risk reduction. Closing it also directly improves 8.25 (§1) and 5.7 (threat intelligence, POL-01 §5), which share the same root cause.
4. Application security requirements (8.26)
Scored partial. Two structural disciplines provide real, if informal, application-security requirements today: TypeScript strict mode across the codebase, and an OpenAPI 3.1 contract (openapi/stratify-api.yaml) that defines the B2B REST API surface. Neither was designed primarily as a security control — TypeScript strictness catches type errors, and the OpenAPI contract documents the API shape — but both incidentally narrow the space for a class of bugs (malformed input handling, undocumented endpoints) that overlaps with application-security requirements. No explicit security-requirements process (e.g. a checklist applied before a new API endpoint ships) exists on top of these.
5. Secure architecture and engineering principles (8.27)
Scored partial. Architecture Decision Records in docs/adr/ are real evidence of secure engineering practice, not merely process documentation: ADR-0001 adopts the development operating standard this repository follows, and ADR-0002 documents a genuine security-hardening decision — moving web sign-in from magic links (completely broken in production, zero successful logins ever recorded) to 6-digit email-OTP codes with a deliberately shortened expiry and rate limiting (POL-03 §3). This is exactly the kind of decision 8.27 asks to see documented — a security-relevant architectural choice, with its reasoning preserved for future reference — even though the ADR process wasn't originally framed as an ISO control. What's missing is a general architecture security-review step applied to _every_ significant design decision, not just the two documented so far.
6. Outsourced development (8.30) — not applicable
Scored not_applicable. Stratify's codebase is developed in-house: a single human engineer (Toby) plus an AI coding agent, confirmed via git history (register/facts.md — exactly one human identity across all 181 commits, no second human committer). There is no outsourced or contracted development activity to which a control of this kind would apply.
7. Separation of development, test and production environments (8.31)
Scored partial, upgraded from not_started on 2026-08-01: a real integration-test harness (test:integration, apps/web/lib/test/integration/) now runs RLS and audit-chain tests against a local Supabase instance (supabase start), structurally isolated from the single Hetzner production host. That's genuine dev/test separation at the testing-infrastructure level — but it doesn't answer the harder question this section originally raised: whether a persistent _staging_ environment exists between local development and production. register/facts.md still doesn't confirm one either way. This interacts directly with POL-04 §5's data-masking gap: an environment that shares infrastructure with production, or is seeded from production data, raises the stakes of the missing masking control considerably. Confirming or building a genuine staging tier remains an open item requiring investigation, not an assumed-safe default.
8. Test information (8.33)
Scored not_started. No policy exists on the use of production-realistic test data — for example, whether tests may use anonymized copies of real subscriber or signal data, or must use synthetic fixtures only. This is the same underlying gap as POL-04 §5 (data masking) viewed from the development-process side rather than the environment side: until it's resolved, the safe default this policy states is that test data should be synthetic, not derived from production, unless and until an explicit masking process exists to make derived data safe to use.
9. Protection of information systems during audit testing (8.34)
Scored not_started, and honestly not yet relevant in practice: no penetration test has been executed against stratify. docs/compliance/pentest-rfp.md is RFP/vendor-shortlist stage only — scope, priorities (RLS-bypass and audit-log tamper-resistance are flagged P0 in that document), a four-vendor EU shortlist, a timeline template, and a €25k budget cap are all drafted, but there is no findings document, no re-test log, and nothing indicating the RFP was actually sent (register/facts.md, register/risks.yaml RISK-006). The RFP's infra-scope section also still says "Vercel EU," which needs correcting to Hetzner before it can be sent to any vendor in good faith. Once a pentest is actually scheduled, this control needs a real answer — how production systems are protected from unintended damage during testing (rate limits, a defined blast radius, a rollback plan) — which does not exist yet because the scenario it protects against hasn't happened.
10. Control summary
| Control | Status (controls.yaml) | This policy's role |
|---|---|---|
| 5.8 Information security in project management | partial | CI gate applies security requirements per-change; not framed as a PM-stage requirement (§1). |
| 8.4 Access to source code | partial | GitHub permissions real; CODEOWNERS inert, ruleset not repo-verifiable (§2). |
| 8.8 Management of technical vulnerabilities | not_started | No SCA scanning anywhere — the single most actionable near-term fix (§3, RISK-003). |
| 8.25 Secure development life cycle | partial | Real required CI gate; missing SCA and any SAST/DAST (§1). |
| 8.26 Application security requirements | partial | TypeScript strict + OpenAPI contract provide informal discipline; no explicit process (§4). |
| 8.27 Secure system architecture and engineering principles | partial | ADRs are real evidence, notably ADR-0002; not applied to every decision (§5). |
| 8.28 Secure coding | implemented | Lint + typecheck enforced on every PR via the required verify check (§1). |
| 8.29 Security testing in development and acceptance | partial | Larger suite + per-workspace coverage floors landed 2026-08-01, not yet in the required gate; still no SAST/DAST. |
| 8.30 Outsourced development | not_applicable | In-house only — single human plus an AI coding agent (§6). |
| 8.31 Separation of dev/test/prod environments | partial | Local-Supabase integration-test harness is real isolation; a persistent staging tier is still unconfirmed (§7). |
| 8.33 Test information | not_started | No policy on production-realistic test data (§8). |
| 8.34 Protection of information systems during audit testing | not_started | Not yet relevant — no pentest has been executed (§9). |
11. Review
This policy is reviewed on the cadence set at approval, and immediately upon: SCA scanning being wired into dos-ci.yml (closing §3), confirmation of whether a staging environment exists (closing §7), a second engineer or contractor joining (reactivating CODEOWNERS per §2), or the pentest RFP in §9 actually being sent.