Webhook signature-verification audit. Stripe, GitHub, Shopify, Twilio +17. Local. Deterministic.
npx hookwarden scan ./your-app
No traffic leaves your machine. No telemetry. No SaaS sign-up required.
📖 Full documentation: hookwarden docs
Every Sunday at 22:00 UTC, this repo's CI runs hookwarden against 45 popular open-source projects — currently cal.com, documenso, formbricks, twenty, plane, unkey, typebot, papermark (full target list, combined ★190k+) — to prove the scanner works on real production code.
Latest sweep — 2026-06-09 · 20/45 projects clean (zero critical/high)
| Provider | 🚨 critical | ⚠️ high | 🟡 manual-review | Rules that fired |
|---|---|---|---|---|
| n8n integrations | 81 | 0 | 0 | n8n/missing-signature-verification (×78)n8n/raw-body-misuse (×3) |
| Slack integrations | 7 | 1 | 0 | slack/missing-signature-verification (×7)slack/verify-after-side-effect (×1) |
| Standard Webhooks integrations | 7 | 0 | 0 | standardwebhooks/missing-signature-verification (×3)standardwebhooks/raw-body-misuse (×4) |
| Stripe integrations | 6 | 0 | 0 | stripe/hardcoded-secret-prefix (×2)stripe/missing-signature-verification (×4) |
| GitHub integrations | 0 | 0 | 0 | — |
| Shopify integrations | 0 | 0 | 0 | — |
| Square integrations | 0 | 0 | 0 | — |
| Twilio integrations | 0 | 0 | 0 | — |
These are bugs in the webhook handlers that receive provider events — flaws in the integrating projects' integration code, not in the providers' own SDKs or services.
Coverage note: the engine couldn't parse 238 files across the corpus (broken syntax or language features the parser doesn't model). Those are scan-coverage diagnostics — not handler bugs — and are excluded from the table above.
Hookwarden checks 11 rule classes across 21 providers — most of the corpus handles webhooks correctly, hence the short list. The full rule catalog lives in the docs.
Per-target findings are never published before responsible disclosure — see methodology. To run the same scan against your own code:
npx hookwarden scan ./your-app
Every dollar of fraud that flows through a webhook starts with a verification bug — and verification bugs hide in plain sight.
A handler that accepts an unsigned payload, compares HMACs with ==, or skips the signature check on a ?test=true path silently routes attacker traffic into your business logic. The bug is one line in a 50K-line app, and it looks plausible — not the shape general-purpose SAST tools are tuned to flag. They were built for SQL injection and prototype pollution; webhook verification falls between their default rule packs.
hookwarden does one thing. It walks your repo, parses every webhook handler across 15 frameworks, and labels each one verified, not-verified, or manual-review — with the exact file, line, and a fix quoted from provider docs. The catalog (24 providers — Stripe, GitHub, Shopify, Slack, Twilio, Square, Sentry, Zendesk, DocuSign, PagerDuty, Notion, Auth0, HubSpot, Intercom, Linear, Zoom, Calendly, Bitbucket, Mailchimp, Postmark, Discord, n8n, the Anthropic Agent SDK, plus Standard Webhooks conformant providers like Clerk, Resend, Mux) encodes signature quirks no generic scanner has the surface area to know: Stripe's 5-minute timestamp tolerance, Slack's v0:${ts}:${body} scheme, Twilio's SHA-1 outlier.
The three-state verdict is not a hedge. manual-review is what you get when hookwarden can't prove safety or unsafety from the source alone — a handler inside a middleware chain the analyzer couldn't unroll, say. It's how hookwarden avoids reporting guesses as bugs. A tool that reports every gray area as a bug isn't a security tool; it's noise. → How the verdict works
npx hookwarden scan . # works everywhere, no install
hookwarden is a CLI, not a library — run it with
npx hookwarden scan .or install globally withnpm i -g hookwarden. (The plainnpm i hookwardenthat npm auto-suggests on the package page installs it as a local dependency, which isn't what you want for a command-line tool.)
Or install natively:
| OS | Recommended | Alternates |
|---|---|---|
| Linux | brew install Hookwarden/tap/hookwarden | npm i -g hookwarden · pip install hookwarden · direct binary |
| macOS | brew install Hookwarden/tap/hookwarden | npm i -g hookwarden · npx hookwarden |
| Windows | scoop bucket add hookwarden https://github.com/Hookwarden/scoop-bucket && scoop install hookwarden | npm i -g hookwarden · pip install hookwarden |
Node 22+ is required for the npm/npx/macOS-brew paths; the standalone binaries (Linux x64/arm64, Windows x64) bundle the runtime. Direct binary downloads are intentionally unsigned (Gatekeeper / SmartScreen will warn) — prefer brew / scoop / npm / pip, which verify by SHA-256. → Install guide
hookwarden --version # check what you have
| Channel | Command |
|---|---|
| brew (macOS/Linux) | brew upgrade hookwarden |
| scoop (Windows) | scoop update hookwarden |
| npm (global) | npm i -g hookwarden@latest |
| pip | pip install -U hookwarden |
| npx (no install) | npx hookwarden@latest scan . — @latest bypasses the npx cache |
| direct binary | re-download from Releases |
Rule pack versions move with the CLI (engine, rules, and CLI ship as a fixed group — 0.7.5 everywhere). Pin in CI with npx hookwarden@0.7.5 scan . if you want byte-stable verdicts across runs.
# Scan — no install required
npx hookwarden scan ./your-app
# Fail CI on high+ findings, machine-readable output
npx hookwarden scan ./your-app --fail-on high --format json
# Exit codes: 0 clean · 1 findings at threshold · 2 engine error · 3 config error · 4 parse coverage below floor
# Adopt on a non-greenfield repo — accept existing findings, gate only NEW ones
npx hookwarden scan ./your-app --baseline write
npx hookwarden scan ./your-app --fail-on high
# List every detected webhook handler (no rules run) — useful for audits
npx hookwarden inventory ./your-app
--diff-only, --provider stripe,github (phased rollout), --include/--exclude globs, --strict-suppressions, repo-level hookwarden.config.yaml, and more: npx hookwarden --help and the CLI docs.
--history)By default scan only looks at your working tree. --history also walks the
git history — including files that were committed then deleted before HEAD —
so a secret that was force-pushed away is still found. It's off by default and
bounded to the last 1000 commits; narrow it with --since <ref|date>:
npx hookwarden scan --history # last 1000 commits
npx hookwarden scan --history --since v1.0.0 # since a tag/ref
npx hookwarden scan --history --since 2026-01-01
--history is fully open-source and never requires a token.
--verify-secrets)--verify-secrets checks whether a leaked API-key-class credential is still
alive by making a single read-only call to the secret's own provider
(Stripe / GitHub) directly from your machine — hookwarden never sees the secret.
A live leak is escalated to critical; a dead (rotated/revoked) one is
downgraded to info. It's part of the paid team tier, which isn't open yet; off by default and
explicit opt-in only.
# Team tier — not open yet. Without a token the flag makes no provider calls.
HOOKWARDEN_TOKEN=hw_… npx hookwarden scan --verify-secrets
whsec_, GitHub webhook secret) is always
reported unverified — no provider can confirm a signing secret's liveness.HOOKWARDEN_TOKEN, findings are reported unverified and no
provider call is made.hookwarden doesn't just name the fix — it applies it. The fix subcommand mechanically rewrites the safety: safe subset across JS/TS, Python, PHP, and Go (covering timing-unsafe comparisons → crypto.timingSafeEqual / hmac.compare_digest / hash_equals, and raw-body misuse). The other 203 rules are architectural and emit per-finding fix prose instead.
npx hookwarden fix ./your-app # dry-run — prints a unified diff, writes nothing
npx hookwarden fix ./your-app --write # atomic staging, re-scan, then rename into place
Every rewrite lands in a staging dir and is re-scanned before replacing the original; the fixer never edits inside strings or comments. → hookwarden fix · Safety levels
Captured verbatim — each line is what you'll see in your terminal, not a mockup. One Express middleware bug produces three findings, because that single mistake violates three distinct invariants; fixing one (re-ordering the middleware) clears all three:
$ hookwarden scan ./your-app
× critical server.js:10:1 stripe/express-middleware-ordering not-verified
Express webhook handler for Stripe has `express.json()` registered before the webhook route. JSON
middleware consumes the request body; by the time the Stripe handler runs, the raw bytes used for
HMAC are gone and `stripe.webhooks.constructEvent` cannot reproduce the signature.
fix › register `express.json()` AFTER the webhook route, OR mount `express.raw(...)` on the path only.
docs › https://docs.stripe.com/webhooks#verify-events
refs › https://expressjs.com/en/guide/using-middleware.html
https://www.svix.com/blog/common-failure-modes-for-webhook-signatures/
× critical server.js:10:1 stripe/missing-signature-verification not-verified
Stripe webhook handler does not appear to verify the signature header before processing the event …
× critical server.js:10:1 stripe/raw-body-misuse not-verified
Stripe webhook handler reads the signature header but does not appear to receive the body as raw bytes …
────────────
Found 3 critical · 0 high · 0 medium · 0 low · 0 info · 0 manual-review — 1 webhook handler across 1 file
Scanned in 3 ms · 1 / 1 candidates parsed (100.0% coverage)
| Glyph | Severity | SARIF level | Counts toward --fail-on? |
|---|---|---|---|
× | critical | error | yes |
! | high | error | yes |
▲ | medium | warning | yes |
· | low | note | yes |
i | info | note | no — informational (e.g. library-verified) |
The state column is the three-state verdict. Output is also available as byte-stable JSON and SARIF 2.1.0 (round-trips through GitHub Code Scanning, dedupes via partialFingerprints). → Output formats & JSON schema
4 languages · 15 frameworks · 24 providers · 249 rules · 100% cited. Every rule carries ≥1 external citation (CWE / RFC / Svix / Stripe spec) alongside the provider's own docs — auditors and reviewers can follow any finding back to a stable external source. JS/TS parse with Babel; Python, PHP, and Go with tree-sitter (WASM).
| Language | Frameworks |
|---|---|
| JavaScript / TypeScript | Express · Hono · Fastify · Next.js |
| Python | Flask · FastAPI · Django |
| PHP | Laravel · Symfony · Slim · vanilla single-file |
| Go | net/http · chi · gin · echo |
Every rule carries fix guidance quoted from the provider's canonical security docs. Full per-rule reference and coverage matrix: rule reference.
# .github/workflows/hookwarden.yml
name: hookwarden
on: [pull_request, push]
permissions:
contents: read
pull-requests: write
security-events: write
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: Hookwarden/hookwarden-action@v1
with:
fail-on: high
Uploads SARIF to Code Scanning automatically; findings appear as PR annotations. Raw-CLI + SARIF-upload recipe, exit-code matrix, and diff-only PR scans: → CI integration guide.
hookwarden is specialized on purpose. The general-purpose scanners below are excellent — they're just not in this fight.
| Tool | What it does well | Webhook verification |
|---|---|---|
| semgrep | General-purpose SAST; flexible rules | Low signal — generic matching misses body-parsing order, timing-safe paths, SDK flows |
| snyk Code | Broad vuln detection (paid SaaS) | No webhook rules; doesn't model HMAC reachability |
| GitGuardian / TruffleHog | Secret-leak detection | Finds hardcoded secrets; doesn't audit whether verification is correct |
| Datadog Static Analysis | Broad SAST; good cloud signal | No webhook specialization; low-signal for this bug class |
| hookwarden | Webhook verification logic only | 249 rules (100% cited), 24 providers, three-state verdicts |
hookwarden is not a general-purpose SAST or DAST scanner — it won't find XSS, SQL injection, or memory-safety bugs, and it isn't trying to. Keep semgrep, CodeQL, or your DAST for those. Already running one? hookwarden is additive — it finds the one class of bug they weren't built to catch.
@hookwarden/mcp is a Model Context Protocol server that gives AI coding agents the scan_handler tool. Paste in any webhook handler, get back the same 3-state verdict (verified / not-verified / manual-review) the CLI would emit — fully local, deterministic, no traffic leaves the agent's machine.
npx @hookwarden/mcp init # auto-detects Claude Desktop / Cursor / Continue and writes the config
The rule pack is bundled inline, content-hashed, and version-pinned to the engine — scan_handler cross-checks both on every call and fails loudly on drift. → @hookwarden/mcp on npm
A pnpm monorepo with a strict, CI-enforced dependency boundary: the engine is pure-functional (no I/O, no filesystem, no network), so the same engine runs in the CLI, in CI, in the MCP server, and — eventually — in a browser playground without modification.
| Package | Purpose | License |
|---|---|---|
@hookwarden/engine | Handler discovery, reachability analysis, evidence collection. Pure-functional, browser-safe. | Apache 2.0 |
@hookwarden/rules | Provider catalog, YAML rule packs, parameterized predicate factories. | Apache 2.0 |
@hookwarden/fix | Auto-remediation — bounded location for AST mutation (Babel traverse + generator). | Apache 2.0 |
@hookwarden/mcp | Model Context Protocol server — scan_handler tool for AI coding agents. | Apache 2.0 |
hookwarden | CLI binary — reads config, drives the engine, renders text/JSON/SARIF. | Apache 2.0 |
dependency-cruiser enforces the engine's I/O boundary in every PR.
@hookwarden/mcp developer preview (Model Context Protocol server exposing scan_handler to Claude Code, Cursor, Continue, and the Anthropic Agent SDK — pasted handler code returns a 3-state verdict locally, zero-network, deterministic). → MCP serverrefs › block per finding; JSON envelope gains scan.findings[].references: string[]. v0.7.1 cited every rule; v0.7.2 makes citations actually visible to scan consumers).hookwarden fix auto-remediation (mechanical AST rewrites, safe/manual-only per rule).--provider filter for phased rollout.Rule-pack PRs are the highest-value contribution — adding a provider is a catalog edit plus N rule YAMLs, no new TypeScript in most cases. See the existing 21 in packages/rules/rules/ as worked examples, and CONTRIBUTING.md.
pnpm install && pnpm -r build && pnpm -r test
Bug reports & feature requests: open an issue. Docs: hookwarden docs · hookwarden.dev.
To add yourself after a merged PR, comment
@all-contributors please add @<username> for <contribution>.
Apache 2.0 — see LICENSE. The CLI, engine, and rule packs are open source and will stay that way. A separate closed-source SaaS tier handles continuous monitoring, secret-leak scanning, automated rotation, and SOC 2 evidence export — hookwarden.dev.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y @hookwarden/mcpMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-hookwarden-mcp": {
"command": "npx",
"args": [
"-y",
"@hookwarden/mcp"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceHookwarden — Webhook Integrity works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.