Docker image secret-leak audit: findings and follow-ups
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- docker, docker-compose, github-actions
- Domain
- build-system, devops, security
Research direction
Start with tmp/docker-audit/docs/security/docker-audit-2026-05-14.md and verify the unresolved GRAYLOG_PASSWORD_SECRET finding, then inspect the listed Dockerfiles and smartem-decisions/.github/workflows/release-smartem-decisions.yml. Done means the Graylog decision and any required rotation are recorded, the published image is replaced, and the listed Dockerfile and .dockerignore hardening actions are addressed; delete the audit scratch files afterward.
Written by the indexing model from the issue text.
Description
Summary
Read-only audit of every Dockerfile, docker-compose file, locally-built image, and CI workflow across smartem-decisions, smartem-devtools, and smartem-frontend, looking for credentials baked into image layers. No rebuilds, no pushes, no rotations. Current Dockerfiles produce clean images; one leak exists in an already-published GHCR image from June 2025, and a handful of hardening warnings apply across the three repos.
This issue is filed in smartem-devtools because that's where workspace-wide concerns live. The actual leak is in ghcr.io/diamondlightsource/smartem-decisions:latest, so remediation work will land in smartem-decisions.
Full report (local, in workspace scratch, not in any repo): tmp/docker-audit/docs/security/docker-audit-2026-05-14.md. Audit scratch (extracted rootfs, raw .env from the leaked image) is at tmp/docker-audit/rootfs-*/ and tmp/docker-audit/leaked.env* — should be deleted once this issue is triaged.
Scope
- Dockerfiles reviewed:
smartem-decisions/Dockerfile,smartem-decisions/Dockerfile.dev,smartem-frontend/Dockerfile - docker-compose reviewed:
smartem-devtools/keycloak-mock/docker-compose.yml - Images scanned:
ghcr.io/diamondlightsource/smartem-decisions:latest,smartem-decisions:latest(local, 6 mo),smartem-decisions:keycloak-local(local, current) - CI workflows reviewed: 9 across the three repos. Only
smartem-decisions/.github/workflows/release-smartem-decisions.ymlbuilds and publishes a container image. - smartem-devtools has no Dockerfile; smartem-frontend has a Dockerfile but no CI builds it.
Methodology
- Static review of each Dockerfile against the checklist in the original audit brief (COPY hygiene, ARG-as-secret, USER directive, multi-stage hygiene, .dockerignore coverage).
docker history --no-truncanddocker inspectfor each local image; checked Env, Labels, Cmd, Entrypoint, User, and every layer's CreatedBy.- Filesystem layer scan via
docker create+docker export(per-image rootfs) +gitleaks detect --no-git. trufflehog 3.94.3 was unusable for this — itsdocker --imagesubcommand insists on registry-pulling rather than using the local daemon, and the GHCR repo is private. - CI grep for
build-arg,BUILD_ARG,NPM_TOKEN,GH_TOKEN,GITHUB_TOKEN,AWS_*,DOCKER_PASSWORD,REGISTRY_PASSWORD. - For the one leak found: registry-visibility check (
curl -sI ghcr.io/v2/.../manifests/latest), provenance check viagit show <rev>:.env.example, hash-preimage check.
Findings
Critical (contingent on one DLS judgement call)
.env and .env.example baked into ghcr.io/diamondlightsource/smartem-decisions:latest
- Image labels:
revision=26a8be4274c505ac01a2d0c7943af398ba44d0bd,version=test-v0.0.2,created=2025-06-02T11:09:29Z. - Layer 5 of the image (
COPY /app/, ~6.26 MB) contains/app/.envand/app/.env.example— byte-identical, 1466 bytes each. Config.Cmd=["cd /app && source .env && python -m smartem_decisions.model.database && uvicorn ..."]— the container actively sources.envat boot.- Audience for the leak: GHCR repo is private. Anonymous manifest fetch returns
HTTP 401; theghcr.io/tokenendpoint refuses to issue an anonymous bearer token for the pull scope. Only DLS-authorised GHCR pullers can extract the file. This significantly narrows urgency. - What is in the leaked
.env(fingerprints, not raw values):POSTGRES_USER= literalusername— placeholder, not sensitivePOSTGRES_PASSWORD= literalpassword— placeholder, not sensitiveRABBITMQ_USER= literalusername— placeholderRABBITMQ_PASSWORD= literalpassword— placeholderGRAYLOG_ROOT_PASSWORD_SHA2= 64-char SHA-256, fp"e3c…951". Verified placeholder — equal toecho -n "yourpassword" | sha256sumbyte-for-byte. No rotation needed.GRAYLOG_PASSWORD_SECRET= 96-charpwgen-shaped string, fp"bQai…Prx". Unresolved. Looks real, but I can't tell from the file alone whether it was ever deployed as a Graylog cluster secret. This is the single judgement call below.
- Provenance: the same values exist in the smartem-decisions git history at revision
26a8be4(git show 26a8be4:.env.examplereproduces them verbatim), so anyone with repo read access already sees them viagit log. The image is not the exclusive leak channel. - Current state: today's Dockerfile (production) installs from PyPI and only copies
alembic.ini+entrypoint.sh. Today'sDockerfile.devdoesCOPY . .but.dockerignoreexcludes.env*. The fresh buildsmartem-decisions:keycloak-localhas 0 leaks per gitleaks. Today'sentrypoint.shsources.envonly whenKUBERNETES_SERVICE_HOSTis unset (defensive).
Warnings
-
smartem-decisions:latestlocal (6 months old) carries/app/.secrets.baseline— detect-secrets metadata listing where secret-shaped strings live in the codebase with file/line/hash. Not raw credentials but a recipe of where to look. gitleaks flagged 2 generic-api-key hits in this file. Newer builds don't ship it. -
smartem-frontend/.dockerignoreis dangerously thin — only 4 entries (.react-router,build,node_modules,README.md). The Dockerfile doesCOPY . /appin two stages. The repo currently hasapps/smartem/.env.local. The final image only ships/app/build, but: (a) intermediate stages carry raw.env.localand would leak if registry-backed build cache is enabled; (b) Vite inlinesVITE_*into the bundle. Today'sVITE_*values are public-by-design OIDC public-client fields (VITE_KEYCLOAK_URL,VITE_KEYCLOAK_REALM,VITE_KEYCLOAK_CLIENT_ID,VITE_AUTH_ENABLED), so no current leak — but no guardrail either. The Dockerfile is not built by any CI workflow, so there is nothing currently published to remediate. -
smartem-frontendDockerfile has no CMD/ENTRYPOINT and no USER — final stage copies artefacts but never declares how to serve them; runs as root by default. Not a secrets issue, flagged for hygiene. -
smartem-decisions/Dockerfile.devfinal stage copies/app/wholesale — line 81,COPY --from=build /app/ /app/. Current.dockerignoreis comprehensive, so this is image bloat rather than a secrets leak today, but every new file at the repo root is one.dockerignore-rule-away from leak. -
Neither
smartem-decisions/DockerfilenorDockerfile.devsetsUSERin the final stage. The conditionaluseraddonly fires whengroupid != 0, andUSERis never declared. Documented as intentional ("suitable for CI/CD pipelines and local development"); flagged for awareness if the K8srunAsUseroverride is ever removed. -
smartem-devtools/.env.k8s.developmentsits unencrypted at the repo root with realDOCKER_USERNAME,POSTGRES_PASSWORD,RABBITMQ_PASSWORD. Properly gitignored, and the repo has no Dockerfile so nothing can bake it in. Flagged for awareness — if a Dockerfile is ever added here, this file plus a thin.dockerignorebecomes a future leak.
CI / build-arg review
- The only
build-argin any workflow isSMARTEM_VERSION=...insmartem-decisions/.github/workflows/release-smartem-decisions.yml:471— a non-secret version string. - All
GITHUB_TOKEN/GH_TOKENreferences are runtime-only (passed viaenv:todocker/login-actionorsoftprops/action-gh-release); none are passed as--build-arg. - No
NPM_TOKEN,AWS_*,DOCKER_PASSWORD,REGISTRY_PASSWORDpatterns.
Tooling notes
- trufflehog v3.94.3 unusable here —
docker --imageinsists on registry-pulling, ignoring the local daemon. Worked around withdocker create+docker export+ gitleaks 8.30.0. - trivy and dockle not installed; skipped per audit brief.
Recommended next actions
Ordered. Nothing applied — left as a checklist.
- One judgement call on Graylog: confirm whether
GRAYLOG_PASSWORD_SECRETfp"bQai…Prx"was ever used as a real Graylog clusterpassword_secret. If yes, rotate (accepting that all encrypted Graylog rows and active sessions invalidate, per the in-file comment); if no, the leak is fully placeholder. - Clean up the published GHCR image regardless of the above. Untag/delete
ghcr.io/diamondlightsource/smartem-decisions:latestand thetest-v0.0.2digest, then republishlatestfrom the current Dockerfile. - Decide on git-history cleanup for
smartem-decisions— only needed if the Graylog secret turns out to have been real. Either accept the historical leak (after rotation) or scrub withgit filter-repo/ BFG. - Harden
smartem-frontend/.dockerignorebefore that image is ever built and published. Deny-list the standard sensitive-file set or flip to allow-list. - Trim
smartem-decisions/Dockerfile.devfinal stage to copy only/venv/,entrypoint.sh, andalembic.inirather than the whole/app/. Defence in depth. - Delete the audit scratch files once this issue has been picked up:
rm -rf /home/vredchenko/dev/ERIC/tmp/docker-audit/rootfs-* /home/vredchenko/dev/ERIC/tmp/docker-audit/leaked.env /home/vredchenko/dev/ERIC/tmp/docker-audit/leaked.env.example. The report itself (docker-audit-2026-05-14.md) can stay or be moved into a realdocs/security/location.
Conversation-log hygiene heads-up
During provenance verification I ran git show 26a8be4:.env.example, which printed the raw GRAYLOG_PASSWORD_SECRET value (96 chars between quotes) into the audit session's tool output. The report and this issue redact to fingerprints, but the raw value lives in the Claude Code transcript for that session. If any part of that transcript gets pasted into another ticket, Slack message, PR description, or another LLM, scrub that one tool-output block first.
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from DiamondLightSource/smartem-devtools
-
security
Difficulty 4/5 3-5 days Newbie friendliness 48/100
-
Dependency Dashboard Open
Difficulty 5/5 Over a week Newbie friendliness 15/100
-
research security
Difficulty 5/5 Over a week Newbie friendliness 35/100
-
devops research smartem-agent
Difficulty 5/5 Over a week Newbie friendliness 25/100
-
enhancement smartem-devtools:claude
Difficulty 5/5 Over a week Newbie friendliness 45/100
All issues in DiamondLightSource/smartem-devtools
Similar issues
-
blocklist removal
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
MetaMask/eth-phishing-detect#296544 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
pastelsky/bundlephobia#1122 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
-
category/development priority/P2 scope/file-operations scope/testing type/enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
-
Enatega Customer and Rider app: Add-ons price is not visible to customer after order is placed. Open
Difficulty 2/5 1-3 hours Newbie friendliness 75/100