@gfargo/doorman 3.14.0 → 3.14.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.
@@ -2,6 +2,8 @@
2
2
 
3
3
  Complete reference for creating and configuring Doorman firewall rules.
4
4
 
5
+ > **Format note:** this reference covers the **legacy (Vercel-only) rule format** — no `provider`/`providers` field in the config. If your config has `provider`/`providers` set (required for Cloudflare/Fastly), rules use a different shape entirely (`conditions`/`enabled`/flat `action: {type}` instead of `conditionGroup`/`active`/`action: {mitigate}`) — see [cloudflare.md](cloudflare.md)/[fastly.md](fastly.md) for the correct format and field/operator/action support per provider, and `SKILL.md`'s "Rule Shape" section for a side-by-side comparison.
6
+
5
7
  ## Rule Structure
6
8
 
7
9
  Every rule in the `rules` array has this shape:
@@ -13,8 +15,16 @@ Every rule in the `rules` array has this shape:
13
15
  "description": "What this rule does and why",
14
16
  "active": true,
15
17
  "conditionGroup": [
16
- { "conditions": [/* AND — all must match */] },
17
- { "conditions": [/* OR — this group is an alternative */] }
18
+ {
19
+ "conditions": [
20
+ /* AND — all must match */
21
+ ]
22
+ },
23
+ {
24
+ "conditions": [
25
+ /* OR — this group is an alternative */
26
+ ]
27
+ }
18
28
  ],
19
29
  "action": { "mitigate": { "action": "deny" } }
20
30
  }
@@ -22,14 +32,14 @@ Every rule in the `rules` array has this shape:
22
32
 
23
33
  ### Fields
24
34
 
25
- | Field | Type | Required | Description |
26
- |-------|------|----------|-------------|
27
- | `id` | string | No | Unique identifier. Convention: `rule_` prefix, snake_case. Auto-generated if omitted. |
28
- | `name` | string | Yes | Display name shown in tables and logs. |
29
- | `description` | string | No | Explains the rule's purpose. Improves health score. |
30
- | `active` | boolean | Yes | `true` to enforce, `false` to disable without deleting. |
31
- | `conditionGroup` | array | Yes | Array of condition groups (see below). |
32
- | `action` | object | Yes | What to do when conditions match (see below). |
35
+ | Field | Type | Required | Description |
36
+ | ---------------- | ------- | -------- | ------------------------------------------------------------------------------------- |
37
+ | `id` | string | No | Unique identifier. Convention: `rule_` prefix, snake_case. Auto-generated if omitted. |
38
+ | `name` | string | Yes | Display name shown in tables and logs. |
39
+ | `description` | string | No | Explains the rule's purpose. Improves health score. |
40
+ | `active` | boolean | Yes | `true` to enforce, `false` to disable without deleting. |
41
+ | `conditionGroup` | array | Yes | Array of condition groups (see below). |
42
+ | `action` | object | Yes | What to do when conditions match (see below). |
33
43
 
34
44
  ## Condition Groups
35
45
 
@@ -41,6 +51,7 @@ Every rule in the `rules` array has this shape:
41
51
  ```
42
52
 
43
53
  **Logic**:
54
+
44
55
  - Conditions **within** a group: AND (all must match)
45
56
  - **Between** groups: OR (any group matching triggers the rule)
46
57
 
@@ -52,35 +63,36 @@ Each condition has:
52
63
  { "type": "field_type", "op": "operator", "value": "match_value", "key": "header_name", "neg": false }
53
64
  ```
54
65
 
55
- | Field | Type | Required | Description |
56
- |-------|------|----------|-------------|
57
- | `type` | string | Yes | What to match against (see types below). |
58
- | `op` | string | Yes | How to compare (see operators below). |
59
- | `value` | string/number/array | Yes | Value to match. Arrays for `inc` operator. |
60
- | `key` | string | No | Required for `header`, `query`, `cookie` types. |
61
- | `neg` | boolean | No | `true` to negate the condition (NOT logic). Default: `false`. |
66
+ | Field | Type | Required | Description |
67
+ | ------- | ------------------- | -------- | ------------------------------------------------------------- |
68
+ | `type` | string | Yes | What to match against (see types below). |
69
+ | `op` | string | Yes | How to compare (see operators below). |
70
+ | `value` | string/number/array | Yes | Value to match. Arrays for `inc` operator. |
71
+ | `key` | string | No | Required for `header`, `query`, `cookie` types. |
72
+ | `neg` | boolean | No | `true` to negate the condition (NOT logic). Default: `false`. |
62
73
 
63
74
  ### Condition Types
64
75
 
65
- | Type | Description | Example Value |
66
- |------|-------------|---------------|
67
- | `path` | URL path | `"/api/users"` |
68
- | `method` | HTTP method | `"POST"` |
69
- | `host` | Hostname | `"example.com"` |
70
- | `user_agent` | User-Agent header | `"Googlebot"` |
71
- | `ip_address` | Client IP | `"192.168.1.1"` |
72
- | `header` | HTTP header (requires `key`) | `"application/json"` |
73
- | `query` | Query parameter (requires `key`) | `"true"` |
74
- | `cookie` | Cookie value (requires `key`) | `"session_abc"` |
75
- | `geo_country` | Country code (ISO 3166-1) | `"US"` or `["US","CA"]` |
76
- | `geo_city` | City name | `"New York"` |
77
- | `geo_continent` | Continent code | `"NA"` |
78
- | `geo_country_region` | Region/state code | `"CA"` |
79
- | `geo_as_number` | ASN number | `13335` |
80
- | `scheme` | URL scheme | `"https"` |
81
- | `protocol` | HTTP protocol version | `"HTTP/2"` |
76
+ | Type | Description | Example Value |
77
+ | -------------------- | -------------------------------- | ----------------------- |
78
+ | `path` | URL path | `"/api/users"` |
79
+ | `method` | HTTP method | `"POST"` |
80
+ | `host` | Hostname | `"example.com"` |
81
+ | `user_agent` | User-Agent header | `"Googlebot"` |
82
+ | `ip_address` | Client IP | `"192.168.1.1"` |
83
+ | `header` | HTTP header (requires `key`) | `"application/json"` |
84
+ | `query` | Query parameter (requires `key`) | `"true"` |
85
+ | `cookie` | Cookie value (requires `key`) | `"session_abc"` |
86
+ | `geo_country` | Country code (ISO 3166-1) | `"US"` or `["US","CA"]` |
87
+ | `geo_city` | City name | `"New York"` |
88
+ | `geo_continent` | Continent code | `"NA"` |
89
+ | `geo_country_region` | Region/state code | `"CA"` |
90
+ | `geo_as_number` | ASN number | `13335` |
91
+ | `scheme` | URL scheme | `"https"` |
92
+ | `protocol` | HTTP protocol version | `"HTTP/2"` |
82
93
 
83
94
  **Vercel-only types** (not available on Cloudflare):
95
+
84
96
  - `environment` — deployment environment (`"production"`, `"preview"`)
85
97
  - `ja3_digest` — TLS fingerprint
86
98
  - `ja4_digest` — TLS fingerprint v4
@@ -89,16 +101,16 @@ Each condition has:
89
101
 
90
102
  ### Operators
91
103
 
92
- | Operator | Name | Description | Value Type |
93
- |----------|------|-------------|------------|
94
- | `eq` | Equals | Exact match | string/number |
95
- | `pre` | Prefix | Starts with | string |
96
- | `suf` | Suffix | Ends with | string |
97
- | `sub` | Substring | Contains | string |
98
- | `inc` | Includes | Is any of (array match) | string[] |
99
- | `re` | Regex | Regular expression match | string (regex pattern) |
100
- | `ex` | Exists | Field/header exists | `true` |
101
- | `nex` | Not Exists | Field/header does not exist | `true` |
104
+ | Operator | Name | Description | Value Type |
105
+ | -------- | ---------- | --------------------------- | ---------------------- |
106
+ | `eq` | Equals | Exact match | string/number |
107
+ | `pre` | Prefix | Starts with | string |
108
+ | `suf` | Suffix | Ends with | string |
109
+ | `sub` | Substring | Contains | string |
110
+ | `inc` | Includes | Is any of (array match) | string[] |
111
+ | `re` | Regex | Regular expression match | string (regex pattern) |
112
+ | `ex` | Exists | Field/header exists | `true` |
113
+ | `nex` | Not Exists | Field/header does not exist | `true` |
102
114
 
103
115
  ### Using `key` for Header/Query/Cookie
104
116
 
@@ -154,13 +166,13 @@ Duration formats: `"30s"`, `"5m"`, `"1h"`, `"1d"`, `"permanent"`
154
166
  }
155
167
  ```
156
168
 
157
- | Field | Type | Required | Description |
158
- |-------|------|----------|-------------|
159
- | `requests` | number | Yes | Max requests allowed in window. |
160
- | `window` | string | Yes | Time window: `"10s"`, `"1m"`, `"5m"`, `"1h"` |
161
- | `characteristics` | string[] | No | What to rate limit by. Default: `["ip.src"]`. Options: `"ip.src"`, `"http.request.uri.path"`, `"http.request.headers[\"user-agent\"]"` |
162
- | `mitigationTimeout` | number | No | How long (seconds) to block after limit exceeded. Default: 3600. |
163
- | `countingExpression` | string | No | Cloudflare-specific: expression for what counts toward the limit. |
169
+ | Field | Type | Required | Description |
170
+ | -------------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- |
171
+ | `requests` | number | Yes | Max requests allowed in window. |
172
+ | `window` | string | Yes | Time window: `"10s"`, `"1m"`, `"5m"`, `"1h"` |
173
+ | `characteristics` | string[] | No | What to rate limit by. Default: `["ip.src"]`. Options: `"ip.src"`, `"http.request.uri.path"`, `"http.request.headers[\"user-agent\"]"` |
174
+ | `mitigationTimeout` | number | No | How long (seconds) to block after limit exceeded. Default: 3600. |
175
+ | `countingExpression` | string | No | Cloudflare-specific: expression for what counts toward the limit. |
164
176
 
165
177
  ### Redirect
166
178
 
@@ -175,10 +187,10 @@ Duration formats: `"30s"`, `"5m"`, `"1h"`, `"1d"`, `"permanent"`
175
187
  }
176
188
  ```
177
189
 
178
- | Field | Type | Description |
179
- |-------|------|-------------|
180
- | `location` | string | Redirect URL (absolute or relative path). |
181
- | `permanent` | boolean | `true` for 301, `false` for 302. |
190
+ | Field | Type | Description |
191
+ | ----------- | ------- | ----------------------------------------- |
192
+ | `location` | string | Redirect URL (absolute or relative path). |
193
+ | `permanent` | boolean | `true` for 301, `false` for 302. |
182
194
 
183
195
  ### Log Only
184
196
 
@@ -210,13 +222,13 @@ IP rules go in the `ips` array, separate from `rules`:
210
222
  }
211
223
  ```
212
224
 
213
- | Field | Type | Required | Description |
214
- |-------|------|----------|-------------|
215
- | `id` | string | No | Unique identifier. |
216
- | `ip` | string | Yes | IP address or CIDR range. Use `/32` for single IPs. |
217
- | `hostname` | string | No | Associated hostname (documentation only). |
218
- | `action` | string | Yes | `"deny"` (only supported value currently). |
219
- | `notes` | string | No | Why this IP was blocked. |
225
+ | Field | Type | Required | Description |
226
+ | ---------- | ------ | -------- | --------------------------------------------------- |
227
+ | `id` | string | No | Unique identifier. |
228
+ | `ip` | string | Yes | IP address or CIDR range. Use `/32` for single IPs. |
229
+ | `hostname` | string | No | Associated hostname (documentation only). |
230
+ | `action` | string | Yes | `"deny"` (only supported value currently). |
231
+ | `notes` | string | No | Why this IP was blocked. |
220
232
 
221
233
  ### CIDR Notation
222
234
 
@@ -262,10 +274,12 @@ IP rules go in the `ips` array, separate from `rules`:
262
274
  "name": "Rate Limit POST to API",
263
275
  "active": true,
264
276
  "conditionGroup": [
265
- { "conditions": [
266
- { "type": "path", "op": "pre", "value": "/api/" },
267
- { "type": "method", "op": "eq", "value": "POST" }
268
- ]}
277
+ {
278
+ "conditions": [
279
+ { "type": "path", "op": "pre", "value": "/api/" },
280
+ { "type": "method", "op": "eq", "value": "POST" }
281
+ ]
282
+ }
269
283
  ],
270
284
  "action": {
271
285
  "mitigate": {
@@ -284,10 +298,12 @@ IP rules go in the `ips` array, separate from `rules`:
284
298
  "name": "Block Missing API Key",
285
299
  "active": true,
286
300
  "conditionGroup": [
287
- { "conditions": [
288
- { "type": "path", "op": "pre", "value": "/api/" },
289
- { "type": "header", "op": "nex", "value": true, "key": "X-API-Key" }
290
- ]}
301
+ {
302
+ "conditions": [
303
+ { "type": "path", "op": "pre", "value": "/api/" },
304
+ { "type": "header", "op": "nex", "value": true, "key": "X-API-Key" }
305
+ ]
306
+ }
291
307
  ],
292
308
  "action": { "mitigate": { "action": "deny" } }
293
309
  }
@@ -327,9 +343,7 @@ IP rules go in the `ips` array, separate from `rules`:
327
343
  {
328
344
  "name": "Redirect Legacy API",
329
345
  "active": true,
330
- "conditionGroup": [
331
- { "conditions": [{ "type": "path", "op": "pre", "value": "/v1/api/" }] }
332
- ],
346
+ "conditionGroup": [{ "conditions": [{ "type": "path", "op": "pre", "value": "/v1/api/" }] }],
333
347
  "action": {
334
348
  "mitigate": {
335
349
  "action": "redirect",
@@ -27,6 +27,7 @@ doorman template ai-bots
27
27
  Detected bots include: GPTBot, ChatGPT-User, ClaudeBot, Claude-Web, Bytespider, CCBot, Google-Extended, GoogleOther, Amazonbot, Applebot-Extended, Meta-ExternalAgent, PerplexityBot, OAI-SearchBot, Diffbot, anthropic-ai, cohere-ai, and many more.
28
28
 
29
29
  **Rule added:**
30
+
30
31
  ```json
31
32
  {
32
33
  "id": "rule_detect_ai_bots",
@@ -58,6 +59,7 @@ doorman template bad-bots
58
59
  Includes: scrapers (HTTrack, WebCopier, SiteRipper), vulnerability scanners (Nikto, Nmap, Acunetix, SQLmap, WPScan, Nuclei), SEO crawlers (AhrefsBot, SemrushBot, MJ12bot, DotBot), and spam/attack tools (Zeus, Havij, Dirbuster).
59
60
 
60
61
  **Rule added:**
62
+
61
63
  ```json
62
64
  {
63
65
  "id": "rule_detect_bad_bots",
@@ -87,6 +89,7 @@ doorman template block-ofac-sanctioned-countries
87
89
  Countries blocked: Syria (SY), Iran (IR), Russia (RU), Cuba (CU), North Korea (KP).
88
90
 
89
91
  **Rule added:**
92
+
90
93
  ```json
91
94
  {
92
95
  "id": "rule_block_traffic_from_ofac_sanctioned_countries",
@@ -117,18 +120,28 @@ doorman template wordpress
117
120
  Paths blocked: `/wp-admin`, `/wp-login.php`, `/xmlrpc.php`, `/wp-content`, `/wp-includes`, `/wp-signup.php`, `/wp-activate.php`, `/register.php`, `/wp-register.php`
118
121
 
119
122
  **Rule added:**
123
+
120
124
  ```json
121
125
  {
122
126
  "name": "Deny WordPress URLs",
123
127
  "active": true,
124
128
  "conditionGroup": [
125
- { "conditions": [{ "type": "path", "op": "re", "value": "/(wp-admin|wp-login\\.php|xmlrpc\\.php|wp-content|wp-includes|wp-signup\\.php|wp-activate\\.php|register\\.php|wp-register\\.php)" }] }
129
+ {
130
+ "conditions": [
131
+ {
132
+ "type": "path",
133
+ "op": "re",
134
+ "value": "/(wp-admin|wp-login\\.php|xmlrpc\\.php|wp-content|wp-includes|wp-signup\\.php|wp-activate\\.php|register\\.php|wp-register\\.php)"
135
+ }
136
+ ]
137
+ }
126
138
  ],
127
139
  "action": { "mitigate": { "action": "deny" } }
128
140
  }
129
141
  ```
130
142
 
131
143
  **Note**: Uses regex — for Cloudflare non-Enterprise, split into multiple `pre` (prefix) conditions:
144
+
132
145
  ```json
133
146
  "conditionGroup": [
134
147
  { "conditions": [{ "type": "path", "op": "pre", "value": "/wp-admin" }] },
@@ -157,9 +170,7 @@ export const myTemplate: Template = {
157
170
  name: 'My Rule',
158
171
  description: 'What it does',
159
172
  active: true,
160
- conditionGroup: [
161
- { conditions: [{ type: 'path', op: 'pre', value: '/protected' }] }
162
- ],
173
+ conditionGroup: [{ conditions: [{ type: 'path', op: 'pre', value: '/protected' }] }],
163
174
  action: { mitigate: { action: 'deny' } },
164
175
  },
165
176
  ],