Security [HIGH]: Docker + Compose — hardcoded dev password, env-var secrets, placeholder secrets baked in prod image #50
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
(1)
docker-compose.yml:11hardcodesPOSTGRES_PASSWORD: capakraken_dev(repo-committed). (2) AI/SMTP secrets passed viaenvironment:in all compose files — visible indocker inspectand/proc/<pid>/environ. (3)Dockerfile.prod:47-62sets placeholder secrets asENV(not justARG) → secrets persist in image layers, and missing-runtime-env silently falls back to placeholders. (4)Dockerfile.devruns as root, no HEALTHCHECK.Evidence
docker-compose.yml:11 — POSTGRES_PASSWORD hardcodeddocker-compose.yml:73-85 — AZURE_OPENAI_API_KEY via env: not secrets:Dockerfile.prod:56-61 — ENV NEXTAUTH_SECRET=$NEXTAUTH_SECRETDockerfile.dev — no USER directive, no HEALTHCHECKImpact
(1) Dev password in repo history. (2) Any host-process reading /proc sees AI keys. (3) Placeholder secret image-layer persistence — missing runtime env silently degrades security. (4) Dev-container compromise yields root.
Proposed Fix
(1) Replace with
${POSTGRES_PASSWORD:?required}in dev compose. (2) Migrate all secrets to Dockersecrets:with file mounts (/run/secrets/*). (3) In Dockerfile.prod, dropENVlines for secrets — keep onlyARG; fail-fast if runtime env not set. (4) AddUSER nodeto Dockerfile.dev + HEALTHCHECK hitting /api/health. (5) Verify.dockerignorecovers**/.env*,**/*.pem,**/secrets/**.Acceptance Criteria
secrets:block for API keysParent Epic: #1
Source: Full-Codebase Security Audit 2026-04-16 (C-9, C-10, C-13)
Fixed on branch
security/audit-2026-04-17(commit805bb04).What changed
1. Hardcoded dev password removed
docker-compose.ymlnow requires${POSTGRES_PASSWORD:?...}for both the postgres service init and the app container sDATABASE_URL. Compose refuses to start without a value — no silent fallback to a known-committed password. Mirrors the existingPGADMIN_PASSWORDpattern.2. Placeholder secrets no longer bake into published images
Dockerfile.prodused toENV NEXTAUTH_SECRET=$NEXTAUTH_SECRETetc. at the builder stage. Since the publishedmigratorimage isFROM builder, those ENV lines leakedci-build-placeholder-secret-minimum-32-charsinto the registry-pushed artifact. Fixed by moving the env vars to an inline env prefix on thepnpm buildRUN step — still available tonext build, no longer persisted in any image layer.3.
runtime-env.tsguard extendedAdded the two CI placeholder strings to
DISALLOWED_PRODUCTION_SECRETS, so a prod container started with a leaked placeholder fails at startup instead of silently running with a known-bad secret. New unit test covers the case.4. Dockerfile.dev HEALTHCHECK
Added
HEALTHCHECKhitting/api/healthon port 3100. Installedcurlfor it. (Did not addUSER node— the dev image bind-mounts the host repo at.:/app; changing owner would break write access from host-triggered file edits.)5. .dockerignore hardening
Added
**/.env*,**/*.pem,**/*.key,**/secrets/**globs so nested package-level env files or accidental PEM files can t sneak into the build context.6. CI adjustments
Fresh-Linux Docker Deployjob s minimal.envnow writesPOSTGRES_PASSWORD=capakraken_dev(matches the hardcoded password indocker-compose.ci.ymlsDATABASE_URLoverride).e2e-testsjob setsPOSTGRES_PASSWORD: ci-unusedin its env block (same reason as the existingPGADMIN_PASSWORD: ci-unused— compose validates interpolation across all services before applying profile filters).7. .env.example documented
Added the new
POSTGRES_PASSWORDvariable with explanation of the dev-vs-prod guidance.Verification
pnpm test:unit— 396 test files / 1922 tests passedpnpm lint— 0 errorspnpm --filter @capakraken/web exec tsc --noEmit— cleannode scripts/check-architecture-guardrails.mjs— passeddocker compose configwithoutPOSTGRES_PASSWORD— fails with the required-variable error (as expected)docker compose configwithPOSTGRES_PASSWORD=test— parses cleanlyOut of scope (left for follow-up)
environment:to docker-composesecrets:mounts — that is a deployment-ops change (needs swarm or secrets manager), not a code change. Listed in the ticket as (2) but gated on infra decision; tracked separately if it becomes required.USER nodeinDockerfile.dev— rejected because of bind-mount ownership (see above).