security(compose): pin docker-compose images by digest #185
Labels
No labels
agent:implement
agent:ready
backend
bug
cannot-reproduce
confirmed
desktop
docs
documentation
duplicate
enhancement
feature
frontend
good first issue
help wanted
high-risk
in-review
invalid
mobile
needs-triage
P0-critical
P1-high
P2-medium
P3-low
qa
QA
question
ready
report
responsive
security
security-escalate
smart-contract
solana
tablet
test-pass
ux
wallet-issue
wallet:keplr
wallet:metamask
wallet:station
wallet:walletconnect
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-bridge-monorepo#185
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
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?
Summary
Repo-root
docker-compose.yml(Dev/QA local stack only) still pulls mutable tags for Foundry Anvil and LocalTerra. Onmainas of 2026-09-12:anvilandanvil1→ghcr.io/foundry-rs/foundry:latestlocalterraandterrad-cli→ghcr.io/plasticdigits/localterra-cl8y:latest:latestis retaggable. A GHCR push or account takeover can change whatdocker compose upruns without a git diff. Foundry is the local EVM compiler/runtime used for Anvil and forges; LocalTerra is the local Terra Classic node andterradCLI image. Poisoned images can change chain behavior, precompiles, or CLI binaries used by QA and E2E.This is not a production funds path. The compose file publishes only to
127.0.0.1. Operator/canceler production images are a different pipeline. Still a supply-chain defect in the checked-in Dev/QA stack, rated high by internal review (FE-H3).Other services in the same file use version tags without digests (
solanalabs/solana:v1.18.26,postgres:16-alpine,prom/prometheus:v2.48.0,grafana/grafana:10.2.2). Those are not:latest, but they also move if the tag is retargeted. Bundle them so a “compose images must be digest-pinned” CI check is honest.This is not #172 (Woodpecker
.woodpecker.yamlalready digest-pins CI runner images). This is not #67 (LocalTerra host port remap). Do not reopen those for this.Internal review id: FE-H3 (high). Still in source 2026-09-12.
Bundle (same ticket, do not split):
image:in this repo’s Dev/QA compose files by digest (name:tag@sha256:<64 hex>). Required for Foundry andlocalterra-cl8y; include Solana, Postgres, Prometheus, and Grafana so the check does not have exceptions.image:line lacks a digest pin (comments and blank lines ignored).e2e,monitoring), and port env remaps unchanged.Supply-chain / deploy-adjacent. No community autoland. Do not add
ready.Impact (today vs hypothetical)
Funds are not at risk today on the live bridge operator, vaults, or on-chain programs from this file. Compose is Dev/QA. Ports bind to loopback. This is not a drain path.
Hypothetical, sticky until pinned:
:latest(or a floating version tag). Nextdocker compose pull/upon a workstation or QA host runs a different Anvil, terrad, validator, or Postgres than the git revision implies.cast, forge) used for local EVM deploy and E2E. Tests can go green on a lie.terradused by E2E. That is QA integrity, not mainnet mint.Today vs hypothetical: the defect is in source today (unpinned tags). Exploitation needs registry control or a malicious republish of those tags, not a permissionless mainnet call. Treat as High for autoland and community because it is a live supply-chain finding, not because vault inventory is reachable from Compose.
Do not publish a GHCR tag-swap recipe or a working malicious image.
Invariants
image:value in this repo’s Dev/QA compose files isrepository:tag@sha256:<64 lowercase hex>. Tag-only (:latest,:v1.18.26,:16-alpine) is forbidden.anvil,anvil1) share one digest-pinned Foundry image unless a documented reason requires two.localterraandterrad-clishare one digest-pinnedlocalterra-cl8yimage (they already comment that they use the same image).image:is added without a digest. A comment must not satisfy the check.Constraints / guardrails
docker buildx imagetools inspect/ equivalent). Do not invent hashes in review comments. Prefer keeping the current intended tag name and appending@sha256:…so humans still seefoundry:latestvs a surprise major bump; the digest is the pin.:latesthas moved since the last known-good QA, pin the digest that matches current working QA (same Anvil flags and LocalTerra chain-idlocalterra), and record the tag+digest in the PR description. Do not silently switch major versions.scripts/check_compose_image_digests.sh) that parsesimage:lines indocker-compose.ymland anydocker-compose*.yml/compose*.ymlshipped in-repo. Require@sha256:[0-9a-f]{64}. Fail on:latesteven if a digest is also present in a comment. Do not shell-out todocker pullin the check (digest presence is the gate; content trust is the pin)..woodpecker.yaml) and into.github/workflows/test.ymlif that workflow is still the merge gate for contributors without Woodpecker.ready.Relevant files
docker-compose.yml.woodpecker.yaml.github/workflows/test.ymlscripts/check_compose_image_digests.sh(new)image:linesscripts/qa/README.md(if it names image tags)Recommended direction
image:indocker-compose.yml, append@sha256:<digest>of the image currently used for QA. Same digest foranvil/anvil1; same digest forlocalterra/terrad-cli.scripts/check_compose_image_digests.sh: scan compose YAML forimage:keys; allow onlyname:tag@sha256:<64 hex>; ignore#comments; fail with the offending line.scripts/qa/README.md: bumping a compose image means updating the digest, not editing:latest.Acceptance criteria
anvilandanvil1image:values include@sha256:and do not rely on a floating Foundry tag alone.localterraandterrad-cliimage:values include@sha256:and do not rely onlocalterra-cl8y:latestalone.image:lacks@sha256:<64 hex>(prove with a unit fixture or a documented negative grep in the script’s tests).e2e/monitoringprofiles, and Solana--cloneflags unchanged.Test plan (functional paths)
docker-compose.ymlimage:linesrepo:tag@sha256:<64 hex>foundry:latestand no digestanvilvsanvil1localterravsterrad-clidocker pullthose images as the gateTest plan (attack, hack, and abuse)
Non-exploitative. File/CI fixtures only. Do not use as a registry-compromise recipe.
:lateston Foundry or LocalTerraimage: repo@sha256:deadbeef(wrong length)image:docker-compose*.ymlwith unpinned image (if such a file exists)Verification criteria
image:.*:latestwithout@sha256:on Compose files.image:matches@sha256:[0-9a-f]{64}.docker compose upAnvil + LocalTerra healthy) is enough to confirm the pinned digests still boot; not a funds-path test.Out of scope
uses:SHAs (different surface).First-pass model recommendation
Recommendation: grok-high
Rationale: Security class (supply-chain image pin, internal FE-H3 high) and deploy-adjacent work. Composer is disallowed: High/security, founder-required deploy/keys-adjacent pinning, not a low-risk first pass. Expected files are
docker-compose.yml, a new digest-check script, and a Woodpecker/GHA step — file count does not establish safety. Wrong digest can break all local E2E or silently accept a retagged Anvil/LocalTerra. Verify with the fail-closed script and composeimage:greps, not by exercising a hostile registry tag.