From 925be5d5e3c52876613c0a2c04908c10bcaeaed5 Mon Sep 17 00:00:00 2001 From: Dave Kowalski Date: Wed, 26 Aug 2026 13:39:16 +0000 Subject: [PATCH] docs(image-build): add pull-token trust contract Authored-By: Codex (GPT-5) --- README.md | 25 ++++++++++++++++++++----- action.yaml | 12 +++++++----- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 9b9eee7..ab1a50a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ runs a smoke test. **Does not push** — pair with `action/image-push` to publis Splitting build from push lets a PR workflow run `image-build` without push or deploy side effects while `main` runs the full build → push → deploy chain. A PR build that pulls a private base image still needs a registry token limited to -the `read:package` capability; public-base builds need no token. +the `read:package` capability; public-base builds need no token. The action logs +in as `ci-bot`, so the token must be issued to that account. ## Usage @@ -15,14 +16,28 @@ the `read:package` capability; public-base builds need no token. - uses: https://code.fritzlab.net/action/image-build@v1 with: image: code.fritzlab.net/fritzlab/chrony - token: ${{ secrets.PACKAGE_READ_TOKEN }} # read:package; omit for public bases smoke-test: docker run --rm --entrypoint /usr/sbin/chronyd $IMAGE -v ``` The image is built and tagged as `:` in the runner's local Docker daemon. Subsequent steps (e.g. `action/image-push`) can reference -the same tag. `PACKAGE_READ_TOKEN` is an example caller-chosen secret name; the -contract is the token's `read:package` capability. +the same tag. + +### Private bases in PRs + +Pass `token` only when the PR head is trusted, and limit `ci-bot` package access +to the private base images that the build requires. Never expose an +organization-wide package reader to a contributor-controlled Dockerfile: it can +pull and disclose any package that the account can read. + +```yaml +with: + token: ${{ secrets.PACKAGE_READ_TOKEN }} # caller-chosen secret name +``` + +The token must be issued to `ci-bot` with `read:package` capability. Tokens from +other accounts fail because the action's registry username is fixed. Omit the +input for public bases. ## Inputs @@ -34,7 +49,7 @@ contract is the token's `read:package` capability. | `build-args` | no | — | Multiline `KEY=VALUE` build args. Visible in `docker history` — never put secrets here. | | `secrets` | no | — | Multiline `id=VALUE` BuildKit secrets (`--secret`). For tokens the build needs (e.g. a ci-bot token to `go mod download` a private module) that must not leak into layers. Reference with `RUN --mount=type=secret,id=`. | | `smoke-test` | no | — | Shell command run after build. `$IMAGE` is set to `:`. Non-zero exit fails the action. | -| `token` | no | — | Registry token with `read:package` capability. Required to pull a private base image; omit for public bases. | +| `token` | no | — | `ci-bot` access token with `read:package` capability. Required to pull a private base image; omit for public bases. | ## Outputs diff --git a/action.yaml b/action.yaml index 585a441..d23d4be 100644 --- a/action.yaml +++ b/action.yaml @@ -37,11 +37,13 @@ inputs: default: '' token: description: | - Registry token with `read:package` capability for - `docker login code.fritzlab.net`. Required when the Dockerfile's FROM is a - PRIVATE fritzlab image (e.g. FROM code.fritzlab.net/fritzlab/base) — the - org is `limited`, so buildx can't pull it anonymously. Omit for public-base - builds (e.g. base itself = FROM debian). + ci-bot access token with `read:package` capability for + `docker login code.fritzlab.net`. The login username is fixed to `ci-bot`, + so a token issued to another account will fail. Required when the + Dockerfile's FROM is a PRIVATE fritzlab image (e.g. FROM + code.fritzlab.net/fritzlab/base) — the org is `limited`, so buildx can't + pull it anonymously. Omit for public-base builds (e.g. base itself = FROM + debian). required: false default: '' outputs: