ci(release): version with release-please and publish a container image - #529
ci(release): version with release-please and publish a container image#529svalleru wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline findings, I checked whether workflow_dispatch's ref and tag inputs could be used to publish a tag that doesn't actually match the ref being built — they are independent free-text fields with no cross-check against real git tags, but triggering it already requires repo write/dispatch access, so this is an operational footgun rather than a new attack surface and doesn't change the outcome here.
Extended reasoning...
I reproduced the ruled-out candidate by reading .github/workflows/publish.yml: in the "Resolve the tag and the commit" step, tag comes from inputs.tag on workflow_dispatch while the checkout step uses inputs.ref || github.ref — these are two separate free-text inputs with no validation that a git tag named tag exists or points at the commit resolved from ref. This confirms the previously ruled-out item is real but bounded: workflow_dispatch on this workflow can only be triggered by someone with write access to the repository (GitHub restricts manual dispatch to collaborators with write permission), so it does not introduce a fork-reachable or privilege-escalation path beyond what a trusted maintainer could already do by hand — it's a usability/footgun issue (a maintainer typo could publish v1.2.3 built from an unrelated ref) rather than a security hole, consistent with why it was ruled out rather than confirmed. Since two other findings (the tag-existence guard treating any gcloud failure as "free", and mutable @ v3 action tags for the GCP auth chain) are already confirmed and will be posted inline, and this additional item is genuinely something I verified beyond those, the narrow exception for a brief defer note applies.
| - name: Set up gcloud | ||
| uses: google-github-actions/setup-gcloud@v3 | ||
|
|
||
| - name: Configure Docker for Artifact Registry | ||
| run: gcloud auth configure-docker us-docker.pkg.dev --quiet | ||
|
|
||
| - name: Refuse to overwrite an existing tag | ||
| env: | ||
| TAG: ${{ steps.meta.outputs.tag }} | ||
| run: | | ||
| set -euo pipefail |
There was a problem hiding this comment.
🟡 (optional) The tag-existence guard swallows every gcloud artifacts docker tags list failure (auth/IAM error, transient API error, quota, wrong project) as "tag is free", not just the NOT_FOUND-before-first-publish case the comment describes, so a misconfigured or under-permissioned publish service account silently disables the overwrite check on every run instead of failing loudly. Fix: only treat an actual NOT_FOUND/repository-missing error as "free"; any other list failure should fail the step instead of falling through to existing="".
Extended reasoning...
Step 'Refuse to overwrite an existing tag' does if ! existing="$(gcloud artifacts docker tags list ...)"; then existing=""; fi — any nonzero exit from gcloud (not just the documented pre-first-publish NOT_FOUND) is coerced to an empty tag list, so the subsequent grep -Fxq never matches and the guard never fires. If the publish service account is granted registry write but not artifactregistry.reader, or a transient API error occurs, this happens on every run, not just before the first image — the workflow proceeds to build/push believing no prior tag exists, losing the intended early, readable failure the PR's own comment claims this step provides.
Verification: nit. The mechanism the candidate cites is real and reachable. In publish.yml lines 118-121, if ! existing="$(gcloud artifacts docker tags list ... 2>&1)"; then ... existing=""; fi coerces ANY nonzero gcloud exit (missing artifactregistry.reader, transient API error, quota, wrong project) into an empty tag list, so the grep -Fxq -- "${TAG}" <<<"${existing}" on line 122 never matches…
| } >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Authenticate to Google Cloud | ||
| uses: google-github-actions/auth@v3 |
There was a problem hiding this comment.
🟡 (optional) google-github-actions/auth@ v3 (and setup-gcloud@ v3, docker/setup-buildx-action@ v3, plus release-please.yml's create-github-app-token@ v2 and googleapis/release-please-action@ v4) are pinned to mutable major-version tags, not immutable commit SHAs. This diff is the first workflow in the repo to pair that convention with real cloud credentials: id-token:write plus GCP workload identity is new to CI here, so a maintainer of any of those actions repointing the tag (compromise or mistake) now runs with write access to the registry, whereas today no workflow in this repo exposes cloud credentials to an unpinned action. …
Extended reasoning...
…Fix: pin every third-party action used in publish.yml and release-please.yml to a full commit SHA (with a version comment), which covers all six sites (publish.yml:64,91,97,128; release-please.yml:38,44).
A tag push triggers publish.yml, which calls google-github-actions/auth@ v3 to exchange OIDC for a short-lived GCP access token scoped to the workload-identity-bound service account, then uses that token to push to Artifact Registry. If the upstream v3 tag is ever repointed to malicious code (supply-chain compromise or accidental force-push by the action's maintainers), the next tag push on this repo executes that code with id-token:write and the publish service account's registry-push credentials, and separately release-please.yml exchanges a GitHub App private key for a token with contents:write/pull-requests:write via an unpinned create-github-app-token@ v2 and hands it to an unpinned release-please-action@ v4. No other workflow in this repo currently exposes credentials this sensitive to a mutable action reference, so this PR is what newly creates that blast radius, not a…
Verification: nit (supply-chain hardening). The code is exactly as described: publish.yml pins google-github-actions/auth@ v3 (line 91), setup-gcloud@ v3 (97), docker/setup-buildx-action@ v3 (128), actions/checkout@ v4 (64); release-please.yml pins create-github-app-token@ v2 (38) and googleapis/release-please-action@ v4 (44) — all mutable major-version tags, no commit SHAs. And the credential surface is…
Summary
vX.Y.Z; aPublishworkflow builds that commit'sDockerfileforlinux/amd64and pushesus-docker.pkg.dev/e2b-artifacts/dashboard/dashboard:vX.Y.Zthrough workloadidentity.
only identity this repository has today. The
E2B Embed package
pins the dashboard image the way it pins the others.
A tag pushed with the default
GITHUB_TOKENdoes not triggeron: push, so amissing App must fail loudly rather than produce a release that never
publishes.
Publishholdsid-token: writebut runs only on av*tag push and manual dispatch, andtags are immutable. Attestations are off because Artifact Registry rejects
buildx's default manifest lists.
bump-minor-pre-major, so the first release isv0.2.0. No release PR opens until the first
featorfixafter thebootstrap commit.
Verification
(
include-component-in-tag: falseand the workflow's^v[0-9]+\.[0-9]+\.[0-9]+$), the registry path and platform, the identityvariables, and the absence of
pull_request,pull_request_targetandworkflow_run.actionlintclean on both workflows; no existing workflow fires on tagpushes, so a release starts exactly one run.
Dockerfilewith--loadinstead of--push: a 111 MBlinux/amd64image with the expectedlabels.
and whether the App grants
contents: write.Merge order / notes
Dockerfile, andthe runtime-config PR before the first tag, or the image bakes
NEXT_PUBLIC_E2B_DOMAIN=unset.invalid. All three PRs editREADME.mdandpackage.jsonin disjoint regions.GCP_WORKLOAD_IDENTITY_PROVIDERandGCP_SERVICE_ACCOUNTas repositoryvariables before the first tag; until then the first step fails with a FIX
line naming both.
Publishnow does — adjust it in whichever of the two merges second.