-
Notifications
You must be signed in to change notification settings - Fork 47
Add Standard Webhooks signature support for HTTP shovels #1423
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
I have read the CLA Document and I hereby sign the CLA You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot. |
49a3c63 to
bcb1d82
Compare
|
Claude finished @Copilot's task —— View job PR Review CompleteTodo List:
Analysis ResultsAfter reviewing the Standard Webhooks signature implementation, I found no critical issues that need fixing. The code follows Crystal best practices and implements the Standard Webhooks specification correctly. The implementation includes:
All previously raised security concerns have been adequately addressed through existing protections and specification compliance. |
376a484 to
d278508
Compare
|
|
Claude finished @baelter's task —— View job Re-evaluating PR ReviewThanks for the clarifications! After re-examining the code with your feedback, I need to revise my assessment: Todo List:
Updated AssessmentYou're absolutely correct on all points: ✅ Timing Attack Concern - Resolved
✅ Memory Allocation - Already Protected
✅ Standard Webhooks Compliance
✅ Exception Handling - Not Required
Remaining Valid Points from Original ReviewThe only remaining concerns from the original review are design questions already raised by other reviewers:
The implementation is solid and follows the Standard Webhooks specification correctly. My original security concerns were based on misunderstanding the existing protections and spec requirements. |
carlhoerberg
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome that we follow a standard/convention now!
I wonder if masking in the backend really adds anything? I think a lot of code could be removed if you just used a password input field for secrets/passwords in the UI.
Messages are already in memory as an IO::Memory, so the optimization and protection against not hashing large messages (and setting an arbitrary limit) is moot i think and a lot of code could be removed.
Commented on a couple memory optimizations, where unneeded allocations where made, some small (join), some big (msg.body_io.gets_to_end).
| # Preserve secret fields from existing parameter if not provided in new value | ||
| # Keys ending in -secret or -password are considered secrets | ||
| private def merge_secrets(existing : JSON::Any, new_value : JSON::Any) : JSON::Any |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this is used because you mask the secret fields in the backend, but what if you didn't? and the secret/password was posted each time from the UI? then this wouldn't be needed, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See: #1423 (comment)
647cb25 to
f99fbaa
Compare
We already use a password field in the UI. The backend masking protects the secrets from ever being accessed through the API. The reason to have it like that instead of not returning the secret at all is to provide info to the user/ui if a secret is set or not. But yeah, maybe we can simplify this... EDIT: |
d212d53 to
5792e3b
Compare
Implements webhook signature verification per standardwebhooks.com spec: - webhook-id: unique message identifier (msg_<uuid>) - webhook-timestamp: Unix timestamp in seconds - webhook-signature: HMAC-SHA256 signature as v1,<base64> Supports multiple space-delimited secrets for zero-downtime key rotation. When no secret is configured, body is streamed directly without buffering.
adddce1 to
1d81334
Compare
- Mask secrets in API responses (keys ending in -secret or -password) - Add --dest-signature-secret option to lavinmqctl add_shovel - Add signature secret field to shovel UI (shown for HTTP destinations) - Update OpenAPI docs with dest-signature-secret parameter - Update changelog and README
1d81334 to
b8445a6
Compare
| return value unless h = value.as_h? | ||
| masked = h.transform_values do |v, k| | ||
| if (k.ends_with?("-secret") || k.ends_with?("-password")) && v.as_s? | ||
| JSON::Any.new("********") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense for a human, but for an automated API it's less clear that is masked. Return nil instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think nil would easily be interpreted as "not set". Another thought I had was to turn it into a bool, so return true if set, false if not, but I found comfort in mimicking github :)
Co-authored-by: Carl Hörberg <[email protected]>
Co-authored-by: Carl Hörberg <[email protected]>
Summary
Adds Standard Webhooks signature support for HTTP webhook shovels, enabling secure webhook delivery with HMAC-SHA256 signatures.
Features
webhook-id,webhook-timestamp,webhook-signature********in API responses (write-only, like GitHub webhooks)--dest-signature-secretoption forlavinmqctl add_shovelConfiguration
{ "src-uri": "amqp://localhost", "src-queue": "events", "dest-uri": "https://example.com/webhook", "dest-signature-secret": "whsec_current_key whsec_old_key" }Example webhook headers
Secret handling (GitHub-style)
"dest-signature-secret": "********")Test plan
Closes #1377