Skip to content

ci(publish): build the Lambda handler zip in a read-only job and verify its hash before release - #7495

Merged
jrusso1020 merged 2 commits into
mainfrom
fix/lambda-zip-readonly-build
Nov 10, 2026
Merged

jrusso1020 merged 2 commits into
mainfrom
fix/lambda-zip-readonly-build

Conversation

@jrusso1020

@jrusso1020 jrusso1020 commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

What

The Lambda handler zip is now built in its own job, lambda-handler-zip, with a read-only token, no secrets and no stored git credentials. The publish job checks the zip's sha256 against the build job's output before it pins the zip and attaches it to the release.

Why

Building the zip runs dependency install and bundling code. Before this change it ran inside the publish job, which has contents: write and id-token: write. The checkout there also leaves the job token in .git/config, so the zip build could read that token even without GH_TOKEN in the step's env. #7485 locked the zip's runtime install. This PR moves the zip's own build (build-zip.ts and that runtime install) out of the publish job.

Scope limit: the publish job still runs bun install and bun run build for the npm packages, with those same permissions and the stored checkout token. That's unchanged from main and not addressed here.

Related work

Refs #7485

How

  • New job lambda-handler-zip:
    • permissions: contents: read, no environment, no secrets, and the checkout uses persist-credentials: false.
    • It checks out the same immutable release SHA as the other jobs and runs the same SHA check.
    • It runs bun install --frozen-lockfile, bun run build and build:zip. The runtime lock from fix(aws-lambda): the handler ZIP installs its runtime from a committed lockfile with scripts off #7485 still installs with npm ci --ignore-scripts.
    • It uploads handler.zip as an artifact and exposes its sha256 as a job output.
  • publish job:
    • It now needs the new job and downloads the artifact.
    • The old "Build Lambda handler zip" step becomes "Pin Lambda handler zip". It fails if the sha256 is empty or doesn't match, then moves the zip into place and runs pin-handler-zip.ts as before.
    • The gh release download retry path stays in this job. A rebuilt zip would not be byte-identical, so a retried run must still pin the zip that's already on the release.
  • Nothing after the pin step changes: the video image build, release creation, the attach steps and npm publish.

Test plan

  • scripts/publish-workflow.test.mjs (7/7) checks that the build job has read-only permissions, has no secrets and doesn't persist credentials, and that the publish job needs it. It also runs the pin step for four cases: retry, matching hash, mismatched hash and empty hash. If the hash check is removed, the test fails.
  • actionlint on the new publish.yml reports no errors; main's version also reports none.
  • I extracted the new pin step's script and ran it against a sandbox copy of packages/aws-lambda and examples/aws-lambda:
    • matching sha256: exits 0, and prebuilt/handler-zip.json and the .sha256 file pin the artifact's hash;
    • wrong sha256: exits 1 with ::error::, nothing pinned;
    • empty sha256 output: exits 1, nothing pinned;
    • release asset already attached (retry): attached=true, and the pin matches the release asset, not the artifact.
  • Not exercised: a real release run. This workflow has no workflow_dispatch or dry-run path; it only fires on a merged release/v* PR or a prerelease tag push. I didn't cut a release to test it, so whoever runs the next release should watch the Lambda handler zip and Publish jobs.
Claude Opus 5.5

Things I checked before splitting the job

  • pin-handler-zip.ts moves packages/aws-lambda/dist/handler.zip only if that file exists. The publish job's bun run build (build.mjs) does not create a zip, so the verified artifact at dist/hyperframes-lambda-handler.zip is what gets hashed and pinned.
  • build-zip.ts resolves workspace packages from source through esbuild aliases and stages files from the build output. No package build script reads git history, so the build job's shallow checkout is enough.
  • The build job doesn't set up pnpm or an npm registry. pnpm is used only by pnpm publish in the publish job, and the runtime lock uses npm ci, which comes with setup-node.
  • The publish job uses bun install --frozen-lockfile and bun run build as before. Only the zip build moved out.

— Rames Jusso

🤖 Generated with Claude Code

…fy its hash before release

The handler zip build runs dependency install and bundling code. It moves out of the
publish job into its own job with `contents: read`, no secrets and no persisted git
credentials. The publish job downloads the artifact, checks its sha256 against the
build job's output, then pins and attaches it as before. A retried run still reuses
the zip already on the release.

Refs #7485

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES · 1 blocker · 1 important · 0 nits · head 9744856

Required CI / Test is red on this head: Test (support) fails 2 tests in scripts/publish-workflow.test.mjs. Details ↓ inline.

Scope

Read publish.yml end to end, plus pin-handler-zip.ts, build-zip.ts, root build, and the CI log for run 42805033787. The root bun run build does not create packages/aws-lambda/dist/handler.zip, so pinHandlerZip cannot rename an unverified zip over the verified one. No prior reviews on this PR.

— Genesis

Comment thread .github/workflows/publish.yml Resolved
Comment thread .github/workflows/publish.yml Resolved
… hash check

The publish workflow test still looked for the old "Build Lambda handler zip"
step. It now checks the build job's read-only permissions and the publish job's
dependency on it, and runs the pin step for the retry, matching-hash,
mismatched-hash and empty-hash cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE — 2 resolved, 0 open, 0 new · 9744856 → 2493438

The workflow is unchanged since 9744856. The 7 tests pass locally, and removing the hash check fails test 7. On this head, run 42830547446 failed CLI smoke with an npm ETARGET for @puppeteer/browsers@3.2.7, which this PR doesn't touch. The newer run, 42830585502, is fully green.

— Genesis

@jrusso1020
jrusso1020 added this pull request to the merge queue Nov 10, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 10, 2026
@jrusso1020
jrusso1020 added this pull request to the merge queue Nov 10, 2026
Merged via the queue into main with commit 265ec66 Nov 10, 2026
114 of 117 checks passed
@jrusso1020
jrusso1020 deleted the fix/lambda-zip-readonly-build branch November 10, 2026 14:59
@miguel-heygen miguel-heygen mentioned this pull request Nov 10, 2026
3 of 4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant