Closes#15. Architecture review QRun-IO/qqq#1150, finding F11: one webhook signer, in core.
Bumps to QQQ 4.1.0-RC.5.
RevalidationHookRunner signs with core WebhookSigner/WebhookSecret (Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature) and sends through core SafeHttpClient, which does the DNS checks and preserves the hostname.
New cdn_revalidation_hook fields:
signatureFormat (CdnSignatureFormat):
STANDARD_WEBHOOKS is the default for new rows.
LEGACY keeps X-Revalidate-Signature: sha256=<hex HMAC of body> byte-for-byte; golden vectors are tested.
A null format, as on existing rows after the column is added, stays LEGACY, so current receivers keep working.
allowPlainHttp and internalAllowlist: explicit exceptions to the SSRF-safe defaults.
Liquibase: add three columns to cdn_revalidation_hook: signatureFormat (nullable), allowPlainHttp and internalAllowlist.
Internal HTTP receivers now need allowPlainHttp=true and the host in internalAllowlist. Without them, SafeHttpClient blocks the call. Set them in the same migration.
Moving a hook to Standard Webhooks needs a receiver change in Website-Frontend:
src/lib/revalidate.ts:14: verify id.timestamp.rawBody with a timestamp window.
src/app/api/revalidate/route.ts:40: pass the three headers.
Then set signatureFormat=STANDARD_WEBHOOKS. A secret must have at least 16 bytes.
Tests
mvnw -B verify: 81 tests, 0 failures, 0 checkstyle violations. Coverage is 95.30% instructions and 100% classes.
core WebhookVerifier round trip;
legacy golden vectors, including the null-format-means-legacy case;
## Summary
Closes #15. Architecture review QRun-IO/qqq#1150, finding F11: one webhook signer, in core.
- Bumps to **QQQ 4.1.0-RC.5**.
- `RevalidationHookRunner` signs with core `WebhookSigner`/`WebhookSecret` (Standard Webhooks: `webhook-id`, `webhook-timestamp`, `webhook-signature`) and sends through core `SafeHttpClient`, which does the DNS checks and preserves the hostname.
- New `cdn_revalidation_hook` fields:
- **`signatureFormat`** (`CdnSignatureFormat`):
- `STANDARD_WEBHOOKS` is the default for **new** rows.
- `LEGACY` keeps `X-Revalidate-Signature: sha256=<hex HMAC of body>` byte-for-byte; golden vectors are tested.
- **A null format, as on existing rows after the column is added, stays `LEGACY`**, so current receivers keep working.
- **`allowPlainHttp`** and **`internalAllowlist`**: explicit exceptions to the SSRF-safe defaults.
- Migration guide: `docs/revalidation-hook-migration.md`.
## Host impact (kof22)
- Liquibase: add three columns to `cdn_revalidation_hook`: `signatureFormat` (nullable), `allowPlainHttp` and `internalAllowlist`.
- **Internal HTTP receivers now need `allowPlainHttp=true` and the host in `internalAllowlist`.** Without them, SafeHttpClient blocks the call. Set them in the same migration.
- Moving a hook to Standard Webhooks needs a receiver change in Website-Frontend:
- `src/lib/revalidate.ts:14`: verify `id.timestamp.rawBody` with a timestamp window.
- `src/app/api/revalidate/route.ts:40`: pass the three headers.
Then set `signatureFormat=STANDARD_WEBHOOKS`. A secret must have at least 16 bytes.
## Tests
`mvnw -B verify`: 81 tests, 0 failures, 0 checkstyle violations. Coverage is 95.30% instructions and 100% classes.
- core `WebhookVerifier` round trip;
- legacy golden vectors, including the null-format-means-legacy case;
- SSRF policy cases.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Closes #15. Architecture review QRun-IO/qqq#1150, finding F11: one webhook signer, in core.
RevalidationHookRunnersigns with coreWebhookSigner/WebhookSecret(Standard Webhooks:webhook-id,webhook-timestamp,webhook-signature) and sends through coreSafeHttpClient, which does the DNS checks and preserves the hostname.cdn_revalidation_hookfields:signatureFormat(CdnSignatureFormat):STANDARD_WEBHOOKSis the default for new rows.LEGACYkeepsX-Revalidate-Signature: sha256=<hex HMAC of body>byte-for-byte; golden vectors are tested.LEGACY, so current receivers keep working.allowPlainHttpandinternalAllowlist: explicit exceptions to the SSRF-safe defaults.docs/revalidation-hook-migration.md.Host impact (kof22)
Liquibase: add three columns to
cdn_revalidation_hook:signatureFormat(nullable),allowPlainHttpandinternalAllowlist.Internal HTTP receivers now need
allowPlainHttp=trueand the host ininternalAllowlist. Without them, SafeHttpClient blocks the call. Set them in the same migration.Moving a hook to Standard Webhooks needs a receiver change in Website-Frontend:
src/lib/revalidate.ts:14: verifyid.timestamp.rawBodywith a timestamp window.src/app/api/revalidate/route.ts:40: pass the three headers.Then set
signatureFormat=STANDARD_WEBHOOKS. A secret must have at least 16 bytes.Tests
mvnw -B verify: 81 tests, 0 failures, 0 checkstyle violations. Coverage is 95.30% instructions and 100% classes.WebhookVerifierround trip;🤖 Generated with Claude Code