fix: read the wrapped compressed-payload envelope - #570
Conversation
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>
There was a problem hiding this comment.
Orca Security Scan Summary
| Status | Check | Issues by priority | |
|---|---|---|---|
| Infrastructure as Code | View in Orca | ||
| OSS Licenses | View in Orca | ||
| SAST | View in Orca | ||
| Secrets | View in Orca | ||
| Vulnerabilities | View in Orca |
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>
There was a problem hiding this comment.
✨ 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 || '') |
There was a problem hiding this comment.
🐞 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)
: nullThis keeps the null → throw path intact while also covering the non-string case.
| 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
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>
|
Closed automatically by the branch rename to |
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: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
typematters: a raw payload can legitimately carry its owntype, 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-nameto protect and keeps sending the barebase64(gzip)form indefinitely, so neither branch can ever be retired. Supporting both also makes the rollout order-independent: customers pick up@v2on their own schedule, so the action and the trigger cannot be deployed in a guaranteed order.A
compressed-payloadenvelope whosedataisn't gzip now fails loudly instead of silently resolving to empty fields.The wrapped compressed tier reports
mode=compressed-enveloperather thanmode=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:
plainplaintype: 'push'plainbase64(gzip)— Bitbucket, permanentcompressedcompressed-payload— GitHub, newcompressed-envelopereferencereferencecompressed-payloadwith non-gzipdata19 tests pass (15 before, +4), lint and prettier clean.
Coordination
Format agreed with the trigger side (gitstream-sls-pipeline). Key names are unchanged from #566 —
oversized-payload-reference/payloadUrl/resolverToken, pluscompressed-payload/data.dataisbase64(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:
tryParsePayload()function andCOMPRESSED_PAYLOADconstant to handle wrapped compressed envelopes with type detectionreadStashReference()with unified envelope parsing inresolvePayload()supporting compressed-payload, reference, and plain modesstashUrl()with explicit URL validation error handling and comprehensive documentation for origin securityGenerated 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