hookarmor 1.0.3 โ 1.1.0
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 +303 -260
- package/bin/hookarmor.js +168 -133
- package/package.json +52 -52
- package/src/alerter.js +72 -63
- package/src/dispatcher.js +409 -356
- package/src/netguard.js +117 -0
- package/src/public/index.html +96 -40
- package/src/server.js +544 -393
- package/src/storage.js +337 -266
- package/src/worker.js +72 -62
package/README.md
CHANGED
|
@@ -1,260 +1,303 @@
|
|
|
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
|
-
[](https://opensource.org/licenses/MIT)
|
|
6
|
-
[. 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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
# Replay
|
|
98
|
-
npx hookarmor replay
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
โ
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
โ
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
Test
|
|
134
|
-
โ
|
|
135
|
-
|
|
136
|
-
Test
|
|
137
|
-
โ
|
|
138
|
-
|
|
139
|
-
Test
|
|
140
|
-
โ
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
* `x-hookarmor-
|
|
172
|
-
* `x-hookarmor-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
*
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
|
228
|
-
|
|
229
|
-
| **
|
|
230
|
-
| **
|
|
231
|
-
| **
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[]()
|
|
7
|
+
[]()
|
|
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
|
+
> **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
|
+
[](https://railway.app/template?template=https://github.com/pkdoddamani/hookarmor)
|
|
69
|
+
[](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
|
+
| Feature | Self-Hosted (MIT) | Hosted Cloud Starter ($29/mo) | Hosted Cloud Pro ($79/mo) |
|
|
228
|
+
|---|---|---|---|
|
|
229
|
+
| **Ingress Proxy & Buffer** | Unlimited | 50,000 events/mo | 500,000 events/mo |
|
|
230
|
+
| **Instant 200 OK Ack** | Yes (<10ms) | Yes (<10ms) | Yes (<10ms) |
|
|
231
|
+
| **Dead-Letter Queue (DLQ)** | Yes | Yes | Yes |
|
|
232
|
+
| **Stripe Signature Re-Signing** | Yes | Yes | Yes |
|
|
233
|
+
| **Background Auto-Retries** | Yes | Yes | Yes |
|
|
234
|
+
| **Concurrency Pool Limiter** | Yes | Yes | Yes |
|
|
235
|
+
| **Event Retention** | Local Disk | 30 days | 90 days |
|
|
236
|
+
| **Alerting** | Discord / Slack Webhook | Discord / Slack Webhook | Priority alerts + PagerDuty |
|
|
237
|
+
| **Infrastructure** | Your own server | Fully managed & redundant | Fully managed & redundant |
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## ๐ Production Security & Admin Authentication
|
|
242
|
+
|
|
243
|
+
When running HookArmor locally on your laptop (`localhost`), management endpoints and the replay dashboard operate without authentication for fast developer onboarding.
|
|
244
|
+
|
|
245
|
+
> [!WARNING]
|
|
246
|
+
> **When deploying HookArmor to a public server or self-hosted cloud container (Railway, Render, VPS), you MUST set the `HOOKARMOR_API_KEY` environment variable.**
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
# Generate a strong 256-bit random key
|
|
250
|
+
openssl rand -hex 32
|
|
251
|
+
|
|
252
|
+
# Set in your production environment / Docker container:
|
|
253
|
+
HOOKARMOR_API_KEY=ha_sec_your_secure_random_key_here
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
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`.
|
|
257
|
+
|
|
258
|
+
When `HOOKARMOR_API_KEY` is present:
|
|
259
|
+
* 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>`.
|
|
260
|
+
* Ingress webhook receiving (`/in/:endpointId`) and public waitlist signups remain open for incoming traffic.
|
|
261
|
+
* 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.
|
|
262
|
+
* The live dashboard feed (`/ws`) only streams events after the client authenticates with the key.
|
|
263
|
+
* Endpoint signing secrets are write-only: the API reports `has_secret` but never returns the secret.
|
|
264
|
+
* Endpoints with a secret reject any request that does not carry a valid Stripe, Shopify, GitHub or Svix/Clerk signature.
|
|
265
|
+
* State-changing API calls must use `Content-Type: application/json`; CORS is disabled unless you list origins in `HOOKARMOR_CORS_ORIGINS`.
|
|
266
|
+
* Query parameter authentication (`?api_key=...`) is strictly prohibited to avoid leaking tokens into browser history and proxy access logs.
|
|
267
|
+
* The web dashboard displays an **Admin Login** prompt storing your key only in ephemeral session memory.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## โ๏ธ Configuration
|
|
272
|
+
|
|
273
|
+
| Variable | Default | Purpose |
|
|
274
|
+
|---|---|---|
|
|
275
|
+
| `HOOKARMOR_API_KEY` | none (required in production) | Admin key for the management API, dashboard and CLI |
|
|
276
|
+
| `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 |
|
|
277
|
+
| `HOOKARMOR_SIGNING_SECRET` | none | Enables Mode B internal signatures |
|
|
278
|
+
| `HOOKARMOR_DATA_DIR` | `./data` (current directory) | Where `hookarmor.db` is stored |
|
|
279
|
+
| `HOOKARMOR_RETENTION_DAYS` | `0` (keep forever) | Delete delivered events older than this many days; failed events are always kept |
|
|
280
|
+
| `HOOKARMOR_MAX_BODY` | `10mb` | Maximum webhook body size |
|
|
281
|
+
| `HOOKARMOR_ENABLE_MOCK` | `false` in production | Mount the `/mock/*` simulation receiver |
|
|
282
|
+
| `HOOKARMOR_CORS_ORIGINS` | none | Comma-separated origins allowed to call `/api` from a browser |
|
|
283
|
+
| `HOOKARMOR_TRUST_PROXY` | `1` on Railway/Render | Express `trust proxy` setting, used for per-client rate limits |
|
|
284
|
+
| `HOOKARMOR_DEMO_MODE` | `false` | Read-only public demo |
|
|
285
|
+
| `HOOKARMOR_ALLOW_NO_AUTH` | `false` | Allow production start without an API key (trusted networks only) |
|
|
286
|
+
|
|
287
|
+
## ๐ฆ Upgrade Notes (v1.1.0)
|
|
288
|
+
|
|
289
|
+
v1.1.0 includes core engine hardening, security perimeter lockdowns, and reliability enhancements. Please review the following behavior changes when upgrading:
|
|
290
|
+
|
|
291
|
+
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.
|
|
292
|
+
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`.
|
|
293
|
+
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.
|
|
294
|
+
4. **Mode B Internal Signatures**: Internal re-signing (`x-hookarmor-signature`) is now only attached to events verified at ingress.
|
|
295
|
+
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.
|
|
296
|
+
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.
|
|
297
|
+
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.
|
|
298
|
+
8. **Notification Throttling**: Alert webhooks trigger on the first delivery failure and upon final dead-letter queue exhaustion to eliminate alert flooding during outages.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## ๐ License
|
|
303
|
+
MIT License. Created by the HookArmor Team.
|