@amirabaris/webhook-kit 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -20
- package/bin/webhook-kit.mjs +2 -0
- package/dist/index.cjs +404 -311
- package/dist/index.d.cts +168 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +168 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +168 -0
- package/dist/index.js +404 -311
- package/dist/index.mjs +382 -312
- package/dist/index.mjs.map +1 -1
- package/docs/agent-rules.md +10 -2
- package/package.json +14 -11
package/docs/agent-rules.md
CHANGED
|
@@ -65,10 +65,15 @@ export async function POST(req: Request) {
|
|
|
65
65
|
2. **Web Cryptography API**:
|
|
66
66
|
- Use `globalThis.crypto.subtle.importKey(...)` and `globalThis.crypto.subtle.sign(...)`.
|
|
67
67
|
- Never import `node:crypto` into `src/`.
|
|
68
|
-
3. **
|
|
68
|
+
3. **Strict Digest & Signature Format Validation**:
|
|
69
|
+
- Validate signature structure and length before cryptographic checks (e.g., GitHub signatures must be exactly 64 hex characters `^[0-9a-fA-F]{64}$`; Shopify signatures must be 44 Base64 characters).
|
|
70
|
+
- Hex decoding (`hexToBytes`) must strictly reject non-hex characters with a `TypeError` rather than silently zero-filling or coercing.
|
|
71
|
+
4. **Replay Attack Mitigation & Idempotency**:
|
|
69
72
|
- Always enforce a timestamp tolerance check when the webhook provider includes a timestamp header (e.g., Stripe `t=...`).
|
|
70
73
|
- Default tolerance: 300 seconds (5 minutes).
|
|
71
|
-
-
|
|
74
|
+
- Both `timestamp` and `toleranceSeconds` must be positive, finite numbers. Reject `NaN`, `Infinity`, or negative values immediately with `TimestampToleranceError`.
|
|
75
|
+
- For deduplication across retries, pass `store: createInMemoryReplayStore()` (includes atomic `setNx` and bounded LRU eviction) or a `WebhookReplayStore` adapter (`has`, `set`, and optional atomic `setNx`) backed by Redis, Upstash, or Cloudflare KV. `webhook-kit` automatically serializes in-flight checks to prevent TOCTOU race conditions under high concurrency.
|
|
76
|
+
- Replay stores and `checkAndRecordReplay` must support both string and numeric `eventId` values (e.g. Shopify numeric order IDs) without skipping idempotency checks.
|
|
72
77
|
|
|
73
78
|
---
|
|
74
79
|
|
|
@@ -146,6 +151,9 @@ Before generating or approving code:
|
|
|
146
151
|
- [ ] Is raw body retained without intermediate JSON parsing?
|
|
147
152
|
- [ ] Are timing-safe comparisons used for all HMAC/signature checks?
|
|
148
153
|
- [ ] Is the code free from `node:crypto` imports in universal source code?
|
|
154
|
+
- [ ] Are signatures and digests strictly validated for length and character sets before comparison?
|
|
155
|
+
- [ ] Are timestamps and tolerance values validated as positive, finite numbers (rejecting `NaN` and `Infinity`)?
|
|
156
|
+
- [ ] Does replay protection and event ID resolution support both string and numeric IDs without skipping?
|
|
149
157
|
- [ ] Are TypeScript types strict (`noImplicitAny`, proper generics on `verifyWebhookSignature<T>`)?
|
|
150
158
|
- [ ] Is test coverage included with both valid signatures and tampered payload assertions?
|
|
151
159
|
- [ ] Does the commit message strictly follow Conventional Commits?
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amirabaris/webhook-kit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Zero-dependency, edge-compatible webhook signature verification toolkit using Web Cryptography API",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"webhook",
|
|
@@ -54,6 +54,17 @@
|
|
|
54
54
|
"README.md",
|
|
55
55
|
"LICENSE"
|
|
56
56
|
],
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsdown --config-loader native",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"test:watch": "vitest",
|
|
61
|
+
"test:types": "tsd --files 'tests-d/**/*.test-d.ts'",
|
|
62
|
+
"test:smoke": "node ./tests/package-smoke.mjs",
|
|
63
|
+
"lint": "oxlint . && eslint . && prettier --check . && tsc -p tsconfig.lint.json --noEmit",
|
|
64
|
+
"format": "eslint --fix . && prettier --write .",
|
|
65
|
+
"prepare": "lefthook install",
|
|
66
|
+
"prepack": "node -e \"import('node:fs').then(fs => { if (!fs.existsSync('dist/index.mjs')) import('node:child_process').then(cp => cp.execSync('pnpm build', { stdio: 'inherit' })) })\""
|
|
67
|
+
},
|
|
57
68
|
"devDependencies": {
|
|
58
69
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
59
70
|
"@types/node": "^26.6.6",
|
|
@@ -71,16 +82,8 @@
|
|
|
71
82
|
"engines": {
|
|
72
83
|
"node": ">=22.0.0"
|
|
73
84
|
},
|
|
85
|
+
"packageManager": "pnpm@9.15.9",
|
|
74
86
|
"publishConfig": {
|
|
75
87
|
"access": "public"
|
|
76
|
-
},
|
|
77
|
-
"scripts": {
|
|
78
|
-
"build": "tsdown --config-loader native",
|
|
79
|
-
"test": "vitest run",
|
|
80
|
-
"test:watch": "vitest",
|
|
81
|
-
"test:types": "tsd --files 'tests-d/**/*.test-d.ts'",
|
|
82
|
-
"test:smoke": "node ./tests/package-smoke.mjs",
|
|
83
|
-
"lint": "oxlint . && eslint . && prettier --check . && tsc -p tsconfig.lint.json --noEmit",
|
|
84
|
-
"format": "eslint --fix . && prettier --write ."
|
|
85
88
|
}
|
|
86
|
-
}
|
|
89
|
+
}
|