docs: close Phase 5D security hardening
The documentation still described Phase 5D as in progress, Phase 6 as blocked, and a disposable-runner isolation proof as a prerequisite for opening the real pull request. All five passes and the independent whole-repository re-audit have landed, so those statements no longer match the accepted operating model. The README's current status now records Phase 5D as complete under the current trusted-contributor, not-yet-deployed threat model, with Phase 6 unblocked, and gains a pass 5 closure paragraph covering the re-audit, the repository-local supply-chain and dependency fixes, the accepted trusted-code-only CI boundary, and what remains required before untrusted CI or a public launch. The pass 1-4 paragraphs keep their technical content unchanged; only their tense changes, so each reads as what was outstanding at the close of that pass rather than as a current claim. `platform-architecture.md` no longer stops at pass 1 or says passes 2-5 are unfinished; it summarizes all five passes and defers the remaining operational limitations to the security document. `security.md` states the trusted-code-only operating rule explicitly and replaces "CI blockers before the real pull request is opened" with "Requirements before executing untrusted contributions", framing the five hardening items as prerequisites for relaxing that rule rather than as Phase 6 blockers, and naming an isolated hosted-runner service as an accepted alternative. The Gitea 1.24.2 warnings, the scope of `persist-credentials: false`, and the unverified runner topology and job-token authority are preserved verbatim; no external Gitea setting is claimed. The launch checklist is marked as gating a public launch, not development. Both Doctor examples are corrected to the real three-migration output, adding `core:0002_authorization_integrity · 634294731180`. The stale sentence calling Core authorization the next architectural phase is refreshed. ADRs 0017-0020 are untouched: their statements that later passes were outstanding are historical records made when each was accepted. Documentation only — no production code, test, workflow, dependency, migration, or schema change. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
27088fc054
commit
288c088377
@@ -114,28 +114,45 @@ Phase 5A wired module-declared permissions into that same authorization system
|
||||
manifest, one composed platform permission registry spanning Core's own declarations and every
|
||||
installed module's, enforced through the identical middleware Core's own routes use. Phase 5B let a
|
||||
module mount its own framework-neutral HTTP routes in production, under
|
||||
`/api/v1/modules/<module-id>`, enforced and response-sanitized exactly like Core's own routes. This
|
||||
branch completes Phase 5C: typed module database access (`context.services.database`) for a
|
||||
module's lifecycle hooks and authorized HTTP handlers, over the platform's one existing PostgreSQL
|
||||
pool. See [ADR 0014](decisions/0014-module-permission-declarations-and-platform-registry.md),
|
||||
`/api/v1/modules/<module-id>`, enforced and response-sanitized exactly like Core's own routes. Phase
|
||||
5C added typed module database access (`context.services.database`) for a module's lifecycle hooks
|
||||
and authorized HTTP handlers, over the platform's one existing PostgreSQL pool.
|
||||
See [ADR 0014](decisions/0014-module-permission-declarations-and-platform-registry.md),
|
||||
[ADR 0015](decisions/0015-static-module-http-routing-and-runtime-lifecycle.md),
|
||||
[ADR 0016](decisions/0016-module-database-access.md), and the repository root
|
||||
[README](../../README.md#current-status) for the complete, current status of every phase.
|
||||
|
||||
Phase 5D pass 1 of 5 hardens the public request perimeter against an independent security audit of
|
||||
the Phase 5C checkpoint: a server-owned client-address resolution boundary (direct mode by default,
|
||||
explicit trusted-proxy mode otherwise) replaces trusting a caller-controlled forwarded header for
|
||||
the authentication rate limiter; public sign-up is disabled by default behind an explicit typed
|
||||
opt-in; every public route — Better Auth, Core's grant mutation, and every module route via a new
|
||||
required `maxBodyBytes` field on the executable route contract — has an explicit, enforced
|
||||
request-body ceiling with one sanitized `413` contract; and the Node HTTP server has finite
|
||||
header/request timeouts. See
|
||||
[ADR 0017](decisions/0017-public-request-perimeter-hardening.md). This pass does not add
|
||||
distributed rate limiting or durable signup-abuse control, and Phase 5D itself is not complete —
|
||||
passes 2–5 and a final independent re-audit remain before Phase 6 may begin.
|
||||
**Phase 5D is complete.** Five passes hardened the platform against an independent security audit of
|
||||
the Phase 5C checkpoint, and an independent whole-repository re-audit closed the phase:
|
||||
|
||||
The project continues finishing the module platform's backend foundations before Phase 6 Surface
|
||||
work begins. Target architecture not yet implemented includes production approval-provider
|
||||
1. the public request perimeter — a server-owned client-address resolution boundary for the
|
||||
authentication rate limiter, public sign-up disabled by default behind an explicit typed opt-in,
|
||||
an enforced request-body ceiling on every public route (including a module route's own declared
|
||||
contract), and finite Node header/request timeouts;
|
||||
2. identity session resolution, and a mandatory certificate- **and** hostname-verified PostgreSQL
|
||||
transport with no plaintext or `rejectUnauthorized: false` exception;
|
||||
3. durable authorization history that survives user deletion, a one-time owner bootstrap that
|
||||
deleting a user cannot reopen, and bounded, unambiguous explicit-grant management;
|
||||
4. module route-overlap rejection at registration, finite startup rollback and shutdown deadlines,
|
||||
and hardened migration command timeouts and SQL execution;
|
||||
5. deployment and supply-chain closure plus the final re-audit.
|
||||
|
||||
See [ADR 0017](decisions/0017-public-request-perimeter-hardening.md),
|
||||
[ADR 0018](decisions/0018-identity-session-resolution-and-database-transport.md),
|
||||
[ADR 0019](decisions/0019-durable-authorization-history-and-grant-management.md), and
|
||||
[ADR 0020](decisions/0020-module-runtime-and-migration-execution-hardening.md).
|
||||
|
||||
These guarantees hold under a **trusted-contributor, not-yet-deployed** threat model. The remaining
|
||||
limitations are operational rather than architectural: CI is a trusted-code-only boundary whose
|
||||
runner topology and job-token authority are unverified, and Ekklesia is not deployed, so the origin,
|
||||
proxy, and throttling properties a public launch requires are unproven. Neither set of requirements
|
||||
blocks Phase 6. Read
|
||||
[Security model and deployment trust boundaries](../development/security.md) for what each boundary
|
||||
does and does not protect against, and the repository root
|
||||
[README](../../README.md#current-status) for the current status of every phase.
|
||||
|
||||
The module platform's backend foundations are now in place and Phase 6 Surface work is unblocked.
|
||||
Target architecture not yet implemented includes production approval-provider
|
||||
composition, physical storage-bucket provisioning, jobs/workflow automation, richer public resource
|
||||
and action contracts, a Dashboard, a Builder, and reference feature modules; none of these are
|
||||
scheduled by this document.
|
||||
|
||||
@@ -108,9 +108,10 @@ Data definitions
|
||||
✓ Schemas validated
|
||||
0 modules · 0 tables
|
||||
✓ Migrations validated
|
||||
2 migrations · checksums valid
|
||||
3 migrations · checksums valid
|
||||
core:0000_identity_core_tables · f8f2086ef06f
|
||||
core:0001_authorization_core_tables · 830e1fbc26d0
|
||||
core:0002_authorization_integrity · 634294731180
|
||||
|
||||
PostgreSQL
|
||||
○ Connection check skipped
|
||||
@@ -133,11 +134,11 @@ Result: READY WITH WARNINGS
|
||||
Doctor is a local, human-readable summary. `pnpm verify`, `pnpm ci:verify`, infrastructure smoke,
|
||||
and migration smoke remain the authoritative automated evidence because they exercise formatting,
|
||||
boundaries, types, tests, builds, containers, and live mutation/rollback behavior that Doctor does
|
||||
not repeat. Core authorization and an explicit platform-owner bootstrap command are the next
|
||||
architectural phase; Doctor does not diagnose authorization, the `apps/api` HTTP transport, runtime
|
||||
module health, or production storage yet, and it never constructs an operational Better Auth
|
||||
instance, starts the API, or claims sign-in works. See [API](api.md) for `pnpm api:start` and the
|
||||
identity HTTP transport this repository now has.
|
||||
not repeat. Core authorization and the explicit platform-owner bootstrap command have since shipped,
|
||||
and Doctor reports the composed permission registry; it still does not diagnose grant evaluation, the
|
||||
`apps/api` HTTP transport, runtime module health, or production storage, and it never constructs an
|
||||
operational Better Auth instance, starts the API, or claims sign-in works. See [API](api.md) for
|
||||
`pnpm api:start` and the identity HTTP transport this repository now has.
|
||||
|
||||
Migration planning and application require `DATABASE_URL`. Keep it in the uncommitted `.env`
|
||||
created from `.env.example`:
|
||||
|
||||
@@ -111,6 +111,10 @@ A loopback default is not a firewall, and a config default is not a verified bou
|
||||
Until every one of these is verified on the actual hosts, the deployment is not launch-ready. None
|
||||
of them can be satisfied by reading this repository.
|
||||
|
||||
This checklist gates a **public launch**. It does not gate development: Surfaces, modules, and Core
|
||||
work all proceed against the local development infrastructure while every item below is still
|
||||
outstanding.
|
||||
|
||||
1. The reverse-proxy path serves the API successfully.
|
||||
2. The origin port is unreachable from the public Internet.
|
||||
3. The origin port is also unreachable from any untrusted network/VLAN — only the intended private
|
||||
@@ -143,6 +147,17 @@ Ekklesia does not ship one. Do not describe the current limiter as distributed t
|
||||
CI runs on self-hosted Gitea Actions runners. The inventoried server is **Gitea 1.24.2**, and that
|
||||
version determines which parts of the workflow are real guarantees and which are not.
|
||||
|
||||
### The current operating rule: trusted code only
|
||||
|
||||
**This CI installation is a trusted-code-only boundary.** Only code controlled and reviewed by
|
||||
trusted repository collaborators may execute on the self-hosted runner. An unknown or untrusted pull
|
||||
request must **not** execute on it automatically — a maintainer reviews the diff, including any
|
||||
change to the workflow itself, before it runs.
|
||||
|
||||
This is an operating rule, not an enforced technical control, and it is the reason the rest of this
|
||||
section is safe today. Everything below describes what the repository can and cannot prove about
|
||||
that runner; none of it is a substitute for the rule.
|
||||
|
||||
### What is true of the repository today
|
||||
|
||||
These are properties of files in this repository, enforced by the action or the package manager,
|
||||
@@ -192,7 +207,15 @@ Docker socket, must be established against the actual Gitea instance and runner
|
||||
evidence exists, treat pull-request CI as executing with whatever authority the runner host actually
|
||||
grants.
|
||||
|
||||
### CI blockers before the real pull request is opened
|
||||
### Requirements before executing untrusted contributions
|
||||
|
||||
These five items are the prerequisites for **relaxing the trusted-code-only rule above** — that is,
|
||||
for letting code this project has not reviewed execute on this Gitea installation. They are backlog
|
||||
requirements for that specific future capability. **They do not block Phase 6 Surface work**, and
|
||||
they are not conditions on merging trusted, reviewed work.
|
||||
|
||||
None of them can be satisfied by editing this repository; each requires access to the Gitea server
|
||||
and runner hosts, and each must be proven there.
|
||||
|
||||
1. Upgrade Gitea to a reviewed supported release with job-token permission controls — at least the
|
||||
1.26 feature line. Select the actual release during infrastructure work, after backup and
|
||||
@@ -204,4 +227,9 @@ grants.
|
||||
private repository.
|
||||
4. Enforce job timeout, cancellation, concurrency, and teardown in the trusted
|
||||
provisioner/reaper rather than relying on the ignored workflow keys above.
|
||||
5. Complete the disposable-guest runner isolation proof before opening the real pull request.
|
||||
5. Complete a disposable-guest runner isolation proof: one throwaway guest per job, torn down by a
|
||||
trusted reaper outside workflow YAML, holding no deployment credential, registration token, or
|
||||
reusable key.
|
||||
|
||||
Moving future public contribution workflows to a service that provides isolated hosted runners — for
|
||||
example GitHub — is an accepted alternative to satisfying items 1–5 on this installation.
|
||||
|
||||
Reference in New Issue
Block a user