Production secrets are platform-managed and the repo documents where they live
Why it matters
SOC 2 CC6.1 (logical access security software, infrastructure, and architectures) expects credentials that gate production systems to live in a managed store with controlled access, not in flat files anyone with repo read access can copy. This check evidences CC6.1: an auditor's first secrets question is "where do production credentials live and who can read them", and the answer must be a platform (Vercel or Netlify encrypted env vars, AWS Secrets Manager, Google Secret Manager, Vault), not a tracked .env. AI-generated and vibe-coded apps typically fail this because coding assistants scaffold dotenv plus a working .env, the app runs, and nothing ever forces the move to a managed store or the writing of a .env.example, so the team cannot even enumerate which secrets exist. This is partly an evidence-pointer check: for dashboard-managed platform env vars it verifies committed evidence of the control (a .env.example, platform config, or ops doc naming the store), which is exactly the artifact a SOC 2 auditor will ask for. It deliberately does not grep for leaked literal values; the sibling no-hardcoded-secrets check covers that. This one checks management posture and documentation.
Severity rationale
High because undocumented or flat-file secret management means production credential access is untracked and unrevocable per person, and a single repo-access grant silently becomes production access, which is the exact access-control architecture failure CC6.1 targets.
Remediation
Move every production credential into your deploy platform's encrypted env vars (Vercel, Netlify, Railway) or a secrets service (AWS Secrets Manager, Google Secret Manager, Vault), read them via process.env only, and commit a .env.example that enumerates each expected variable with placeholder values (STRIPE_SECRET_KEY=sk_live_xxx_set_in_vercel_dashboard). If any tracked file currently holds live values, rotate those credentials, then git rm --cached .env and add it to .gitignore. Add a short ops note (for example docs/secrets.md or a README section) naming the store so the location is auditable from the repo.
Detection
- ID:
production-secrets-platform-managed - Severity:
high - What to look for: First determine whether this is a deployable app: a web/server framework in
package.json(next,express,fastify,hono,remix,@sveltejs/kit,nuxt), a server entrypoint, or deploy config (vercel.json,netlify.toml,fly.toml,render.yaml,app.yaml,Dockerfilewith a serving CMD); Python/Go equivalents:django/flask/fastapiinrequirements.txt/pyproject.toml,net/httpserver inmain.go. If pure library or CLI with no deployment surface, skip. Otherwise gather two evidence legs. Leg 1, env-based reads:process.env.X(JS/TS),os.environ/pydantic BaseSettings(Python),os.Getenv(Go). Leg 2, a committed pointer to the secret store, any ONE of: (a).env.exampleor.env.templatewith placeholder values enumerating expected vars; (b) platform config file (vercel.json,netlify.toml,fly.toml,render.yaml,app.yaml); (c) IaC secret-manager resources (aws_secretsmanager_secret,google_secret_manager_secret,vault_generic_secret/ othervault_*resources) in*.tfor CDK/Pulumi sources; (d) CI secret references (${{ secrets.X }}in.github/workflows/*.yml, or GitLab/CircleCI equivalents); (e) an ops doc (README.md,docs/**,SECURITY.md,secrets.md) naming the secret store. Also checkgit ls-filesoutput (or the tracked tree) for flat secret files: tracked.env,.env.production,.env.local,config/production.json,secrets.ymlcontaining live-looking values (long random strings,sk_live_,AKIA, real connection strings), and confirm.gitignorecovers.env*. Platform-encrypted env vars managed in a Vercel/Netlify/Railway dashboard are a fully compliant store; the committed.env.example, platform config, or doc is the evidence this check verifies, so never fail solely because the store itself is dashboard-side. - Pass criteria: Code reads secrets from the environment (Leg 1) AND at least one Leg 2 pointer exists with placeholder-only values, AND no tracked flat file carries live-looking production credential values.
- Fail criteria: Any tracked flat file contains real-looking production credential values (tracked
.env/.env.productionwith live keys,config/production.jsonwith secrets), even if a.env.examplealso exists; OR the app is deployable and there is zero committed evidence of where production secrets live (no example file, no platform config, no IaC secret resources, no CI secret refs, no ops doc); OR the only "evidence" is a sneaky variant: a.env.examplewhose values are themselves live-looking credentials (a renamed real env file), an ops doc that is a stub or TODO placeholder, an IaC secret resource block that is fully commented out, ordotenvwired to a tracked.env. - Skip (N/A) when: No deployable app surface (pure library, CLI, or docs repo with no server entrypoint and no deploy config). Quote:
"No web framework, server entrypoint, or deploy config — project has no deployable app surface". - Before evaluating, quote: One
process.env(oros.environ/os.Getenv) read site with file path, plus the Leg 2 evidence: the.env.examplepath with 2-3 placeholder lines, or the platform/IaC/CI file path and the specific secret reference, or the ops doc path and the sentence naming the store. On fail, quote the tracked flat-file path and describe (never reproduce) the live-looking values found. - Report even on pass:
"Secrets read via process.env; store evidence: <path> (<env-example | platform-config | iac-secret-manager | ci-secret-refs | ops-doc>); no tracked flat files with live values". - Detail on fail:
".env.production is git-tracked and contains live-looking values (sk_live_ prefix, 40+ char random strings) — rotate these credentials, git rm --cached, and commit a placeholder-only .env.example"or"Next.js app deploys via Dockerfile but repo has no .env.example, platform config, IaC secret resources, CI secret refs, or ops doc naming the secret store — commit evidence of where production secrets live". - Remediation: Move production credentials into platform-encrypted env vars or a secrets service, read via
process.env, and commit a placeholder-only.env.exampleenumerating every expected variable (e.g.DATABASE_URL=postgres://user:pass@host/db_set_in_vercel). If live values are tracked, rotate them first, thengit rm --cached .envand gitignore.env*. Adddocs/secrets.mdnaming the store. Literal leaked-credential grepping is covered by the Stack Scan'sno-hardcoded-secretscheck in theproject-snapshotaudit.
External references
- soc2:2017 · CC6.1 — Logical access security software, infrastructure, and architectures
- iso-27001:2022 · A.5.17 — Authentication information
- nist:rev5 · IA-5 — Authenticator Management
Taxons
History
- 2026-07-03·v1.0.0·Initial soc2-readiness v1 authoring; platform-managed production secrets with committed documentation of the store evidence SOC 2 CC6.1 logical access architecture.·by soc2-readiness-v1-build