Skip to content

fix: read the wrapped compressed-payload envelope - #570

Closed
yeelali14 wants to merge 3 commits into
developfrom
fix/wrapped-compressed-envelope
Closed

fix: read the wrapped compressed-payload envelope#570
yeelali14 wants to merge 3 commits into
developfrom
fix/wrapped-compressed-envelope

Conversation

@yeelali14

@yeelali14 yeelali14 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Problem

The GitHub trigger is changing the wire format: compressed payloads stop being a bare base64 string and get wrapped in a double-encoded envelope, so that run-name — evaluated before any job or step exists, and therefore unreachable from this action — parses instead of failing the whole workflow at startup.

Live @v2 (2.0.258) mishandles that envelope, and does so silently. We detect compression by sniffing gzip magic bytes on the raw input, so the wrapped form misses the sniff, falls through to the raw branch, and treats the envelope itself as the payload. Measured against the proposed format:

github_token = ""      → checkout falls back to github.token (breaks private/cross-repo)
url          = ""      → git remote add upstream '' fails
has_cm_repo  = "false" → cm repo never checked out → no rules evaluated
                         no setFailed, exit 0

gitStream would appear to run and evaluate nothing.

Change

Parse first, then switch on the value of type.

Matching on the value rather than the presence of type matters: a raw payload can legitimately carry its own type, because Bitbucket builds it from the webhook context. Such a payload must fall through to the raw branch — there's a test for exactly that.

Both compressed forms are permanent, not a migration step. Bitbucket has no run-name to protect and keeps sending the bare base64(gzip) form indefinitely, so neither branch can ever be retired. Supporting both also makes the rollout order-independent: customers pick up @v2 on their own schedule, so the action and the trigger cannot be deployed in a guaranteed order.

A compressed-payload envelope whose data isn't gzip now fails loudly instead of silently resolving to empty fields.

The wrapped compressed tier reports mode=compressed-envelope rather than mode=compressed, so logs distinguish which trigger version produced a run while both formats are in flight.

Verification

All seven wire shapes, plus the malformed envelope:

shape mode
raw single-encoded plain
raw double-encoded plain
raw carrying its own type: 'push' plain
bare base64(gzip) — Bitbucket, permanent compressed
wrapped compressed-payload — GitHub, new compressed-envelope
bare reference — today's format reference
wrapped reference — GitHub, new reference
compressed-payload with non-gzip data fails loudly

19 tests pass (15 before, +4), lint and prettier clean.

Coordination

Format agreed with the trigger side (gitstream-sls-pipeline). Key names are unchanged from #566oversized-payload-reference / payloadUrl / resolverToken, plus compressed-payload / data. data is base64(gzip(JSON.stringify(payload))).

The same change is on the two sibling PRs: #568 (v1) and #569 (v2-lite). The trigger side is holding its merge until all three refs can read the new format.

🤖 Generated with Claude Code

✨ PR Description

Purpose: Support wrapped compressed-payload envelopes and improve payload resolution logic to handle multiple compression formats and envelope types.

Main changes:

  • Added tryParsePayload() function and COMPRESSED_PAYLOAD constant to handle wrapped compressed envelopes with type detection
  • Replaced readStashReference() with unified envelope parsing in resolvePayload() supporting compressed-payload, reference, and plain modes
  • Enhanced stashUrl() with explicit URL validation error handling and comprehensive documentation for origin security

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

The GitHub trigger is wrapping compressed payloads in a double-encoded
envelope so that run-name - evaluated before any step exists, and so
unreachable from this action - still parses instead of failing the whole
workflow at startup.

We detected compression by sniffing gzip magic bytes on the raw input, so
the wrapped form missed the sniff, fell through to the raw branch and
treated the envelope itself as the payload. That failed silently: empty
github_token (checkout falls back to github.token), empty url (git remote
add upstream '' fails) and has_cm_repo=false, so the cm repo is never
checked out and no rules are evaluated. Exit code 0 throughout.

Resolve by parsing first and switching on the value of `type`. Both
compressed forms are permanent, not a migration step - Bitbucket has no
run-name and keeps sending the bare form, so neither branch can be
retired.

`type` is matched by value rather than presence because a raw payload can
carry its own `type`: Bitbucket builds it from the webhook context. Such a
payload must fall through to the raw branch.

A compressed-payload envelope whose data is not gzip now fails loudly
rather than silently resolving to empty fields.

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

@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

LGTM

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

linearb[bot]
linearb Bot previously approved these changes Aug 13, 2026
The stash holds the payload rather than the envelope, and its form depends
on whether compression won: bare base64(gzip) if it did, raw JSON if not.
Both were already covered; this pins the third case, so a stash body that
is neither can never start falling through to empty fields.

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 refactor correctly addresses the silent-failure path by parsing first and switching on the envelope type value rather than its presence. The logic is sound, both permanent wire shapes are handled, and the new tests cover all enumerated cases. One minor robustness gap exists in the compressed-payload branch.

1 issues detected:

🐞 Bug - `parsed.data || ''` only replaces falsy values with an empty string; a truthy non-string value (object, number) is forwarded unchanged to `Buffer.from(value, 'base64')`, producing an opaque TypeError instead of the intended descriptive error. 🛠️

Details: When a compressed-payload envelope arrives with a data field that is present but not a string (e.g., a number or a nested object due to a malformed envelope), parsed.data || '' evaluates to that non-string value. inflateIfGzipped then calls Buffer.from(value, 'base64') with a non-string argument, which throws a TypeError from inside Node's Buffer code rather than the intentional "carries no gzip data" error. The PR's own explicit goal is to "fail loudly" in this case, but a TypeError: The "string" argument must be of type string surfaces instead of the descriptive message.

File: scripts/resolve-payload-fields.js (121-121)

🛠️ 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

return { mode: 'reference', payload }
}
if (parsed.type === COMPRESSED_PAYLOAD) {
const inflated = inflateIfGzipped(parsed.data || '')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🐞 Bug - Non-string data bypasses descriptive error: Guard the data field to be a non-empty string before passing it to inflateIfGzipped:

const inflated = typeof parsed.data === 'string' && parsed.data
  ? inflateIfGzipped(parsed.data)
  : null

This keeps the null → throw path intact while also covering the non-string case.

Suggested change
const inflated = inflateIfGzipped(parsed.data || '')
const inflated = typeof parsed.data === 'string' && parsed.data
? inflateIfGzipped(parsed.data)
: null
Is this review accurate? Use 👍 or 👎 to rate it

If you want to tell us more, use /gs feedback e.g. /gs feedback this review doesn't make sense, I disagree, and it keeps repeating over and over

@linearb
linearb Bot dismissed their stale review August 13, 2026 11:28

Review dismissed

A relative payloadUrl failed with a bare "TypeError: Invalid URL", which
tells whoever is on call nothing. Fail with the URL and the likely cause
instead.

The trigger guarantees an absolute URL - payloadUrl and resolver_url are
built from the same public API base, and resolver_url ships on every
dispatch, so an empty base would take out result reporting for every run
long before a stashed payload could expose it. This stays a diagnostic,
not relative-URL support: accepting relative URLs would add resolution
leniency for a state that cannot be reached quietly.

Also document that the host in payloadUrl is discarded always, not only
when it disagrees with the resolver, so the immunity to redirection
through that field is not later "fixed" away.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@yeelali14 yeelali14 closed this Aug 13, 2026
@yeelali14
yeelali14 deleted the fix/wrapped-compressed-envelope branch August 13, 2026 11:57
@yeelali14

Copy link
Copy Markdown
Contributor Author

Closed automatically by the branch rename to LINBEE-27157-wrapped-compressed-envelope. Continued in #571 with the same commits (e8cbf69).

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.

1 participant