Secure development
How a code change reaches production, what gates it, and the controls around production change.
Six gates, in order
- Change on a branch, never directly on the default branch.
- Static type checking. The codebase is fully typed and a type error stops the change. This is what makes controls such as the licence classification a compile-time requirement rather than a convention.
- Automated tests. 985 of them, including tests that assert security controls refuse.
- Review against the recorded reasoning in the repository before the change lands.
- Deploy as an immutable version. The platform records every deployment; nothing is edited in place.
- Verify against the running system after deployment, rather than treating a passing test suite as confirmation that a feature works.
Version control is the audit trail
- Every change lands through version control with a full auditable history: who, when, what and why. The reasoning is recorded in the commit alongside the change.
- Database changes are versioned migrations, applied in order, recorded in a ledger and replayable from the repository. There are currently 67. An ad-hoc change to the production schema that is not written as a migration is treated as a defect, and the operations report compares what the code expects against what the database holds.
- Deployments are immutable and versioned. Rollback is redeploying a prior version rather than editing a running system.
- Schema changes are additive first, so the old and new application versions can run concurrently during a deployment.
- Secrets are never committed. Commits are scanned for credential patterns before they land.
What each environment can reach
| Environment | Data | Secrets |
|---|---|---|
| Local | Local store, synthetic fixtures | None bound by default |
| Preview | Static HTML only | None bound |
| Production | The live record | Platform secret store, injected at runtime, not readable back |
There is no shared staging environment holding a copy of production data. A staging copy is a second copy of the data under weaker controls and is a common source of exposure. Correctness is established by the type system, the test suite and the integrity checks; deployment safety is established by immutability and fast rollback.
Reaching production from a development machine requires deliberately supplying production credentials and is treated as production access under access control.
The specific controls in this codebase
- Parameterised queries throughout. No string-built SQL on any path that accepts user input. Query filters are built by shared builders so that a new endpoint cannot construct its own and lose a control — the licence filter is implemented inside one of those builders for that reason.
- Fail closed. Authorisation failures deny; a refused request never reads from or writes to the shared cache; an unrecognised data source is treated as licence-restricted rather than open.
- Inputs are validated and bounded. Every list endpoint has a hard row ceiling, every request has a time budget, and every external fetch has a timeout. Bulk export is a licensing arrangement rather than a query parameter.
- Output encoding and a restrictive Content-Security-Policy on every page, enumerating the permitted origins.
- No secret reaches a log, an error message or a response body.
- Errors are surfaced rather than swallowed. A component that cannot do its job reports that, rather than returning an empty result — an empty result and a broken component are indistinguishable downstream. This is the most consistently enforced rule in the codebase.
What governs a change at this company's size
Reviewers ask whether one person can put arbitrary code into production unnoticed. The current position:
Deployment is performed by the engineering owner. A second human approver is planned as the team grows and will be documented here when it is in place. The controls that apply today:
- Nothing reaches production without passing the automated gates. Static type checking and 985 tests, including tests that assert the security controls refuse. Neither can be bypassed.
- Every deployment is recorded and immutable. There is a complete, externally held record of every version that has run and when, and rollback takes about a minute.
- The database enforces its own half. Deny-by-default row-level security and zero publicly executable functions mean an application error does not become a data exposure.
- The integrity checks monitor outcomes, not intent. A change that breaks the licence filter, empties a column or stops a cursor advancing is detected regardless of who deployed it.
- Every change is recorded with its reasoning, so the history is reviewable after the fact, including by an auditor.