Repository navigation
Description
Summary
Compiled .lock.yml workflows produced by gh aw compile reference container images by mutable tag, never by immutable digest. This includes both version-tagged and floating tags.
What we see today
A compiled lock file (e.g., review-responder.lock.yml) contains a gh-aw-manifest header listing six containers, referenced by tag only:
{
"containers": [
{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.23"},
{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.23"},
{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.23"},
{"image":"ghcr.io/github/gh-aw-mcpg:v0.2.22"},
{"image":"ghcr.io/github/github-mcp-server:v0.32.0"},
{"image":"node:lts-alpine"}
]
}The same tags appear in the generated docker run command lines and download_docker_images.sh invocation inside the lock file.
Why this matters
- Floating tags:
node:lts-alpinemoves with every Node.js LTS release. Supply-chain compromise of that tag would silently propagate to every gh-aw consumer on next workflow run. - Mutable version tags:
ghcr.io/github/gh-aw-firewall/agent:0.25.23could in principle be re-pushed to a different digest after publication. Consumers have no way to detect this. - Asymmetry with
actions-lock.json: gh-aw already pins GitHub Actions to specific SHAs inactions-lock.json(we seeactions/checkout@de0fac2e...etc. in lock files). The same discipline should apply to containers.
Request
Add digest pinning for containers in one of the following ways, in decreasing order of preference:
Option 1 — Native digest resolution at compile time
gh aw compile resolves each container tag to its current digest (via registry HEAD request / docker manifest inspect semantics) and emits image@sha256:<digest> everywhere in the lock file and manifest. A containers-lock.json (analogous to actions-lock.json) records the resolved digests so subsequent compiles on the same gh-aw version produce reproducible locks.
Option 2 — Opt-in pinning
Add a top-level containers: frontmatter directive or a gh aw pin-containers subcommand that performs digest resolution once and persists the result. Users who opt in get deterministic, auditable containers.
Option 3 — At minimum, stop using floating tags
Replace node:lts-alpine with a specific version tag (e.g., node:22-alpine3.20 or node:22.11.0-alpine3.20). This does not fix mutable-tag risk but removes the worst case (an LTS flip silently pulling a major-version change).
Threat model context
Consumers running gh-aw workflows on GitHub-hosted ephemeral runners have limited blast radius per run, but:
- Secrets (the workflow's
GITHUB_TOKEN, PATs, OIDC tokens) are available inside the containers /var/run/docker.sockis mounted into the MCP gateway (see-v /var/run/docker.sock:/var/run/docker.sockin the compileddocker runcommand), giving a compromised container full daemon control on the runner- Any repo with write scopes on its workflow token would allow code push / release manipulation
Related prior discussion
I searched digest and pin in this repo's issues and saw several closed as "not planned". If there is an existing decision record explaining the tradeoff, a pointer in the frontmatter docs would help downstream security audits. (We tried to find one and could not.)
Our plan
We are documenting this gap in our internal security audit and accepting the residual risk for now, pending upstream direction. Happy to contribute a PR if one of the options above is acceptable.
Activity
pelikhan commentedon Apr 22, 2026
@lpcox does awf support these?
lpcox commentedon Apr 22, 2026
@pelikhan yes, both mcpg and firewall support hash-pinned images
pelikhan commentedon Apr 22, 2026
@copilot hash pinned images should be used for containers. Builtin images MUST be using hash pinned images.
Update community contributions (11 new, 2026-04-22)