hookarmor 1.0.3 โ†’ 1.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HookArmor Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,260 +1,305 @@
1
- # ๐Ÿ›ก๏ธ HookArmor
2
-
3
- > **Zero-loss Webhook Dead-Letter Queue (DLQ), Reliability Proxy, and Replay Gateway for Stripe, Shopify, Clerk, and modern B2B SaaS.**
4
-
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
- [![Tests: Passing](https://img.shields.io/badge/Tests-8%20Passing-brightgreen.svg)]()
7
- [![Status: Production Ready](https://img.shields.io/badge/Status-v1.0.3-blueviolet.svg)]()
8
-
9
- ---
10
-
11
- ## ๐Ÿ’ฅ The Problem HookArmor Solves
12
-
13
- Every developer using Stripe, Shopify, GitHub, Clerk, Paddle, or custom webhooks faces catastrophic silent revenue loss when:
14
- 1. **Serverless cold starts & timeouts**: Next.js, Vercel, or AWS Lambda cold starts hit the 10-15s webhook limit.
15
- 2. **Zero-downtime deploy reboots**: Container restarts on Render, Railway, or Fly.io return transient `502 Bad Gateway`.
16
- 3. **Database connection pool exhaustion**: Prisma or Postgres locks during sudden traffic spikes cause unhandled `500 Internal Server Error`.
17
- 4. **Stripe's painful retry schedule**: When Stripe receives a failure, it backs off exponentially (1 hour, then several hours, then days). Meanwhile, your paying customer logs in, sees "Free Plan", assumes a scam, and disputes the charge or issues a refund.
18
-
19
- ---
20
-
21
- ## โšก How HookArmor Works
22
-
23
- ```
24
- [Stripe / Shopify / Clerk]
25
- โ”‚ (POST webhook)
26
- โ–ผ
27
- โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
28
- โ”‚ HookArmor Ingress Edge โ”‚
29
- โ”‚ - Returns immediate 200 OK (<10ms) โ”‚
30
- โ”‚ - Stores raw payload in SQLite DLQ โ”‚
31
- โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
32
- โ”‚
33
- โ–ผ
34
- โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
35
- โ”‚ Relay Dispatch Engine โ”‚
36
- โ”‚ - Forwards request to user's server โ”‚
37
- โ”‚ - Preserves exact headers & HMAC sigsโ”‚
38
- โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
39
- โ”‚ โ”‚
40
- (2xx Success) (5xx / 4xx / Timeout)
41
- โ–ผ โ–ผ
42
- โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
43
- โ”‚ Event Archive โ”‚ โ”‚ Dead-Letter Queue โ”‚
44
- โ”‚ - Latency log โ”‚ โ”‚ - Capture error code & body โ”‚
45
- โ”‚ - Status 200 โ”‚ โ”‚ - Instant Discord / Slack Alert โ”‚
46
- โ”‚ โ”‚ โ”‚ - Smart exponential auto-retry โ”‚
47
- โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ - 1-Click UI & CLI Replay Engine โ”‚
48
- โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
49
- ```
50
-
51
- 1. **Sub-10ms Ingest**: HookArmor returns an immediate `200 OK` to the sender so Stripe or Shopify never marks the event failed.
52
- 2. **Cryptographic Header & Signature Preservation**: Forwards exact raw bytes, `stripe-signature`, `x-shopify-hmac-sha256`, and timestamps.
53
- 3. **Dead-Letter Queue (DLQ)**: If your server returns 500, 502, 504, 429, or times out, HookArmor safely preserves the raw event with full error diagnostics.
54
- 4. **Instant Alerts**: Sends immediate Slack / Discord webhooks when an endpoint begins failing.
55
- 5. **1-Click Bulk Replay**: As soon as you push your code fix, hit **Replay All Dead-Letter** in the Web UI or run `hookarmor replay --failed` to restore all customer transactions in 2 seconds.
56
- 6. **Local Dev Tunnel**: `hookarmor listen http://localhost:3000/api/webhooks` to replay production webhooks straight into localhost debuggers.
57
-
58
- ---
59
-
60
- ## ๐Ÿš€ Quickstart
61
-
62
- ### 1-Click Cloud Deployment (Free & Self-Hosted)
63
-
64
- Deploy your own private, persistent HookArmor instance to the cloud with one click:
65
-
66
- [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template?template=https://github.com/pkdoddamani/hookarmor)
67
- [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/pkdoddamani/hookarmor)
68
-
69
- ---
70
-
71
- ### Local Quickstart
72
-
73
- #### Option A: Docker Compose (Zero Config)
74
- ```bash
75
- git clone https://github.com/pkdoddamani/hookarmor.git
76
- cd hookarmor
77
- docker compose up -d
78
- ```
79
-
80
- #### Option B: Terminal CLI
81
- ```bash
82
- npx hookarmor start
83
- ```
84
- * **Web Dashboard**: `http://localhost:4000`
85
- * **Ingress Gateway**: `http://localhost:4000/in/:endpointId`
86
-
87
- ### 2. Tunnel Direct to Localhost
88
- ```bash
89
- npx hookarmor listen http://localhost:3000/api/webhooks/stripe
90
- ```
91
-
92
- ### 3. Replay Failed Dead-Letter Events
93
- ```bash
94
- # Replay all failed events across all endpoints
95
- npx hookarmor replay --failed
96
-
97
- # Replay a specific event ID
98
- npx hookarmor replay evt_1758513516086_m8r0e7
99
- ```
100
-
101
- ---
102
-
103
- ## ๐Ÿ“Š Verification Test Suite
104
-
105
- HookArmor includes an 8-scenario automated verification test suite covering edge cases, replay re-signing, and pool protection:
106
- ```bash
107
- npm test
108
- ```
109
- ```
110
- ๐Ÿงช Starting HookArmor Hardened Verification Test Suite (Post-Audit)...
111
-
112
- Test 1: Testing SSRF Protection & Endpoint Creation...
113
- โœ… SSRF probe against 169.254.169.254 successfully blocked.
114
- โœ… Valid endpoint created with secret.
115
-
116
- Test 2: Testing Ingress Signature Verification (Security Boundary)...
117
- โœ… Forged signature rejected with 400 Bad Request.
118
- โœ… Authentic signature verified, ingested, and delivered.
119
-
120
- Test 3: Testing 5-Minute Expiration Defeat (Fresh Outbound Re-Signing)...
121
- โœ… HookArmor defeated the 5-minute Stripe expiration trap:
122
- Stored in DB: t=1600000000 (Expired 5+ years ago)
123
- Re-signed on replay: t=1790970445 (Current) -> stripe.webhooks.constructEvent succeeds!
124
- Provenance verified: is-replay=true, orig-sig preserved.
125
-
126
- Test 4: Simulating Downstream Failure (500) -> Dead-Letter Queue...
127
- โœ… Event safely quarantined in Dead-Letter Queue with HTTP 500.
128
- Next automated retry scheduled with exponential backoff + jitter.
129
-
130
- Test 5: Testing Background Retry Worker (Automatic Self-Healing)...
131
- โœ… Background Retry Worker automatically claimed and delivered DLQ event! (Attempts: 2)
132
-
133
- Test 6: Testing Concurrency Limiting (Pool Protection)...
134
- โœ… Concurrency capped at max 2 parallel in-flight connections (pool protected).
135
-
136
- Test 7: Testing Safe Idempotency Key Deduplication...
137
- โœ… Duplicate event intercepted and suppressed cleanly.
138
-
139
- Test 8: Testing API Key Authentication & Route Lockdown...
140
- โœ… Unauthenticated reads and replays rejected with 401.
141
- โœ… Bearer token and x-api-key headers validated with 200.
142
- โœ… Ingress gateway remains open for external webhook providers.
143
-
144
- ๐ŸŽ‰ ALL 8 HARDENED HOOKARMOR VERIFICATION TESTS PASSED PERFECTLY!
145
- ```
146
-
147
- ---
148
-
149
- ## ๐Ÿ”’ Defeating the 5-Minute Stripe Signature Expiration Trap
150
-
151
- Stripe's official SDK (`stripe.webhooks.constructEvent()`) enforces a strict 300-second (5-minute) tolerance on the `Stripe-Signature` timestamp `t=`.
152
-
153
- If you retry a failed webhook 15 minutes later, or replay a dead-letter event tomorrow, **standard Stripe handlers will throw `Webhook signature verification failed`**.
154
-
155
- HookArmor solves this automatically:
156
- 1. **Ingress verification**: Validates the signature at ingress using your webhook signing secret (preventing unauthorized payloads).
157
- 2. **Fresh re-signing on forward & replay**: When HookArmor delivers or replays the event, it re-signs the outbound payload using your secret with a current epoch timestamp `t=now`.
158
- 3. **Zero code changes**: Your existing backend handler code remains completely untouched and verifies 100% of the time.
159
-
160
- ---
161
-
162
- ## ๐Ÿ›ก๏ธ Provenance Headers & Out-of-Order Replay Protection
163
-
164
- When replaying dead-letter events hours or days later, applying payloads out of order can accidentally clobber newer database state (e.g., an older `customer.subscription.updated` event overwriting a newer cancellation).
165
-
166
- To protect downstream handlers against state drift, HookArmor injects explicit provenance headers on every forward and replay:
167
-
168
- * `x-hookarmor-delivery-id`: Unique HookArmor event delivery ID.
169
- * `x-hookarmor-attempt`: Current delivery attempt number (e.g., `1` on initial, `2+` on retries).
170
- * `x-hookarmor-original-timestamp`: The exact ISO-8601 timestamp when the webhook originally arrived at the ingress edge.
171
- * `x-hookarmor-is-replay`: Set to `'true'` during automated retries and manual dashboard replays (`'false'` on initial delivery).
172
- * `x-hookarmor-original-stripe-signature`: Preserves the authentic original Stripe signature for audit logs and forensic verification.
173
-
174
- **Recommended Downstream Handler Pattern:**
175
- ```javascript
176
- app.post('/api/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => {
177
- // Standard verification passes 100% of the time
178
- const event = stripe.webhooks.constructEvent(req.body, req.headers['stripe-signature'], endpointSecret);
179
-
180
- // If this is a replay, verify you are not clobbering newer database state:
181
- if (req.headers['x-hookarmor-is-replay'] === 'true') {
182
- const originalTime = new Date(req.headers['x-hookarmor-original-timestamp']).getTime();
183
- // Fetch latest object from Stripe API or check if local DB already has a newer updated_at timestamp
184
- }
185
-
186
- res.json({ received: true });
187
- });
188
- ```
189
-
190
- ---
191
-
192
- ## ๐Ÿ›๏ธ Dual Trust Boundaries: Transparent vs. Isolated Mode
193
-
194
- HookArmor supports two forwarding architectures depending on your team's security posture:
195
-
196
- 1. **Mode A: Transparent Proxy (Default ยท Zero Code Changes)**
197
- * HookArmor re-signs the outbound request using your provider secret with `t=now`.
198
- * Your existing application code (`stripe.webhooks.constructEvent()`) runs unmodified.
199
- * Original signatures are preserved in `x-hookarmor-original-stripe-signature`.
200
-
201
- 2. **Mode B: Isolated Trust Boundary (Enterprise Security)**
202
- * Set the `HOOKARMOR_SIGNING_SECRET` environment variable.
203
- * HookArmor attaches an isolated `x-hookarmor-signature: t=..., v1=...` HMAC header using your internal secret.
204
- * Downstream handlers verify HookArmor as the internal sender, maintaining strict separation between external Stripe signatures and internal relay deliveries.
205
-
206
- ---
207
-
208
- ## โš ๏ธ Production Durability: The Early 200 OK Tradeoff
209
-
210
- HookArmor returns an immediate `200 OK` to Stripe and Shopify in `<10ms` to protect your ingress latency and prevent providers from marking your endpoint failed during downstream outages.
211
-
212
- **What this means for production operators:**
213
- * Once HookArmor returns `200 OK`, Stripe considers the webhook delivered and **halts its external retry backup**.
214
- * HookArmor's embedded SQLite database assumes **sole custody** of that event.
215
- * **Persistent storage is mandatory:** When deploying via Docker, you must mount the data volume (`-v $(pwd)/data:/app/data`) and ensure host-level backups are active.
216
-
217
- ---
218
-
219
- ## ๐Ÿ“ฆ Hosted Cloud & Self-Hosting
220
-
221
- | Feature | Self-Hosted (MIT) | Hosted Cloud Starter ($29/mo) | Hosted Cloud Pro ($79/mo) |
222
- |---|---|---|---|
223
- | **Ingress Proxy & Buffer** | Unlimited | 50,000 events/mo | 500,000 events/mo |
224
- | **Instant 200 OK Ack** | Yes (<10ms) | Yes (<10ms) | Yes (<10ms) |
225
- | **Dead-Letter Queue (DLQ)** | Yes | Yes | Yes |
226
- | **Stripe Signature Re-Signing** | Yes | Yes | Yes |
227
- | **Background Auto-Retries** | Yes | Yes | Yes |
228
- | **Concurrency Pool Limiter** | Yes | Yes | Yes |
229
- | **Event Retention** | Local Disk | 30 days | 90 days |
230
- | **Alerting** | Discord / Slack Webhook | Discord / Slack Webhook | Priority alerts + PagerDuty |
231
- | **Infrastructure** | Your own server | Fully managed & redundant | Fully managed & redundant |
232
-
233
- ---
234
-
235
- ## ๐Ÿ” Production Security & Admin Authentication
236
-
237
- When running HookArmor locally on your laptop (`localhost`), management endpoints and the replay dashboard operate without authentication for fast developer onboarding.
238
-
239
- > [!WARNING]
240
- > **When deploying HookArmor to a public server or self-hosted cloud container (Railway, Render, VPS), you MUST set the `HOOKARMOR_API_KEY` environment variable.**
241
-
242
- ```bash
243
- # Generate a strong 256-bit random key
244
- openssl rand -hex 32
245
-
246
- # Set in your production environment / Docker container:
247
- HOOKARMOR_API_KEY=ha_sec_your_secure_random_key_here
248
- ```
249
-
250
- When `HOOKARMOR_API_KEY` is present:
251
- * All management and inspection endpoints (`/api/stats`, `/api/endpoints`, `/api/events`, `/api/events/:id/replay`, `/api/events/replay-all`) strictly reject unauthenticated requests with `401 Unauthorized` and require `Authorization: Bearer <key>` or `X-Api-Key: <key>`.
252
- * Ingress webhook receiving (`/in/:endpointId`) and public waitlist signups remain open for incoming traffic.
253
- * Public read-only demo access can only be enabled if explicitly running with `HOOKARMOR_DEMO_MODE=true` (for isolated marketing sandboxes).
254
- * Query parameter authentication (`?api_key=...`) is strictly prohibited to avoid leaking tokens into browser history and proxy access logs.
255
- * The web dashboard displays an **Admin Login** prompt storing your key only in ephemeral session memory.
256
-
257
- ---
258
-
259
- ## ๐Ÿ“„ License
260
- MIT License. Created by the HookArmor Team.
1
+ # ๐Ÿ›ก๏ธ HookArmor
2
+
3
+ > **Zero-loss Webhook Dead-Letter Queue (DLQ), Reliability Proxy, and Replay Gateway for Stripe, Shopify, Clerk, and modern B2B SaaS.**
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+ [![Tests: Passing](https://img.shields.io/badge/Tests-38%20Passing-brightgreen.svg)]()
7
+ [![Status: Production Ready](https://img.shields.io/badge/Status-v1.1.1-blueviolet.svg)]()
8
+
9
+ ---
10
+
11
+ ## ๐Ÿ’ฅ The Problem HookArmor Solves
12
+
13
+ Every developer using Stripe, Shopify, GitHub, Clerk, Paddle, or custom webhooks faces catastrophic silent revenue loss when:
14
+ 1. **Serverless cold starts & timeouts**: Next.js, Vercel, or AWS Lambda cold starts hit the 10-15s webhook limit.
15
+ 2. **Zero-downtime deploy reboots**: Container restarts on Render, Railway, or Fly.io return transient `502 Bad Gateway`.
16
+ 3. **Database connection pool exhaustion**: Prisma or Postgres locks during sudden traffic spikes cause unhandled `500 Internal Server Error`.
17
+ 4. **Stripe's painful retry schedule**: When Stripe receives a failure, it backs off exponentially (1 hour, then several hours, then days). Meanwhile, your paying customer logs in, sees "Free Plan", assumes a scam, and disputes the charge or issues a refund.
18
+
19
+ ---
20
+
21
+ ## โšก How HookArmor Works
22
+
23
+ ```
24
+ [Stripe / Shopify / Clerk]
25
+ โ”‚ (POST webhook)
26
+ โ–ผ
27
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
28
+ โ”‚ HookArmor Ingress Edge โ”‚
29
+ โ”‚ - Returns immediate 200 OK (<10ms) โ”‚
30
+ โ”‚ - Stores raw payload in SQLite DLQ โ”‚
31
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
32
+ โ”‚
33
+ โ–ผ
34
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
35
+ โ”‚ Relay Dispatch Engine โ”‚
36
+ โ”‚ - Forwards request to user's server โ”‚
37
+ โ”‚ - Preserves exact headers & HMAC sigsโ”‚
38
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
39
+ โ”‚ โ”‚
40
+ (2xx Success) (5xx / 4xx / Timeout)
41
+ โ–ผ โ–ผ
42
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
43
+ โ”‚ Event Archive โ”‚ โ”‚ Dead-Letter Queue โ”‚
44
+ โ”‚ - Latency log โ”‚ โ”‚ - Capture error code & body โ”‚
45
+ โ”‚ - Status 200 โ”‚ โ”‚ - Instant Discord / Slack Alert โ”‚
46
+ โ”‚ โ”‚ โ”‚ - Smart exponential auto-retry โ”‚
47
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ - 1-Click UI & CLI Replay Engine โ”‚
48
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
49
+ ```
50
+
51
+ 1. **Sub-10ms Ingest**: HookArmor returns an immediate `200 OK` to the sender so Stripe or Shopify never marks the event failed.
52
+ 2. **Cryptographic Header & Signature Preservation**: Forwards exact raw bytes, `stripe-signature`, `x-shopify-hmac-sha256`, and timestamps.
53
+ 3. **Dead-Letter Queue (DLQ)**: If your server returns 500, 502, 504, 429, or times out, HookArmor safely preserves the raw event with full error diagnostics.
54
+ 4. **Instant Alerts**: Sends immediate Slack / Discord webhooks when an endpoint begins failing.
55
+ 5. **1-Click Bulk Replay**: As soon as you push your code fix, hit **Replay All Dead-Letter** in the Web UI or run `hookarmor replay --failed` to re-deliver customer transactions.
56
+ 6. **Local Dev Relay**: `hookarmor listen http://localhost:3000/api/webhooks` to proxy webhooks straight into local dev servers with automatic re-signing.
57
+
58
+ ---
59
+
60
+ ## ๐Ÿš€ Quickstart
61
+
62
+ ### 1-Click Cloud Deployment (Free & Self-Hosted)
63
+
64
+ Deploy your own private, persistent HookArmor instance to the cloud with one click:
65
+
66
+ > **Before you deploy:** production instances refuse to start without `HOOKARMOR_API_KEY` (Render generates one automatically). Events live in SQLite under `/app/data`, so that path must be on persistent storage: Render's blueprint attaches a disk (paid `starter` plan, since free instances have no persistent disk), and on **Railway you must add a Volume mounted at `/app/data`** or every redeploy wipes the queue.
67
+
68
+ [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template?template=https://github.com/pkdoddamani/hookarmor)
69
+ [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/pkdoddamani/hookarmor)
70
+
71
+ ---
72
+
73
+ ### Local Quickstart
74
+
75
+ #### Option A: Docker Compose
76
+ ```bash
77
+ git clone https://github.com/pkdoddamani/hookarmor.git
78
+ cd hookarmor
79
+ export HOOKARMOR_API_KEY=$(openssl rand -hex 32) # required: protects the management API
80
+ docker compose up -d
81
+ ```
82
+
83
+ #### Option B: Terminal CLI
84
+ ```bash
85
+ npx hookarmor start
86
+ ```
87
+ * **Web Dashboard**: `http://localhost:4000/dashboard`
88
+ * **Ingress Gateway**: `http://localhost:4000/in/:endpointId`
89
+
90
+ ### 2. Tunnel Direct to Localhost
91
+ ```bash
92
+ npx hookarmor listen http://localhost:3000/api/webhooks/stripe
93
+ ```
94
+
95
+ ### 3. Replay Failed Dead-Letter Events
96
+ ```bash
97
+ # Replay all failed events across all endpoints (including ones that used up their automatic retries)
98
+ npx hookarmor replay --failed --api-key "$HOOKARMOR_API_KEY"
99
+
100
+ # Replay a specific event ID
101
+ npx hookarmor replay evt_1758513516086_m8r0e7
102
+ ```
103
+
104
+ ---
105
+
106
+ ## ๐Ÿ“Š Verification Test Suite
107
+
108
+ HookArmor includes an 8-scenario verification suite (`test/verify.js`) plus a 26-case regression suite (`test/regression.js`) covering retry scheduling, crash recovery, duplicate suppression, signature verification for every supported provider, outbound address validation, authentication and dashboard escaping:
109
+ ```bash
110
+ npm test
111
+ ```
112
+ ```
113
+ ๐Ÿงช Starting HookArmor Hardened Verification Test Suite (Post-Audit)...
114
+
115
+ Test 1: Testing SSRF Protection & Endpoint Creation...
116
+ โœ… SSRF probe against 169.254.169.254 successfully blocked.
117
+ โœ… Valid endpoint created with secret.
118
+
119
+ Test 2: Testing Ingress Signature Verification (Security Boundary)...
120
+ โœ… Forged signature rejected with 400 Bad Request.
121
+ โœ… Authentic signature verified, ingested, and delivered.
122
+
123
+ Test 3: Testing 5-Minute Expiration Defeat (Fresh Outbound Re-Signing)...
124
+ โœ… HookArmor defeated the 5-minute Stripe expiration trap:
125
+ Stored in DB: t=1600000000 (Expired 5+ years ago)
126
+ Re-signed on replay: t=1790970445 (Current) -> stripe.webhooks.constructEvent succeeds!
127
+ Provenance verified: is-replay=true, orig-sig preserved.
128
+
129
+ Test 4: Simulating Downstream Failure (500) -> Dead-Letter Queue...
130
+ โœ… Event safely quarantined in Dead-Letter Queue with HTTP 500.
131
+ Next automated retry scheduled with exponential backoff + jitter.
132
+
133
+ Test 5: Testing Background Retry Worker (Automatic Self-Healing)...
134
+ โœ… Background Retry Worker automatically claimed and delivered DLQ event! (Attempts: 2)
135
+
136
+ Test 6: Testing Concurrency Limiting (Pool Protection)...
137
+ โœ… Concurrency capped at max 2 parallel in-flight connections (pool protected).
138
+
139
+ Test 7: Testing Safe Idempotency Key Deduplication...
140
+ โœ… Duplicate event intercepted and suppressed cleanly.
141
+
142
+ Test 8: Testing API Key Authentication & Route Lockdown...
143
+ โœ… Unauthenticated reads and replays rejected with 401.
144
+ โœ… Bearer token and x-api-key headers validated with 200.
145
+ โœ… Ingress gateway remains open for external webhook providers.
146
+
147
+ ๐ŸŽ‰ ALL 8 HARDENED HOOKARMOR VERIFICATION TESTS PASSED PERFECTLY!
148
+ ```
149
+
150
+ ---
151
+
152
+ ## ๐Ÿ”’ Defeating the 5-Minute Stripe Signature Expiration Trap
153
+
154
+ Stripe's official SDK (`stripe.webhooks.constructEvent()`) enforces a strict 300-second (5-minute) tolerance on the `Stripe-Signature` timestamp `t=`.
155
+
156
+ If you retry a failed webhook 15 minutes later, or replay a dead-letter event tomorrow, **standard Stripe handlers will throw `Webhook signature verification failed`**.
157
+
158
+ HookArmor solves this automatically:
159
+ 1. **Ingress verification**: Validates the signature at ingress using your webhook signing secret (preventing unauthorized payloads).
160
+ 2. **Fresh re-signing on forward & replay**: When HookArmor delivers or replays the event, it re-signs the outbound payload using your secret with a current epoch timestamp `t=now`.
161
+ 3. **Zero code changes**: Your existing backend handler code remains completely untouched and verifies 100% of the time.
162
+
163
+ ---
164
+
165
+ ## ๐Ÿ›ก๏ธ Provenance Headers & Out-of-Order Replay Protection
166
+
167
+ When replaying dead-letter events hours or days later, applying payloads out of order can accidentally clobber newer database state (e.g., an older `customer.subscription.updated` event overwriting a newer cancellation).
168
+
169
+ To protect downstream handlers against state drift, HookArmor injects explicit provenance headers on every forward and replay:
170
+
171
+ * `x-hookarmor-delivery-id`: Unique HookArmor event delivery ID.
172
+ * `x-hookarmor-attempt`: Current delivery attempt number (e.g., `1` on initial, `2+` on retries).
173
+ * `x-hookarmor-original-timestamp`: The exact ISO-8601 timestamp when the webhook originally arrived at the ingress edge.
174
+ * `x-hookarmor-is-replay`: Set to `'true'` during automated retries and manual dashboard replays (`'false'` on initial delivery).
175
+ * `x-hookarmor-original-stripe-signature`: Preserves the authentic original Stripe signature for audit logs and forensic verification.
176
+
177
+ **Recommended Downstream Handler Pattern:**
178
+ ```javascript
179
+ app.post('/api/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => {
180
+ // Standard verification passes 100% of the time
181
+ const event = stripe.webhooks.constructEvent(req.body, req.headers['stripe-signature'], endpointSecret);
182
+
183
+ // If this is a replay, verify you are not clobbering newer database state:
184
+ if (req.headers['x-hookarmor-is-replay'] === 'true') {
185
+ const originalTime = new Date(req.headers['x-hookarmor-original-timestamp']).getTime();
186
+ // Fetch latest object from Stripe API or check if local DB already has a newer updated_at timestamp
187
+ }
188
+
189
+ res.json({ received: true });
190
+ });
191
+ ```
192
+
193
+ ---
194
+
195
+ ## ๐Ÿ›๏ธ Dual Trust Boundaries: Transparent vs. Isolated Mode
196
+
197
+ HookArmor supports two forwarding architectures depending on your team's security posture:
198
+
199
+ 1. **Mode A: Transparent Proxy (Default ยท Zero Code Changes)**
200
+ * HookArmor re-signs the outbound request using your provider secret with `t=now`.
201
+ * Your existing application code (`stripe.webhooks.constructEvent()`) runs unmodified.
202
+ * Original signatures are preserved in `x-hookarmor-original-stripe-signature`.
203
+
204
+ 2. **Mode B: Isolated Trust Boundary (Enterprise Security)**
205
+ * Set the `HOOKARMOR_SIGNING_SECRET` environment variable.
206
+ * HookArmor attaches an isolated `x-hookarmor-signature: t=..., v1=...` HMAC header using your internal secret.
207
+ * The header is only attached to events whose provider signature was **verified at ingress**, so the endpoint must have its provider secret configured. Events on endpoints without a secret are forwarded without it.
208
+ * Downstream handlers verify HookArmor as the internal sender, maintaining strict separation between external Stripe signatures and internal relay deliveries.
209
+
210
+ ---
211
+
212
+ ## โš ๏ธ Production Durability: The Early 200 OK Tradeoff
213
+
214
+ HookArmor returns an immediate `200 OK` to Stripe and Shopify in `<10ms` to protect your ingress latency and prevent providers from marking your endpoint failed during downstream outages.
215
+
216
+ **What this means for production operators:**
217
+ * Once HookArmor returns `200 OK`, Stripe considers the webhook delivered and **halts its external retry backup**.
218
+ * HookArmor's embedded SQLite database assumes **sole custody** of that event.
219
+ * **Persistent storage is mandatory:** When deploying via Docker, you must mount the data volume (`-v $(pwd)/data:/app/data`) and ensure host-level backups are active.
220
+ * Every commit is fsynced (`synchronous=FULL`) before the `200 OK` is sent, so an acknowledged event survives a crash or power loss of the HookArmor host. It is still a single SQLite file on a single disk: back it up, and run one HookArmor process per database.
221
+ * If the process stops mid-delivery, those events are re-queued automatically on the next start.
222
+
223
+ ---
224
+
225
+ ## ๐Ÿ“ฆ Hosted Cloud & Self-Hosting
226
+
227
+ HookArmor is 100% free and open-source under the MIT license. Managed cloud tiers are currently in private waitlist preview.
228
+
229
+ | Feature | Self-Hosted OSS (MIT) | Hosted Cloud Starter (Waitlist) | Hosted Cloud Pro (Waitlist) |
230
+ |---|---|---|---|
231
+ | **Ingress Proxy & Buffer** | Unlimited | 50,000 events/mo | 500,000 events/mo |
232
+ | **Instant 200 OK Ack** | Yes (<10ms) | Yes (<10ms) | Yes (<10ms) |
233
+ | **Dead-Letter Queue (DLQ)** | Yes | Yes | Yes |
234
+ | **Stripe Signature Re-Signing** | Yes | Yes | Yes |
235
+ | **Background Auto-Retries** | Yes | Yes | Yes |
236
+ | **Concurrency Pool Limiter** | Yes | Yes | Yes |
237
+ | **Event Retention** | Local Disk (Configurable) | 30 days | 90 days |
238
+ | **Alerting** | Discord / Slack Webhooks | Discord / Slack Webhooks | Priority Webhooks |
239
+ | **Infrastructure** | Single-node Docker / VPS | Fully managed & redundant | Fully managed & redundant |
240
+
241
+ ---
242
+
243
+ ## ๐Ÿ” Production Security & Admin Authentication
244
+
245
+ When running HookArmor locally on your laptop (`localhost`), management endpoints and the replay dashboard operate without authentication for fast developer onboarding.
246
+
247
+ > [!WARNING]
248
+ > **When deploying HookArmor to a public server or self-hosted cloud container (Railway, Render, VPS), you MUST set the `HOOKARMOR_API_KEY` environment variable.**
249
+
250
+ ```bash
251
+ # Generate a strong 256-bit random key
252
+ openssl rand -hex 32
253
+
254
+ # Set in your production environment / Docker container:
255
+ HOOKARMOR_API_KEY=ha_sec_your_secure_random_key_here
256
+ ```
257
+
258
+ When `HOOKARMOR_API_KEY` is **not** set, the management API only answers requests addressed to `localhost`, and the server refuses to start in production (`NODE_ENV=production`, Railway or Render) unless `HOOKARMOR_ALLOW_NO_AUTH=true`.
259
+
260
+ When `HOOKARMOR_API_KEY` is present:
261
+ * All management and inspection endpoints (`/api/stats`, `/api/endpoints`, `/api/events`, `/api/events/:id/replay`, `/api/events/replay-all`) strictly reject unauthenticated requests with `401 Unauthorized` and require `Authorization: Bearer <key>` or `X-Api-Key: <key>`.
262
+ * Ingress webhook receiving (`/in/:endpointId`) and public waitlist signups remain open for incoming traffic.
263
+ * Public demo access can only be enabled with `HOOKARMOR_DEMO_MODE=true` (for isolated marketing sandboxes) and is strictly read-only: replays always require the key.
264
+ * The live dashboard feed (`/ws`) only streams events after the client authenticates with the key.
265
+ * Endpoint signing secrets are write-only: the API reports `has_secret` but never returns the secret.
266
+ * Endpoints with a secret reject any request that does not carry a valid Stripe, Shopify, GitHub or Svix/Clerk signature.
267
+ * State-changing API calls must use `Content-Type: application/json`; CORS is disabled unless you list origins in `HOOKARMOR_CORS_ORIGINS`.
268
+ * Query parameter authentication (`?api_key=...`) is strictly prohibited to avoid leaking tokens into browser history and proxy access logs.
269
+ * The web dashboard displays an **Admin Login** prompt storing your key only in ephemeral session memory.
270
+
271
+ ---
272
+
273
+ ## โš™๏ธ Configuration
274
+
275
+ | Variable | Default | Purpose |
276
+ |---|---|---|
277
+ | `HOOKARMOR_API_KEY` | none (required in production) | Admin key for the management API, dashboard and CLI |
278
+ | `HOOKARMOR_STRICT_SSRF` | `true` in production, `false` locally | Block loopback, private-network and link-local delivery targets (checked on the resolved IP at delivery time). Cloud metadata addresses are always blocked |
279
+ | `HOOKARMOR_SIGNING_SECRET` | none | Enables Mode B internal signatures |
280
+ | `HOOKARMOR_DATA_DIR` | `./data` (current directory) | Where `hookarmor.db` is stored |
281
+ | `HOOKARMOR_RETENTION_DAYS` | `0` (keep forever) | Delete delivered events older than this many days; failed events are always kept |
282
+ | `HOOKARMOR_MAX_BODY` | `10mb` | Maximum webhook body size |
283
+ | `HOOKARMOR_ENABLE_MOCK` | `false` in production | Mount the `/mock/*` simulation receiver |
284
+ | `HOOKARMOR_CORS_ORIGINS` | none | Comma-separated origins allowed to call `/api` from a browser |
285
+ | `HOOKARMOR_TRUST_PROXY` | `1` on Railway/Render | Express `trust proxy` setting, used for per-client rate limits |
286
+ | `HOOKARMOR_DEMO_MODE` | `false` | Read-only public demo |
287
+ | `HOOKARMOR_ALLOW_NO_AUTH` | `false` | Allow production start without an API key (trusted networks only) |
288
+
289
+ ## ๐Ÿ“ฆ Upgrade Notes (v1.1.0)
290
+
291
+ v1.1.0 includes core engine hardening, security perimeter lockdowns, and reliability enhancements. Please review the following behavior changes when upgrading:
292
+
293
+ 1. **Mandatory Production API Key**: HookArmor refuses to start in `NODE_ENV=production` without `HOOKARMOR_API_KEY` unless `HOOKARMOR_ALLOW_NO_AUTH=true` is set.
294
+ 2. **Strict Content-Type Enforced**: State-changing API routes (`POST`, `PUT`, `DELETE`) require `Content-Type: application/json`. Requests with missing or invalid content types receive HTTP `415 Unsupported Media Type`.
295
+ 3. **Unsigned Webhooks Rejected on Secured Endpoints**: Ingress webhooks to endpoints configured with a signing secret now strictly require a recognized cryptographic signature header (`stripe-signature`, `x-shopify-hmac-sha256`, `x-hub-signature-256`, or `svix-signature`). Unsigned requests are rejected with HTTP 400.
296
+ 4. **Mode B Internal Signatures**: Internal re-signing (`x-hookarmor-signature`) is now only attached to events verified at ingress.
297
+ 5. **Non-blocking Replay-All**: `POST /api/events/replay-all` immediately claims up to 500 failed events and returns `{ replayedCount, eventIds, remaining }`, delivering them asynchronously in the background.
298
+ 6. **Data Storage Directory**: Default database path is `./data` in current working directory (overridable with `HOOKARMOR_DATA_DIR`). Docker deployments using `/app/data` are unaffected.
299
+ 7. **Root Route Behavior**: Self-hosted instances serve the interactive Dashboard at `/` by default. Set `HOOKARMOR_SERVE_LANDING=true` to serve the marketing landing page.
300
+ 8. **Notification Throttling**: Alert webhooks trigger on the first delivery failure and upon final dead-letter queue exhaustion to eliminate alert flooding during outages.
301
+
302
+ ---
303
+
304
+ ## ๐Ÿ“„ License
305
+ MIT License. Created by the HookArmor Team.