Skip to content

docs(spec): a security baseline for reference deployments, enforced in CI - #34

Draft
antstanley wants to merge 1 commit into
spec/harden-release-supply-chainfrom
spec/baseline-reference-deployments
Draft

docs(spec): a security baseline for reference deployments, enforced in CI#34
antstanley wants to merge 1 commit into
spec/harden-release-supply-chainfrom
spec/baseline-reference-deployments

Conversation

@antstanley

@antstanley antstanley commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Note

Stacked PR 15 of 16 — part of stack #35, which targets main.
Base: #30 (spec/harden-release-supply-chain) · Followed by: #28

This PR's diff shows only its own change. Merge the stack bottom-up; do not merge this before its base.

Why it sits at position 15: Reference deployments and templates. Follows #30 because both modify bindings/specs/05-distribution.md, and follows #25, which owns the KMS algorithm strings in the example configs.

Scope of this PR

This PR currently carries the change spec. It will also carry the implementation plan and the build work for that spec — plan and code land as later commits on this same branch, so the spec, its plan, and its implementation review and merge as one unit.

Review order within the PR: spec first, then plan, then implementation.


Change spec adopting the reference-deployment-baseline hardening proposal — the one proposal of eight that no spec had taken, leaving 7 findings with no home including both remaining uncovered mediums.

Proposed delta. Option 2 on the proposal's recommended path: a named, versioned baseline plus a CI conformance gate, with Option 1's per-template fixes as a mandatory first phase and the Option 3 carve-out of one manual end-to-end run of the two KMS examples. Seven findings become work packages A1–A7; the reference-baseline CI job asserts seven baseline properties (B1–B7) over every shipped template, plus three cross-layer assertions that live in cargo nextest because a Terraform scan cannot see a client silently downgrading its own connection.

Findings closed: g4-aws-web-demo-app-unverified-jwt (medium), g4-fargate-alb-plaintext-http-listener (medium), g4-fargate-valkey-unencrypted-unauthenticated, g4-cdk-client-secret-plaintext-lambda-env, g4-k8s-example-no-security-context, g4-postgres-example-schema-drift, g3-local-store-default-file-permissions — plus the orphaned deferred_sqlite_index_recreate_window, which folds into the same create_pool the 0600 fix already opens.

Three things reading the real files corrected:

  • Every published image runs as root. The root Dockerfile has no USER directive, so the Kubernetes finding's runAsNonRoot: true alone would produce CreateContainerConfigError. The fix has to land in the image, not just the manifest.
  • The Fargate finding's own remediation breaks terraform apply. It recommends making the plaintext listener conditional, but main.tf:677-679 pins the ECS service to aws_lb_listener.http via depends_oncount-conditioning it indexes into an empty list. The finding does not mention this.
  • g4-postgres-example-schema-drift names the wrong half. The missing version column is repaired at startup (ALTER TABLE … ADD COLUMN IF NOT EXISTS succeeds because compose makes the app role the DB owner). The unrepairable half is the index: init.sql:14 creates idx_users_external_id while the migration only drops idx_users_external_id_provider.

Confirmed traps: crates/adapters/Cargo.toml:31 is features = ["serde-json"] with no TLS feature and valkey/mod.rs:31 calls Config::from_url, so rediss:// is silently downgraded — the fix needs a Cargo feature (enable-rustls-ring, for consistency with the workspace's sqlx tls-rustls). And resolve_placeholders_in_str reads std::env::var only, so a Secrets Manager ARN in a Lambda environment variable resolves to nothing; the CDK fix needs the AWS_LAMBDA_EXEC_WRAPPER that stack.ts:89 already uses for the sibling function.

On the demo relying party the spec commits to verifying against the service JWKS rather than shipping a decode-only variant, on three grounds: the loader has the literal shape of an auth gate (redirect(302,'/') on both absence and parse failure), so a "display only" comment is the first thing lost to copy-paste; GET /keys otherwise has zero demonstrated consumers anywhere in the repo; and the httpOnly cookie is no substitute, since anything that can write a cookie for the origin supplies a token this loader accepts indefinitely with no exp check.

Canonical pages: bindings/specs/05-distribution.md, service/specs/08-persistence.md, service/specs/06-configuration.md, architecture-principles.md.

Two incidental divergences flagged, not fixed (outside this spec's scope): 05-distribution.md says the builder image is rust:1.85-slim while the real Dockerfile is rust:1.96-slim, and docs/deployment/linux-postgres.md:57 tells operators the service runs its own migrations, contradicting the compose-mounted init.sql.

Drafted with the spec-creator plugin from a deep security scan of the repository at 53cbdec9. Spec only — no source or template changes.

…n CI

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@antstanley
antstanley force-pushed the spec/baseline-reference-deployments branch from fa819d6 to 2ae5cde Compare August 5, 2026 12:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant