Skip to content

fix(distribute): stop bloating FLY_DISTRIBUTE_RESULTS env var (E2BIG)#70

Merged
sverdlov93 merged 5 commits into
mainfrom
fix/distribute-results-env-e2big
Jun 24, 2026
Merged

fix(distribute): stop bloating FLY_DISTRIBUTE_RESULTS env var (E2BIG)#70
sverdlov93 merged 5 commits into
mainfrom
fix/distribute-results-env-e2big

Conversation

@sverdlov93

@sverdlov93 sverdlov93 commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Mitigates #69 — the distribute action breaks all later steps with Argument list too long (E2BIG) once FLY_DISTRIBUTE_RESULTS exceeds the Linux single-env-var limit (MAX_ARG_STRLEN, 128 KB).

Root cause: distribute persisted the entire DistributeResponse — including the unbounded per-file files[] breakdown — into $GITHUB_ENV so the post step could render the job-summary "Distributed Artifacts" table. Accumulated across distribute steps, the value crossed 128 KB, after which the kernel rejects execve for every later step and post-cleanup step.

Fix: The job summary only ever reads 5 fields (package_name, package_version, package_type, public_url, download_url). Persist just those via a new DistributeSummaryEntry projection, bounding per-entry size. The full response is still exposed verbatim through the step results output, which is not subject to the env-var limit.

"Mitigates" rather than "Fixes": the projection bounds per-entry size; the variable is still appended across steps. Moving the accumulator off $GITHUB_ENV (temp file) is the root-cause follow-up tracked in #71. With slim entries the practical failure mode is gone.

Changes

  • src/types.ts — add DistributeSummaryEntry (canonical distribute action breaks all later steps with "Argument list too long" (E2BIG) — FLY_DISTRIBUTE_RESULTS exported to $GITHUB_ENV exceeds 128 KB #69 rationale lives here) and TransferSummaryResult.
  • src/distribute.ts — export toSummaryEntry; project results before exporting to $GITHUB_ENV; keep full response on the results output.
  • src/transfer.tssame projection applied to the transfer path (appendTransferResults): persist only {name,status} per file to FLY_TRANSFER_RESULTS, dropping the unused message. results[] scales with file count, so this closes the second instance of the same E2BIG class. Full results (with message) remain on the results output.
  • src/job-summary.ts — parse/render the slim types.
  • src/distribute-core.ts — narrow buildDockerPullCommand param (full responses stay compatible).
  • lib/*.js — rebuilt bundles.

Dependency bump (security, not scope creep)

  • Bumps undici 6.24.1 → 6.27.0 (transitive via @actions/http-client). Frogbot flagged that 6.24.1 carries CVE-2026-12151 (High, WebSocket DoS) and CVE-2026-9679 (Medium, cookie-parser header injection), both fixed in 6.27.0. Both are Not Applicable by contextual analysis (we don't use undici's WebSocket client or cookie parser), so the bump is remediation/hygiene with no behavior change. The rebuilt lib/*.js undici internals follow from this bump. Also synced the lockfile's stale root version (1.6.10 → 1.7.1) to match package.json.

Test plan

  • npm test — 296 tests pass, incl. regressions asserting the distribute env var contains no files/manifest.json and that the job summary renders from the slim shape (no download_count/files).
  • npm run build — tsc --noEmit typecheck + esbuild bundles regenerated.
  • npm run lint — clean.
  • CI unit-tests (incl. "Verify lib/ is compiled and up to date") green.

Follow-up

The distribute action persisted the full DistributeResponse — including the
unbounded per-file `files[]` breakdown — into $GITHUB_ENV as
FLY_DISTRIBUTE_RESULTS so the post step could render the job-summary table.
Across distribute steps this accumulated past the Linux single-env-var limit
(MAX_ARG_STRLEN, 128 KB), after which the kernel rejects execve for every
later step and post-cleanup with "Argument list too long" (E2BIG).

The job summary only reads package_name, package_version, package_type,
public_url and download_url. Persist just those fields via a new
DistributeSummaryEntry projection, bounding per-entry size. The full response
is still exposed verbatim through the step `results` output, which is not
subject to the env-var limit.

Fixes #69
Add a createJobSummary test that feeds the exact slim FLY_DISTRIBUTE_RESULTS
shape (no download_count, no files[]) and asserts the Distributed Artifacts
table — including the docker pull command — still renders, faithfully
covering the new env-var contract from #69.
@github-actions

Copy link
Copy Markdown
Contributor

🚨 Frogbot scanned this pull request and found the below:

📗 Scan Summary

  • Frogbot scanned for vulnerabilities and found 2 issues
Scan Category Status Security Issues
Software Composition Analysis ✅ Done
2 Issues Found 1 High
1 Medium
Contextual Analysis ✅ Done -
Static Application Security Testing (SAST) ✅ Done Not Found
Secrets ✅ Done -
Infrastructure as Code (IaC) ✅ Done Not Found

📦 Vulnerable Dependencies

Severity ID Contextual Analysis Direct Dependencies Impacted Dependency Fixed Versions
high (not applicable)
High
CVE-2026-12151 Not Applicable @actions/core:3.0.1
@actions/http-client:4.0.1
@actions/tool-cache:4.0.0
undici 6.24.1 [6.27.0]
[7.28.0]
[8.5.0]
medium (not applicable)
Medium
CVE-2026-9679 Not Applicable @actions/core:3.0.1
@actions/http-client:4.0.1
@actions/tool-cache:4.0.0
undici 6.24.1 [6.27.0]
[7.28.0]
[8.5.0]

🔖 Details

[ CVE-2026-12151 ] undici 6.24.1

Vulnerability Details

Jfrog Research Severity: Medium
Contextual Analysis: Not Applicable
Direct Dependencies: @actions/core:3.0.1, @actions/http-client:4.0.1, @actions/tool-cache:4.0.0
Impacted Dependency: undici:6.24.1
Fixed Versions: [6.27.0], [7.28.0], [8.5.0]
CVSS V3: 7.5

Resource exhaustion in undici's WebSocket client leads to denial of service when when connecting to untrusted servers.

[ CVE-2026-9679 ] undici 6.24.1

Vulnerability Details

Contextual Analysis: Not Applicable
Direct Dependencies: @actions/core:3.0.1, @actions/http-client:4.0.1, @actions/tool-cache:4.0.0
Impacted Dependency: undici:6.24.1
Fixed Versions: [6.27.0], [7.28.0], [8.5.0]
CVSS V3: 5.9

Impact:
undici's cookie parser in parseSetCookie percent-decodes cookie values via qsUnescape, turning encoded sequences like %0D%0A, %00, %3B, and %3D into their literal byte equivalents. RFC 6265 §5.4 does not specify any decoding and browsers do not decode either.

Applications that parse a Set-Cookie header and then forward the parsed value into a response header (proxies, middleware, SSR frameworks) become vulnerable to HTTP response header injection: an attacker-controlled upstream can inject arbitrary Set-Cookie, Location, or Cache-Control headers into the application's downstream response, enabling session fixation, open redirect, or cache poisoning.

Affected applications are those that use undici's cookie parsing (parseSetCookie, parseCookie, getSetCookies) and forward the parsed cookie value into a response header.

This was introduced in undici 7.0.0 via PR #3789.

Patches:
Upgrade to undici v6.26.0, v7.28.0 or v8.5.0.

Workarounds:
If upgrade is not immediately possible, do not forward values returned by parseSetCookie/parseCookie/getSetCookies directly into response headers; sanitize the value first to strip or reject CR, LF, NUL, ;, and = bytes.


Transitive dependency via @actions/http-client (range ^6.23.0). Also syncs
the lockfile's stale root version (1.6.10 -> 1.7.1) to match package.json.
@sverdlov93 sverdlov93 enabled auto-merge (squash) June 24, 2026 13:36
The lib/*.js bundles embed undici via @actions/http-client (esbuild). Bumping
undici requires regenerating them so the committed bundles match the lockfile;
fixes the "Verify lib/ is compiled and up to date" CI check.
@sverdlov93 sverdlov93 disabled auto-merge June 24, 2026 13:39
@sverdlov93 sverdlov93 enabled auto-merge (squash) June 24, 2026 13:40

@omerzi omerzi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Solid, correctly-targeted fix — the projection is the right call, the type-narrowing of buildDockerPullCommand is valid (it only reads package_type/public_url/package_version, all present in the slim entry), and the regression test pins the exact failure mode. A few things worth a conversation before merge; details are inline. Two cross-cutting points that don't land on changed lines:

1. Same latent bug in the transfer path (most important). appendTransferResults in src/transfer.ts:210 persists the full TransferSummaryEntry { ..., results: FlyClientResult[] } to FLY_TRANSFER_RESULTS with the identical newline-accumulate pattern. FlyClientResult is small per item, but results[] scales with file count, so a large upload/download accumulated across steps can hit the same E2BIG wall. This fix addresses one of two instances of the same class of bug — please at least flag the transfer path as the next domino, ideally apply the same projection there.

2. Root cause vs. mitigation. The projection bounds per-entry size but not total accumulation — the env var is still newline-appended across every distribute step, so with enough steps you march toward 128 KB more slowly. The truly root-cause fix is to stop using $GITHUB_ENV as the accumulator (write to a temp file or $GITHUB_STATE, which the post step can already read and which isn't subject to MAX_ARG_STRLEN). Scoping that out here is reasonable since slim entries are tiny, but "Fixes #69" slightly oversells it — consider softening to "mitigates" or filing a follow-up for the file-based accumulator.

The code change itself is clean on KISS/YAGNI — explicit field-listing over Omit/destructure-rest is the right choice since it fails safe when a new unbounded field is added to DistributeResponse.

Comment thread src/distribute.ts
Comment thread src/distribute.ts Outdated
Comment thread src/types.ts
Comment thread package-lock.json
- Apply the same slim-projection to the transfer path: appendTransferResults
  now persists only {name,status} per file to FLY_TRANSFER_RESULTS, dropping
  the unused `message`. results[] scales with file count, so this closes the
  second instance of the #69 E2BIG class of bug. Full results (with message)
  remain on the step `results` output.
- DRY: export toSummaryEntry from distribute.ts and reuse it in the test
  instead of a byte-identical local copy.
- Docs: keep the full #69 rationale on the DistributeSummaryEntry type
  (canonical home) and reduce the toSummaryEntry / appendDistributeResults
  JSDocs to one-line pointers.
@sverdlov93

Copy link
Copy Markdown
Collaborator Author

Thanks @omerzi — all points addressed in 18b11f4:

  1. Transfer path (the next domino) — applied the same projection to appendTransferResults: it now persists only {name, status} per file to FLY_TRANSFER_RESULTS, dropping the unused message. Full results (with message) still go to the step results output. This also flows through go-publish.ts, which reuses appendTransferResults.

  2. Root cause vs. mitigation — softened the PR title/description from "Fixes" to "Mitigates" and filed distribute/transfer: move results accumulator off $GITHUB_ENV to avoid unbounded growth #71 to move the accumulator off $GITHUB_ENV onto a temp file (noting $GITHUB_STATE isn't a drop-in since the reader is a different action). Inline comment on distribute.ts:78 is covered by distribute/transfer: move results accumulator off $GITHUB_ENV to avoid unbounded growth #71.

  3. DRY (toSummaryEntry vs summaryOf) — exported toSummaryEntry from distribute.ts and reuse it in the test; removed the duplicate. The docker test keeps its independent not.toContain("files"/"manifest.json") assertions as the oracle.

  4. Repeated distribute action breaks all later steps with "Argument list too long" (E2BIG) — FLY_DISTRIBUTE_RESULTS exported to $GITHUB_ENV exceeds 128 KB #69 docs — kept the full rationale on the DistributeSummaryEntry type (canonical home); reduced the toSummaryEntry and appendDistributeResults JSDocs to one-line pointers.

  5. undici / scope — called it out explicitly in the description: Frogbot flagged 6.24.1 for CVE-2026-12151 (High) and CVE-2026-9679 (Medium), both fixed in 6.27.0 (both Not Applicable by contextual analysis). Kept as deliberate security hygiene rather than incidental; lockfile version sync (1.6.10 → 1.7.1) noted too.

296 tests pass, lint clean, lib/ rebuilt.

@sverdlov93 sverdlov93 merged commit 152d6f9 into main Jun 24, 2026
7 of 8 checks passed
@sverdlov93 sverdlov93 deleted the fix/distribute-results-env-e2big branch June 24, 2026 14:50
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