Skip to content

feat: handle client payload oversized parsing - #566

Open
yeelali14 wants to merge 6 commits into
developfrom
LINBEE-27157
Open

feat: handle client payload oversized parsing#566
yeelali14 wants to merge 6 commits into
developfrom
LINBEE-27157

Conversation

@yeelali14

@yeelali14 yeelali14 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

✨ PR Description

Purpose: Implement a robust payload resolution step to handle plain, gzipped, or server-stashed oversized client payloads.

Main changes:

  • Added Resolve payload fields step using github-script to parse and inflate payloads
  • Implemented security checks for stashed payload origins and decompression bomb protection
  • Replaced inline fromJSON expressions with steps.payload-fields outputs for downstream steps

Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Description using Guidelines Learn how

@orca-security-us orca-security-us Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Orca Security Scan Summary

Status Check Issues by priority
Passed Passed Infrastructure as Code high 0   medium 0   low 0   info 0 View in Orca
Passed Passed OSS Licenses high 0   medium 0   low 0   info 0 View in Orca
Passed Passed SAST high 0   medium 0   low 0   info 0 View in Orca
Passed Passed Secrets high 0   medium 0   low 0   info 0 View in Orca
Passed Passed Vulnerabilities high 0   medium 0   low 0   info 0 View in Orca

@linearb linearb Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✨ PR Review

The PR correctly refactors payload resolution into a dedicated pre-step to handle plain, compressed, and reference-based payloads. The logic is well-structured, but there is a critical security issue with token handling and a notable robustness gap in the gzip decompression path.

2 issues detected:

🔒 Security - A GitHub token is emitted as an unmasked step output, making it readable in the Actions UI and log artifacts. 🛠️

Details: payload.githubToken is written to a step output via core.setOutput('github_token', ...). Step outputs in GitHub Actions are stored in plain text and are visible in the workflow run summary and logs. Any subsequent step that references steps.payload-fields.outputs.github_token will also echo the raw token value into the log if the expression is ever printed or an error occurs. The token must be masked before it is used anywhere.

File: action.yml (89-89)

🛠️ A suggested code correction is included in the review comments.

🐞 Bug - A zlib decompression error inside `inflate` propagates as an opaque exception with no useful context about what failed. 🛠️

Details: gunzipSync(buffer) in the inflate helper will throw a native zlib error if the buffer contains the correct gzip magic bytes (0x1f 0x8b) but the rest of the compressed data is malformed or truncated. This exception is not caught inside inflate, so it surfaces as an unhandled throw inside resolve, which the outer try/catch catches with a generic "Failed resolving client payload" message. The error message gives no indication that decompression failed, making debugging harder and also means a partially-corrupted payload is silently treated as a fatal error rather than giving a clear diagnostic.

File: action.yml (55-55)

🛠️ A suggested code correction is included in the review comments.

Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Review using Guidelines Learn how

Comment thread action.yml Outdated
Comment thread action.yml Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds oversized client payload handling for plain, compressed, and server-stashed payloads.

Changes:

  • Resolves payload fields before dependent steps.
  • Uses resolved outputs for checkout tokens, URLs, repositories, and refs.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

yeelali14 and others added 2 commits August 10, 2026 17:34
client_payload carries githubToken, and every step that takes the payload as env
logs it in plaintext in its own env dump. Registering it as a secret from the
first step that parses the payload masks those occurrences.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@linearb linearb Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✨ PR Review

The PR correctly handles the three client_payload forms (plain, compressed, referenced) and replaces fragile inline fromJSON(fromJSON(...)) expressions with a dedicated resolution step. The core.setSecret call for the github token is a good addition. A few security and reliability issues remain.

3 issues detected:

🔒 Security - `reference.resolverToken` is used in a network request without being registered as a secret, leaving it exposed in logs. 🛠️

Details: reference.resolverToken is passed directly in the Authorization header at line 68 but is never registered with core.setSecret. If the runner echoes the authorization header (e.g., on a redirect, a curl trace, or an error that prints request details), the token will appear in plain text in the workflow logs.

File: action.yml (67-70)

🛠️ A suggested code correction is included in the review comments.

🐞 Bug - `process.exit(1)` bypasses the `actions/github-script` teardown and output-flushing machinery. 🛠️

Details: process.exit(1) at line 102 terminates the Node.js process immediately. Inside actions/github-script, this skips the action's internal teardown (output flushing, annotation writing) and may leave the step in an ambiguous state on some runner versions. The idiomatic way to signal failure from within a github-script block is core.setFailed(message), which marks the step as failed, writes the error annotation, and lets the runner finish cleanly.

File: action.yml (101-102)

🛠️ A suggested code correction is included in the review comments.

🔒 Security - An externally-controlled URL is fetched without origin validation, creating a server-side request forgery vector from the Actions runner.

Details: reference.payloadUrl is fetched at line 67 without any allowlist or origin validation. The client_payload input is supplied by the external event that triggered the workflow. A malicious dispatcher could set payloadUrl to an internal network address (e.g., instance metadata endpoint http://169.254.169.254/...) and the Actions runner would make that request, potentially leaking cloud-provider credentials or internal service responses.

File: action.yml (67-67)

Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Review using Guidelines Learn how

Comment thread action.yml
Comment thread action.yml Outdated
Comment thread action.yml Outdated
…r gzip error

- core.setSecret on reference.resolverToken before it goes into the Authorization
  header, so it is masked like the installation token
- core.setFailed instead of core.error + process.exit(1), so the step fails through
  the runner's normal path without risking truncated log output
- gunzipSync wrapped so a corrupt blob reports "gzip decompression failed: ..."
  rather than a bare zlib message
- shortened the step comment per review

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@linearb linearb Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✨ PR Review

The PR cleanly resolves all five previously-reported issues (token masking, decompression error handling, resolver-token secret registration, process.exit replacement, and the SSRF-enabling URL fetch). No new critical issues were introduced. One previously-raised SSRF concern (issue_5) remains unaddressed.

1 issues detected:

🔒 Security - Untrusted `payloadUrl` from the external event payload is fetched without origin validation, enabling SSRF.

Details: reference.payloadUrl is fetched without any allowlist or origin validation. A malicious dispatcher can set payloadUrl to an internal-network address (e.g., http://169.254.169.254/latest/meta-data/) and the Actions runner will make that request, potentially leaking cloud-provider credentials or internal service responses.

File: action.yml (69-69)

Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Review using Guidelines Learn how

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

action.yml:76

  • response.text() buffers the entire remote response, while the timeout only limits duration and does not bound bytes received. A referenced endpoint can return a very large or fast chunked response and exhaust runner memory. Read the body with an explicit byte cap (and reject oversized Content-Length early when present) before parsing or decompressing it.
                const body = await response.text();

Comment thread action.yml Outdated
gzip is asymmetric: a ~66KB base64 input inflates to 50MB and can exhaust the
runner before parsing. gunzipSync now runs with maxOutputLength, which covers both
the compressed input and the body fetched for a stashed payload, and reports a
clear message instead of a bare zlib error when the cap trips.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@linearb linearb Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✨ PR Review

The PR refactors client payload handling to support plain JSON, base64(gzip), and server-stashed (oversized) references, replacing fragile inline fromJSON(fromJSON(...)) YAML expressions with a dedicated resolution step. Security mitigations (secret masking, decompression bomb guard, fetch timeout) are solid. One notable logic flaw exists in the resolve function's early-exit path for the oversized reference check.

1 issues detected:

🐞 Bug - A substring match on raw input triggers a JSON.parse call that can throw for valid non-JSON payloads (e.g., base64-gzip blobs), bypassing the inflate path and failing the step.

Details: The raw.includes(OVERSIZED_PAYLOAD_REFERENCE) substring check at line 72 is performed before any parsing. If a legitimate payload (plain JSON or base64-gzip) happens to contain the string 'oversized-payload-reference' as part of a field value (e.g., in a commit message or branch name), the code will call parsePayload(raw) on it. If raw is a base64-gzip blob (not valid JSON), JSON.parse will throw, and the exception will propagate up through resolve to the outer catch, causing core.setFailed to be called. The inflate and plain-JSON paths are never tried, so the step fails on a valid payload.

File: action.yml (72-86)

Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Review using Guidelines Learn how

reference.payloadUrl came straight from client_payload, so a crafted dispatch could
aim the runner's fetch at any address - an SSRF primitive that matters most on
self-hosted runners. The stash is served by the same host as the resolver, so the
origin is now required to match inputs.resolver_url, which keeps prod, dev and
self-hosted deployments working without hardcoding a domain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@linearb linearb Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✨ PR Review

The PR properly addresses the oversized-payload reference flow with origin validation, secret masking, and improved error handling. Several previous issues (SSRF, resolverToken masking, process.exit, inflate error clarity) are now resolved. Two previously reported issues remain open (issue_1, issue_7), and one new bug is introduced around invalid RESOLVER_URL_ARG handling.

1 issues detected:

🐞 Bug - `new URL('')` throws a TypeError, so a missing or empty `resolver_url` input causes every reference-type payload to fail with an unintelligible error. 🛠️

Details: When inputs.resolver_url is not supplied (or is an empty/malformed string), process.env.RESOLVER_URL_ARG || '' evaluates to '', and new URL('') throws TypeError: Invalid URL. This unhandled throw propagates up through resolve and is caught by the outer catch, producing a confusing "Failed resolving client payload: TypeError: Invalid URL" message with no indication that the resolver URL was never configured. Any payload that contains the string oversized-payload-reference (which triggers this code path) will silently fail the step with an opaque error.

File: action.yml (79-79)

🛠️ A suggested code correction is included in the review comments.

Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Review using Guidelines Learn how

Comment thread action.yml

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (2)

action.yml:56

  • Buffer.from(..., 'base64') silently ignores non-base64 characters, so some valid plain JSON is misclassified as gzip. For example, a payload whose first key begins with H4sI decodes to the gzip magic bytes after the JSON punctuation is discarded, then fails in gunzipSync instead of being parsed as plain JSON. Validate that the whole input is base64 before checking its decoded magic bytes.
            const buffer = Buffer.from(value, 'base64');
            if (buffer.length < 2 || buffer[0] !== 0x1f || buffer[1] !== 0x8b) {

action.yml:114

  • This output is set after githubToken is registered with core.setSecret. GitHub Actions does not allow a masked value to be set as an output, so steps.payload-fields.outputs.github_token can be omitted and the checkout falls back to github.token; that breaks private/cross-repository checkouts requiring the installation token. Keep the value masked but pass it to later steps through a subsequent-step environment variable rather than an output.
            core.setOutput('github_token', githubToken);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants