You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Plugin routes parse the request body before the handler runs and expose it as ctx.input; ctx.request.text() / .json() are guarded because the stream is already consumed (#1293). That's the right default — but it makes one important class of integrations impossible to do securely: verifying webhook signatures.
Providers like Stripe, GitHub, Svix, Paddle, and Shopify sign the exact raw bytes of the delivery with an HMAC. To verify, the receiver must compute the HMAC over the unmodified body string. A re-serialized JSON.stringify(ctx.input) doesn't work: key order, whitespace, and unicode escapes differ, so the digest never matches.
Today a plugin receiving webhooks has two options, both bad:
Skip verification and treat the payload as untrusted input (forces awkward "re-fetch the event from the provider's API" patterns, which cost an extra round-trip and don't exist on all providers), or
Ask users to route webhooks through an external proxy/Worker that verifies before forwarding.
ctx.rawBody is the body as a string, only populated when the route sets rawBody: true (no memory overhead for everyone else — the dispatcher already buffers the body to parse it, so this is just keeping the string it already had).
ctx.input keeps working exactly as today (parsed from the same buffered text).
Non-JSON bodies (e.g. form-encoded webhook deliveries) become handleable at all: today they parse to undefined and are lost; with rawBody: true the handler can parse them itself.
Scope note: I'd start with trusted (configured) plugins. Sandboxed plugins hand the request over a serialization boundary, so passing the raw body there deserves its own consideration (probably a manifest capability).
I have a working implementation with tests I can PR if this direction sounds right.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Problem
Plugin routes parse the request body before the handler runs and expose it as
ctx.input;ctx.request.text()/.json()are guarded because the stream is already consumed (#1293). That's the right default — but it makes one important class of integrations impossible to do securely: verifying webhook signatures.Providers like Stripe, GitHub, Svix, Paddle, and Shopify sign the exact raw bytes of the delivery with an HMAC. To verify, the receiver must compute the HMAC over the unmodified body string. A re-serialized
JSON.stringify(ctx.input)doesn't work: key order, whitespace, and unicode escapes differ, so the digest never matches.Today a plugin receiving webhooks has two options, both bad:
Proposal
An opt-in flag on the route definition:
ctx.rawBodyis the body as a string, only populated when the route setsrawBody: true(no memory overhead for everyone else — the dispatcher already buffers the body to parse it, so this is just keeping the string it already had).ctx.inputkeeps working exactly as today (parsed from the same buffered text).undefinedand are lost; withrawBody: truethe handler can parse them itself.Scope note: I'd start with trusted (configured) plugins. Sandboxed plugins hand the request over a serialization boundary, so passing the raw body there deserves its own consideration (probably a manifest capability).
I have a working implementation with tests I can PR if this direction sounds right.
Beta Was this translation helpful? Give feedback.
All reactions