Skip to content

Per-Feature Security Requirements

This page maps the open Features in the rag-sample backlog to testable security requirements. The system-level controls live in assessment; the boundaries live in trust boundaries; the threats live in threat model. This page is the per-feature translation layer that turns those into acceptance criteria that a story author or QA reviewer can check off.

Each requirement carries a Verify column. If a requirement cannot be verified by a test (unit, integration, IaC scan, or runbook step), it does not belong on this page.

  • TM references point to rows in threat model (T1, T2, T3, or a STRIDE matrix row by boundary).
  • TB references point to rows in trust boundaries (B1 through B9).
  • ADR references are the binding decisions; deviation requires a new ADR.
  • Where a Feature has no security requirement beyond the system baseline, the row says so explicitly. Empty rows are an oversight, not a pass.

Feature 18: SPA wiring of the chat surface

Section titled “Feature 18: SPA wiring of the chat surface”

Browser code consuming /query and (per RAG009) attaching the bearer token.

RequirementVerifyMitigates
SPA attaches Authorization: Bearer <key> to every /query call, never to /healthz.Cypress or Playwright test asserting header presence on /query and absence on /healthz.RAG009; B1.
Key is read from localStorage, never from URL parameters or referrer-leakable channels.Unit test on the auth hook; code review on PRs touching the auth module.RAG009 SPA exposure note.
Question input is length-capped client-side at the same bound the API enforces (e.g. 2 KB), to surface 422 as a UX issue rather than as wasted Bedrock spend.Component test on the input bound; contract test against openapi.yaml.T2; assessment gap “confirm bound is meaningful”.
No third-party script is loaded into the SPA without a CSP review; the bundle ships with a Content-Security-Policy header (set at CloudFront response-headers policy).Terraform asserts the response-headers policy on the CloudFront distribution; integration test fetches index.html and asserts the header.RAG009 XSS re-evaluation trigger.
The visible-key badge masks all but the last 4 characters of the secret half of the key.Component test on the badge renderer.RAG009 visible-key element.

Feature 19/20/21/22/23/24/25: Infrastructure modules and IAM/secrets

Section titled “Feature 19/20/21/22/23/24/25: Infrastructure modules and IAM/secrets”

Terraform modules that materialize the platform. Treat each module as a unit of compliance.

RequirementVerifyMitigates
Every Lambda execution role is scoped to specific resource ARNs (cluster ARN, secret ARN, Bedrock model IDs); wildcards on resources are forbidden outside cloudwatch:PutMetricData and X-Ray put-segments.tfsec or checkov rule in CI; PR check fails on wildcards in aws_iam_policy_document resources.Assessment “Least-privilege IAM”; B4, B5.
Aurora cluster has no public endpoint and the security group denies inbound from 0.0.0.0/0; access is via RDS Data API only.tfsec rule; integration test on terraform plan JSON.STRIDE row “Aurora at rest”; assessment “Network minimization”.
Aurora and Secrets Manager use KMS encryption at rest. AWS-managed keys today, customer-managed keys when reclassification occurs (data-classification rule).terraform plan assertion that storage_encrypted = true and a KMS key reference is present.Assessment “Encryption at rest”; T3.
Secrets Manager secret ARN is referenced, never the secret value, in Terraform variables and outputs.tfsec/checkov “no plaintext secret” rule; PR review on *.tfvars.Assessment “Credentials”.
S3 buckets (SPA bundle, Terraform state) deny public access via bucket policy and block-public-access settings; CloudFront origin access uses OAC, not a public bucket.tfsec rule; integration test on the OAC binding.STRIDE row “CloudFront / S3 SPA”.
The OIDC role used by GitHub Actions has an environment-scoped trust policy that names the repo and the environment (qa, prod); * in the sub claim is forbidden.PR check parsing the trust policy JSON.STRIDE row “CI -> AWS”; B8.
All log groups are created with retention_in_days = 30 set explicitly. Default-infinite retention is a CI failure.tfsec rule; terraform plan assertion.RAG008; assessment “Observability”.

Feature 26/27/28: Environment promotion (QA to Prod)

Section titled “Feature 26/27/28: Environment promotion (QA to Prod)”
RequirementVerifyMitigates
The Prod GitHub Environment requires a manual reviewer approval; the QA environment does not.gh api check on the environment protection rules in CI.RAG001; STRIDE row “CI -> AWS”.
Promotion artifacts (Lambda zip, SPA bundle, OpenAPI spec) are byte-identical between QA and Prod runs; the only diff is environment configuration.Pipeline step diffs artifact hashes between QA and Prod jobs.Supply chain integrity; B8.
Terraform state is per-environment with separate S3 backends with S3-native state locking; Prod state is not writable from the QA role.Bucket policy review; one-time test that the QA OIDC role cannot s3:PutObject against the Prod state bucket.RAG001; STRIDE row “CI -> AWS”.
RequirementVerifyMitigates
pip-audit runs on every PR. Findings of high or critical severity block merge once the noise floor is established; until then they post as PR comments. See sbom.CI job; required check on the protected branch once promoted.Assessment gap “Dependency vulnerability scanning not in CI”.
npm audit --audit-level=high runs on every PR for the SPA workspace.CI job.Same as above for the SPA tree.
Secret scanning (e.g. gitleaks) runs on every PR and fails the build on any detection that is not in the documented allowlist.CI job; allowlist file under code review.Assessment “Credentials”.
Terraform plan output is posted to the PR with tfsec/checkov results inline; high-severity findings block merge.CI job; required check.All IaC requirements above.
The CI role cannot read production secrets. The Prod OIDC role is only assumable from the prod environment-gated job.Trust-policy check in CI; runbook for periodic audit.STRIDE row “CI -> AWS”; B8.

Feature 31/32/33/34: Cost guardrails and observability

Section titled “Feature 31/32/33/34: Cost guardrails and observability”
RequirementVerifyMitigates
The Powertools Logger configuration scrubs the Authorization header from every log entry; a failing test on log output is part of the authorizer Story acceptance (per RAG009).Unit test asserting the logged event has no authorization key under any casing.RAG009 log-scrub follow-up; STRIDE row “Lambda -> Secrets Manager”; log-as-exfil path.
Question and answer bodies log at INFO with a configurable redaction list; if the data-classification rule ever fires for a non-public class, the redaction config is the single switch the operator flips.Unit test on the redactor; runbook entry.T3; data-classification rule.
The CloudWatch alarm on Lambda error rate routes to SNS with at least one subscribed email per environment.Terraform assertion; one-time confirm of the subscription.RAG008; assessment “Observability”.
The token-spend alarm (#64) fires on a defined daily ceiling; the threshold is documented per environment.Terraform assertion; runbook entry.T1/T2 cost-runaway shape; assessment gap “No abuse alarm on Bedrock token spend”.
The /query route throttle (#65) caps requests at the documented rate; /healthz is exempt.Terraform assertion on the API Gateway route settings; integration test driving 429s.Assessment control “Route-level rate limit” (#65 closed as completed); STRIDE row “Browser -> API Gateway”.

The implementation choice (Bedrock Guardrails) is settled in Decision #55 (closed). The Feature itself carries requirements regardless of choice.

RequirementVerifyMitigates
Retrieved chunks are delimited in the prompt with an unambiguous fence; the system prompt instructs the model to treat fenced content as data, not instructions.Unit test inspecting the assembled prompt for the fence and the instruction.T1; B3.
User-authored question is never interpolated into the system prompt block; it occupies its own delimited region with its own provenance instruction.Unit test on prompt assembly.T2; B3.
Question input strips ASCII and Unicode control characters before prompt assembly.Unit test with adversarial fixtures.T2.
Any future safety-gate implementation produces a structured verdict that is logged with the request correlation ID.Contract test against the verdict schema.T1, T2; observability traceability.

This Feature is the umbrella for the items already filed (#62 log scrub, #64 token alarm, #65 throttle, #66/#67 provenance/classification gate) plus the assessment gaps. No new requirements specific to the parent; the parent passes when its children pass.

RequirementVerifyMitigates
The corpus Apache-2.0 attribution appears in the rendered SPA (footer or About) and in the architecture overview, as already noted in compliance.Cypress test asserting the attribution text is present in the SPA.Compliance “Third-party license: Apache-2.0”.
Dependency licenses are surfaced in the SBOM (see sbom); copyleft licenses (GPL, AGPL) on production runtime dependencies block merge until reviewed.CI step parses the CycloneDX SBOM, flags non-permissive licenses, posts to PR.Supply-chain license risk; not covered in current threat model (gap, see report).

Largely covered under 31/32/33/34. The Feature-specific addition:

RequirementVerifyMitigates
Correlation IDs propagate from API Gateway through the API Lambda and into Bedrock and Data API subsegments; logs and traces share the ID.Integration test on a single request that asserts the same ID appears in CloudWatch Logs Insights and the X-Ray trace.Repudiation row across STRIDE matrix; incident-response readiness.

The Prod-promotion checklist that closes the assessment gaps.

RequirementVerifyMitigates
The incident-response runbook from the assessment gap exists and has been walked through once with the operator.Runbook checked in under docs/.../runbooks/; dry-run notes attached.Assessment gap “No documented incident response runbook”.
The Bedrock token-spend alarm and the /query throttle are deployed in Prod.Terraform plan check at Prod promotion.Assessment gap “abuse alarm”; assessment control “Route-level rate limit”.
pip-audit and npm audit are blocking checks on the protected branch (no longer noise-floor mode).gh api check on branch protection.Assessment gap “Dependency vulnerability scanning”.
The Hugging Face dataset revision SHA is pinned and recorded in the ingestion run metadata.Ingestion log assertion; runbook step.Assessment gap “Corpus integrity”; B6.

These map directly to RAG009; the Feature’s security requirements are the ADR’s acceptance criteria. Restating for traceability:

StoryRequirementVerify
#60 authorizerAuthorization: Bearer <key> is the only accepted credential; expires_at > now() AND remaining_requests > 0 AND hash match are all required; FOR UPDATE makes the decrement race-safe.Unit and integration tests in the authorizer module.
#60 authorizerAuthorizer response caching is disabled on the API Gateway authorizer resource.Terraform assertion.
#60 authorizerThe authorizer Lambda IAM role is scoped to rds-data:ExecuteStatement on the cluster ARN and secretsmanager:GetSecretValue on the credential secret ARN. Nothing else.tfsec rule; PR review.
#61 issue-key CLIKey generation uses a cryptographic RNG (Python secrets), prints the key once, and stores only the hash.Unit test; CLI integration test asserting the key is not stored in plaintext.
#62 log scrubThe Authorization header is scrubbed from every CloudWatch log entry across the API Lambda, the authorizer Lambda, and any future Lambda that touches the request.Unit test on each handler’s log output with a fixture request carrying a fake token.
#63 SPA key intakeKey is stored in localStorage keyed under a documented name; the SPA never sends it anywhere except the /query Authorization header.Cypress test inspecting outbound network calls during a session.

Covered as an acceptance row under Feature 31/32/33/34 above. The alarm threshold is documented per environment in the runbook.

Covered as an acceptance row under Feature 31/32/33/34 above.

Feature 66/67: Content provenance and classification gate

Section titled “Feature 66/67: Content provenance and classification gate”
RequirementVerifyMitigates
The ingestion pipeline records the dataset name, revision SHA, license string, and ingestion timestamp in a corpus_provenance table or equivalent metadata file alongside the embeddings.Unit test on the ingestion writer; runbook step on operator inspection.T3; data-classification rule item 1.
Any change to the corpus reference in pyproject.toml, Terraform, or the ingestion config requires a CODEOWNERS review by the data-classification owner.CODEOWNERS rule on the relevant paths; PR check that the review fired.Data-classification rule “process gate”.
The classification gate is a CI step that fails if the corpus reference changes without a matching update to data classification.CI job parsing the diff and the classification page.Data-classification rule items 1 through 7.