Skip to content

fix(aws-lambda): the handler ZIP installs its runtime from a committed lockfile with scripts off - #7485

Merged
jrusso1020 merged 3 commits into
mainfrom
fix/lambda-zip-locked-runtime
Nov 10, 2026
Merged

jrusso1020 merged 3 commits into
mainfrom
fix/lambda-zip-locked-runtime

Conversation

@jrusso1020

@jrusso1020 jrusso1020 commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

What

The Lambda handler ZIP's node_modules now come from a committed lockfile (packages/aws-lambda/scripts/runtime-lock/<source>/package-lock.json, with version and integrity for every package). They are installed with npm ci --ignore-scripts. The build fails if the lock's puppeteer-core or @sparticuz/chromium version disagrees with bun.lock. The transitive tree is the lock's own (npm resolves it, so it can differ from bun.lock), and Renovate's weekly lock-file maintenance keeps it current.

Why

build-zip.ts staged the runtime with npm install --no-package-lock:

  • Only puppeteer-core and @sparticuz/chromium were pinned, and transitive dependencies floated at release time. Two builds of the same commit could ship different code.
  • readDepVersion silently fell back to the package.json range, or to latest, if bun.lock's format changed.
  • Any dependency's install script ran inside publish.yml's release job, next to its credentials.

Related work

Hardening from an internal security scan. No public issue.

How

  • scripts/runtimeLock.ts:
    • Lists which packages each Chrome source installs.
    • Reads the exact versions from bun.lock.
    • assertRuntimeLockCurrent refuses a committed lock whose top-level versions differ from bun.lock. In a published package there is no bun.lock, so the committed lock is used as is.
  • build-zip.ts:
    • Copies the lock for the chosen --source into staging and runs npm ci --ignore-scripts --omit=dev --omit=optional.
    • Deletes the lock afterwards, so the ZIP keeps its old layout.
    • readDepVersion and its latest fallback are gone.
  • scripts/lock-runtime.ts: regenerates both locks from bun.lock. Run it after bumping puppeteer-core or @sparticuz/chromium.
  • .github/renovate.json: for the two runtime-lock dirs, Renovate's direct-version bumps are off (those follow bun.lock through lock-runtime.ts), and its weekly lockFileMaintenance refreshes the transitive tree. A local renovate --platform=local --dry-run=lookup scoped to those dirs disabled the 3 direct bumps and kept only the renovate/lock-file-maintenance branch.
  • CI: lambda-arm64-image.yml now also runs its Handler ZIP job when the lock or runtimeLock.ts changes.

--ignore-scripts is safe here: a trial install of both trees found no package with a preinstall, install or postinstall script.

Test plan

  • Real build: ran bun run build, then bun run --cwd packages/aws-lambda build:zip, on this branch.

    • npm ci added 44 packages from runtime-lock/sparticuz and the ZIP built: 242.8 MiB unzipped, 123.9 MiB zipped.
    • Its 44 node_modules packages have exactly the versions today's floating npm install of the same two pins resolves, so nothing that ships changes now. It just can't drift later.
    • package-lock.json isn't in the ZIP.
  • runtimeLock.test.ts:

    • Both committed locks match bun.lock.
    • A bun.lock that bumps puppeteer-core without re-locking is reported as stale. 3 pass.
    • build-zip.test.ts still passes.
  • oxlint, oxfmt and the pre-commit fallow gate are clean.

  • Unit tests added/updated

  • Manual testing performed

  • Documentation updated (if applicable)

  • Comments follow CONTRIBUTING.md "Comments"

— Rames Jusso

🤖 Generated with Claude Code

…d lockfile with scripts off

build-zip.ts ran `npm install --no-package-lock` in the publish job, so
transitive versions floated at release time and any dependency's install
script ran beside the release credentials. The ZIP now runs `npm ci
--ignore-scripts` from runtime-lock/<source>/package-lock.json (versions
and integrity for every package), and the build fails if that lock
disagrees with bun.lock instead of falling back to a range or "latest".
`bun packages/aws-lambda/scripts/lock-runtime.ts` regenerates it.

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

@jerrai-bot-heygen jerrai-bot-heygen 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.

At 86f665a, the release ZIP staging now uses committed integrity-pinned npm locks and npm ci --ignore-scripts --omit=dev --omit=optional; the hosted Handler ZIP job actually built successfully with that path. I found no demonstrated missing native/install-script dependency in the current locks. One promised safety invariant remains false: lockMismatches compares only the direct package versions, and a transitive version already differs between the Bun and npm locks. Please clarify and separately maintain independent runtime locks, or make the claimed parity gate cover transitive drift as noted inline. The publish workflow calls build:zip for a fresh ZIP, but an existing attached ZIP retry reuses its artifact; no release run, Lambda runtime smoke or local build by me. Findings-only, not approval or release. — Jerrai

const lock = JSON.parse(readFileSync(lockPath, "utf-8")) as {
packages: Record<string, { version?: string }>;
};
return runtimePackages(source).flatMap((name) => {

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.

This parity guard compares only the two direct package versions, not the dependency closure. There is already a mismatch at this head: Bun resolves modern-tar@0.8.4 (bun.lock:1881) while the committed Sparticuz npm lock ships modern-tar@0.8.6 (package-lock.json, node_modules/modern-tar). Both top-level checks pass. If Bun later updates a transitive dependency to address a security issue without changing puppeteer-core/Chromium versions, this gate will pass and the ZIP will keep shipping the prior npm-locked dependency. The npm lock still makes release builds reproducible and integrity-pinned; please either explicitly treat/runtime-audit these as independently maintained lock trees and narrow the parity claim, or enforce actual transitive parity with a regression for transitive-only drift. — Jerrai

… Renovate refreshes the rest

The runtime lock's transitive tree is npm's own resolution, not bun.lock's (modern-tar
0.8.6 there vs 0.8.4 in bun.lock), so the parity claim now covers puppeteer-core and
@sparticuz/chromium only. Renovate's weekly lockFileMaintenance refreshes the transitive
tree; its direct-version bumps are off for these dirs because those follow bun.lock.

— Rames Jusso

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

@jerrai-bot-heygen jerrai-bot-heygen 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.

Re-review at 50cae4a: my prior transitive-parity objection is addressed by making the two lock trees explicitly independent. runtimeLock.ts and build-zip.ts now promise/check only the direct Puppeteer/Chromium versions; the npm lock owns the complete runtime transitive tree. .github/renovate.json retains scheduled lock-file maintenance and vulnerability alerts while disabling direct-version bumps under these runtime-lock directories, and the author reports a scoped Renovate dry run. This is a coherent maintenance contract for the actually observed Bun/npm divergence. Hosted exact-head Handler ZIP and support tests completed successfully; I did not run a release or Lambda runtime smoke, and did not independently execute the Renovate dry run. No new code blocker found in this narrow successor; findings-only COMMENT, not approval or merge. — Jerrai

@jerrai-bot-heygen
jerrai-bot-heygen dismissed their stale review November 10, 2026 09:02

Superseded by code-clear successor review 5839243087 at 50cae4a; independent runtime lock maintenance contract now explicit. This dismisses the prior finding, not an approval.

@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 · 0 important · 0 nits · head 50cae4a

The lockfile change holds up. npm ci --ignore-scripts is the only install in staging. Both locks carry integrity hashes for every package. No locked package has hasInstallScript. runtimeLock.test.ts catches drift from bun.lock in the required Test (support). Jerrai's transitive-parity finding is fixed.

  • blocker: the required Build check is red because of this PR (jobs 129728675553 and 129733844129, verify:packed-manifests): @hyperframes/aws-lambda packed size 79651 bytes is over its budget of 78851 by more than 1%, 12 bytes over. files: ["scripts/"] now ships both locks, the runtime-lock package.jsons, runtimeLock.ts and lock-runtime.ts. Build passed at 86f665a, and the comment-only 50cae4a tipped it over. Fix: add "!scripts/lock-runtime.ts" to files (it's a maintainer tool, like the already-excluded pin-handler-zip.ts), or raise the budget in scripts/package-size-budgets.json with a reason. Keep runtimeLock.ts and runtime-lock/, because build-zip.ts reads them.
Notes
  • Tests on windows-latest failed on engine-cli timeouts (parallelWorkerBrowsers, reject-unknown-flags, cloud/render --dry-run) in files this PR doesn't touch. Other Windows runs that hour were green, so rerun it after the fix.
  • Pre-existing, follow-up: publish.yml:158 bun install --frozen-lockfile still runs bun's default-trusted install scripts in the release job, and the ZIP's ffmpeg comes from ffmpeg-static's postinstall download with no lock hash. The body reads as if the "scripts next to release credentials" concern is fully closed.

— Genesis

…the package

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

@jerrai-bot-heygen jerrai-bot-heygen 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.

Narrow re-review at 79f53be: the only new change excludes maintainer-only scripts/lock-runtime.ts from the published @hyperframes/aws-lambda package's files list while leaving the runtime-lock data and runtimeLock.ts available to the ZIP builder. This directly addresses the earlier packed-manifest budget regression; the exact-head hosted Handler ZIP job passed, and Build had completed its build step but was still running verify:packed-manifests at review time. No new source blocker found in this one-line successor; the prior independent npm lock/update contract remains as reviewed. I did not run npm pack, Lambda runtime smoke or a release locally. Findings-only COMMENT, not approval or merge. — Jerrai

@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: 1 resolved, 0 open, 0 new · 50cae4a → 79f53be. Build (verify:packed-manifests) and every other required check are green. Nothing shipped imports lock-runtime.ts.

— Genesis

@jrusso1020
jrusso1020 added this pull request to the merge queue Nov 10, 2026
Merged via the queue into main with commit f9255e3 Nov 10, 2026
74 checks passed
@jrusso1020
jrusso1020 deleted the fix/lambda-zip-locked-runtime branch November 10, 2026 10:59
jrusso1020 added a commit that referenced this pull request Nov 10, 2026
…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 mentioned this pull request Nov 10, 2026
3 of 4 tasks
timothybrush pushed a commit to timothybrush/hyperframes that referenced this pull request Nov 10, 2026
…fy its hash before release (heygen-com#7495)

* ci(publish): build the Lambda handler zip in a read-only job and verify 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 heygen-com#7485

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

* test(ci): cover the read-only Lambda zip build job and the pin step's 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>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants