fix(webhook): fail-closed on missing secret; refresh CLAUDE.md module map #102

Merged
charles merged 1 commit from fix/webhook-fail-closed into main 2026-04-19 20:04:54 +00:00
Collaborator

Summary

Audit finding: src/webhook.ts was silently fail-open when the webhook secret was missing. loadWebhookConfig swallows the secret-file read error (logs a warning, continues with secret=""), and verifySignature returned true on empty secret — so every unsigned payload was accepted without verification. The warning scrolls past in startup logs and then disappears.

Also rolling in the CLAUDE.md module-map refresh since it's a one-file doc change and belongs in the same cleanup pass.

Changes

  • src/webhook.tsverifySignature now returns false when the secret is empty, with a [webhook] rejecting payload — no secret configured warning on each rejection so the operator sees the misconfig as loud 403s instead of silent acceptance.
  • src/webhook.test.ts — point the test config at a real secret file and sign every makeRequest body. Added three tests:
    • Invalid signature rejected (403)
    • Missing x-forgejo-signature header rejected (403)
    • Empty-secret config rejected (403) — direct regression guard for the fail-open bug
  • CLAUDE.md — drop the "Single-file service — everything in src/main.ts" claim (dead since the refactor). Added a Modules table documenting the 16 production files and their responsibilities.

Test plan

  • just qa — 276 pass, 0 fail
  • New regression test confirms fail-closed on empty secret
  • All 39 existing webhook.test.ts tests continue to pass with signing

Out of scope

  • Making loadWebhookConfig throw on missing secret file (would break local dev without a secret — current degrade-and-warn behaviour preserved)
  • Other audit findings (test coverage, route-handler extraction) — queued as follow-up PRs

🤖 Generated with Claude Code

## Summary Audit finding: `src/webhook.ts` was silently fail-open when the webhook secret was missing. `loadWebhookConfig` swallows the secret-file read error (logs a warning, continues with `secret=""`), and `verifySignature` returned `true` on empty secret — so every unsigned payload was accepted without verification. The warning scrolls past in startup logs and then disappears. Also rolling in the CLAUDE.md module-map refresh since it's a one-file doc change and belongs in the same cleanup pass. ## Changes - **`src/webhook.ts`** — `verifySignature` now returns `false` when the secret is empty, with a `[webhook] rejecting payload — no secret configured` warning on each rejection so the operator sees the misconfig as loud 403s instead of silent acceptance. - **`src/webhook.test.ts`** — point the test config at a real secret file and sign every `makeRequest` body. Added three tests: - Invalid signature rejected (403) - Missing `x-forgejo-signature` header rejected (403) - Empty-secret config rejected (403) — direct regression guard for the fail-open bug - **`CLAUDE.md`** — drop the "Single-file service — everything in `src/main.ts`" claim (dead since the refactor). Added a Modules table documenting the 16 production files and their responsibilities. ## Test plan - [x] `just qa` — 276 pass, 0 fail - [x] New regression test confirms fail-closed on empty secret - [x] All 39 existing `webhook.test.ts` tests continue to pass with signing ## Out of scope - Making `loadWebhookConfig` throw on missing secret file (would break local dev without a secret — current degrade-and-warn behaviour preserved) - Other audit findings (test coverage, route-handler extraction) — queued as follow-up PRs 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(webhook): fail-closed on missing secret; refresh CLAUDE.md module map
All checks were successful
qa / qa (pull_request) Successful in 2m33s
qa / dockerfile (pull_request) Successful in 9s
ea2f103ac1
`verifySignature` returned `true` when the webhook secret was empty
(e.g. `webhook_secret_file` missing — `loadWebhookConfig` swallows the
read error and leaves secret=""). Startup logged a warning, then every
payload was accepted without verification. Switch to fail-closed and
warn on each rejection so the misconfig surfaces as visible 403s
instead of silent acceptance.

- src/webhook.ts: `!secret` now returns false + warns, not true.
- src/webhook.test.ts: point test config at a real secret file, sign
  every `makeRequest` body. Added three regression tests: invalid
  signature rejected, missing signature rejected, empty-secret config
  rejected (this last one is the fail-open fix's regression guard).
- CLAUDE.md: drop the "single-file service" claim and add a Modules
  table covering all 16 production files (matches current layout).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
charles deleted branch fix/webhook-fail-closed 2026-04-19 20:04:54 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!102
No description provided.