feat(kyc): persist webhook authentication failures for audit - #1206
Open
bilhokista wants to merge 3 commits into
Open
feat(kyc): persist webhook authentication failures for audit#1206bilhokista wants to merge 3 commits into
bilhokista wants to merge 3 commits into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1104.
Requirement 1 was already met — by something better
The issue asks for
subtle::ConstantTimeEqfor the signature comparison.verify_webhook_signaturealready compares in constant time, viaMac::verify_slice:That is the
hmaccrate's own constant-time check — internally it is the samesubtleprimitive, reached through the API designed for exactly this. Swapping it for a hand-rolledConstantTimeEqon the hex string would be a step backwards: it adds a dependency, and comparing hex text rather than raw digest bytes is easier to get subtly wrong.So I did not change it. If you specifically want the
subtledependency for audit reasons, say so and I will add it, but I did not want to weaken working code to match the letter of the description.Requirement 2 was genuinely missing
"Log all failed webhook authentication attempts into
kyc_webhook_logs" was not happening. The handler returns early on a signature failure:…and the
INSERT INTO kyc_webhook_logssits roughly eighty lines further down, after the payload parse and the database update. Rejected requests therefore left no trace in the database at all — only awarn!line on stdout. Those are precisely the attempts an audit most wants: someone probing the endpoint or guessing at the secret.Rejections are now persisted before the early return.
Schema
A rejected request never reaches the parser, so
wallet_address,event_type,kyc_statusandraw_payloadcannot be populated for one. The migration relaxes those four to nullable rather than filling them with sentinels, so "we never learned this" stays distinguishable from a real value — andkyc_statusis an enum, so a sentinel would have meant polluting the enum itself.It also adds
auth_failure_reason TEXT(NULL for a successful request) with a partial index on(processed_at DESC) WHERE auth_failure_reason IS NOT NULL, which is the query the column exists for.The
.down.sqlrestoresNOT NULL, which requires first deleting the rows that could not populate those columns — exactly the failure rows this migration introduced. That is called out in a comment, since it makes the down-migration lossy and a reviewer should agree to it deliberately.Three security decisions worth review
client_messagedeliberately cannot distinguish a malformed signature from a wrong one, and that must not change — soaudit_reason()is a separate method that is only ever stored, never returned. There is a test asserting the client message still hides the variant.I did not record the client IP. Doing so means taking
ConnectInfoand thinking about proxy headers (X-Forwarded-Foris caller-controlled unless the proxy is trusted), which is a separate decision rather than something to slip into this change. Happy to follow up if you want it.Verification
Honest note: I could not run
cargo testfor the crate — it needs the full dependency graph and sqlx's database or offline metadata, neither of which I have here.What I did do: lifted
SignatureError, its impls and the three new tests into a standalone file and compiled them withrustc --test --edition 2021. It compiles clean and all three tests pass — that the four audit reasons are distinct, that they are stable snake_case identifiers (they land in a column that dashboards will filter on, so they are an interface, not prose), and that adding a precise audit label did not make the client response precise too.The SQL and the sqlx query in
log_auth_failureneed CI and a real database.🤖 Generated with Claude Code
https://claude.ai/code/session_01CrfEY1tvXrbeMDAUzxfuk7