@gfargo/doorman 3.13.11 → 3.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gfargo/doorman",
3
- "version": "3.13.11",
3
+ "version": "3.14.1",
4
4
  "description": "Manage firewall rules as code across multiple providers",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -13,8 +13,16 @@ Every rule in the `rules` array has this shape:
13
13
  "description": "What this rule does and why",
14
14
  "active": true,
15
15
  "conditionGroup": [
16
- { "conditions": [/* AND — all must match */] },
17
- { "conditions": [/* OR — this group is an alternative */] }
16
+ {
17
+ "conditions": [
18
+ /* AND — all must match */
19
+ ]
20
+ },
21
+ {
22
+ "conditions": [
23
+ /* OR — this group is an alternative */
24
+ ]
25
+ }
18
26
  ],
19
27
  "action": { "mitigate": { "action": "deny" } }
20
28
  }
@@ -22,14 +30,14 @@ Every rule in the `rules` array has this shape:
22
30
 
23
31
  ### Fields
24
32
 
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). |
33
+ | Field | Type | Required | Description |
34
+ | ---------------- | ------- | -------- | ------------------------------------------------------------------------------------- |
35
+ | `id` | string | No | Unique identifier. Convention: `rule_` prefix, snake_case. Auto-generated if omitted. |
36
+ | `name` | string | Yes | Display name shown in tables and logs. |
37
+ | `description` | string | No | Explains the rule's purpose. Improves health score. |
38
+ | `active` | boolean | Yes | `true` to enforce, `false` to disable without deleting. |
39
+ | `conditionGroup` | array | Yes | Array of condition groups (see below). |
40
+ | `action` | object | Yes | What to do when conditions match (see below). |
33
41
 
34
42
  ## Condition Groups
35
43
 
@@ -41,6 +49,7 @@ Every rule in the `rules` array has this shape:
41
49
  ```
42
50
 
43
51
  **Logic**:
52
+
44
53
  - Conditions **within** a group: AND (all must match)
45
54
  - **Between** groups: OR (any group matching triggers the rule)
46
55
 
@@ -52,35 +61,36 @@ Each condition has:
52
61
  { "type": "field_type", "op": "operator", "value": "match_value", "key": "header_name", "neg": false }
53
62
  ```
54
63
 
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`. |
64
+ | Field | Type | Required | Description |
65
+ | ------- | ------------------- | -------- | ------------------------------------------------------------- |
66
+ | `type` | string | Yes | What to match against (see types below). |
67
+ | `op` | string | Yes | How to compare (see operators below). |
68
+ | `value` | string/number/array | Yes | Value to match. Arrays for `inc` operator. |
69
+ | `key` | string | No | Required for `header`, `query`, `cookie` types. |
70
+ | `neg` | boolean | No | `true` to negate the condition (NOT logic). Default: `false`. |
62
71
 
63
72
  ### Condition Types
64
73
 
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"` |
74
+ | Type | Description | Example Value |
75
+ | -------------------- | -------------------------------- | ----------------------- |
76
+ | `path` | URL path | `"/api/users"` |
77
+ | `method` | HTTP method | `"POST"` |
78
+ | `host` | Hostname | `"example.com"` |
79
+ | `user_agent` | User-Agent header | `"Googlebot"` |
80
+ | `ip_address` | Client IP | `"192.168.1.1"` |
81
+ | `header` | HTTP header (requires `key`) | `"application/json"` |
82
+ | `query` | Query parameter (requires `key`) | `"true"` |
83
+ | `cookie` | Cookie value (requires `key`) | `"session_abc"` |
84
+ | `geo_country` | Country code (ISO 3166-1) | `"US"` or `["US","CA"]` |
85
+ | `geo_city` | City name | `"New York"` |
86
+ | `geo_continent` | Continent code | `"NA"` |
87
+ | `geo_country_region` | Region/state code | `"CA"` |
88
+ | `geo_as_number` | ASN number | `13335` |
89
+ | `scheme` | URL scheme | `"https"` |
90
+ | `protocol` | HTTP protocol version | `"HTTP/2"` |
82
91
 
83
92
  **Vercel-only types** (not available on Cloudflare):
93
+
84
94
  - `environment` — deployment environment (`"production"`, `"preview"`)
85
95
  - `ja3_digest` — TLS fingerprint
86
96
  - `ja4_digest` — TLS fingerprint v4
@@ -89,16 +99,16 @@ Each condition has:
89
99
 
90
100
  ### Operators
91
101
 
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` |
102
+ | Operator | Name | Description | Value Type |
103
+ | -------- | ---------- | --------------------------- | ---------------------- |
104
+ | `eq` | Equals | Exact match | string/number |
105
+ | `pre` | Prefix | Starts with | string |
106
+ | `suf` | Suffix | Ends with | string |
107
+ | `sub` | Substring | Contains | string |
108
+ | `inc` | Includes | Is any of (array match) | string[] |
109
+ | `re` | Regex | Regular expression match | string (regex pattern) |
110
+ | `ex` | Exists | Field/header exists | `true` |
111
+ | `nex` | Not Exists | Field/header does not exist | `true` |
102
112
 
103
113
  ### Using `key` for Header/Query/Cookie
104
114
 
@@ -154,13 +164,13 @@ Duration formats: `"30s"`, `"5m"`, `"1h"`, `"1d"`, `"permanent"`
154
164
  }
155
165
  ```
156
166
 
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. |
167
+ | Field | Type | Required | Description |
168
+ | -------------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- |
169
+ | `requests` | number | Yes | Max requests allowed in window. |
170
+ | `window` | string | Yes | Time window: `"10s"`, `"1m"`, `"5m"`, `"1h"` |
171
+ | `characteristics` | string[] | No | What to rate limit by. Default: `["ip.src"]`. Options: `"ip.src"`, `"http.request.uri.path"`, `"http.request.headers[\"user-agent\"]"` |
172
+ | `mitigationTimeout` | number | No | How long (seconds) to block after limit exceeded. Default: 3600. |
173
+ | `countingExpression` | string | No | Cloudflare-specific: expression for what counts toward the limit. |
164
174
 
165
175
  ### Redirect
166
176
 
@@ -175,10 +185,10 @@ Duration formats: `"30s"`, `"5m"`, `"1h"`, `"1d"`, `"permanent"`
175
185
  }
176
186
  ```
177
187
 
178
- | Field | Type | Description |
179
- |-------|------|-------------|
180
- | `location` | string | Redirect URL (absolute or relative path). |
181
- | `permanent` | boolean | `true` for 301, `false` for 302. |
188
+ | Field | Type | Description |
189
+ | ----------- | ------- | ----------------------------------------- |
190
+ | `location` | string | Redirect URL (absolute or relative path). |
191
+ | `permanent` | boolean | `true` for 301, `false` for 302. |
182
192
 
183
193
  ### Log Only
184
194
 
@@ -210,13 +220,13 @@ IP rules go in the `ips` array, separate from `rules`:
210
220
  }
211
221
  ```
212
222
 
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. |
223
+ | Field | Type | Required | Description |
224
+ | ---------- | ------ | -------- | --------------------------------------------------- |
225
+ | `id` | string | No | Unique identifier. |
226
+ | `ip` | string | Yes | IP address or CIDR range. Use `/32` for single IPs. |
227
+ | `hostname` | string | No | Associated hostname (documentation only). |
228
+ | `action` | string | Yes | `"deny"` (only supported value currently). |
229
+ | `notes` | string | No | Why this IP was blocked. |
220
230
 
221
231
  ### CIDR Notation
222
232
 
@@ -262,10 +272,12 @@ IP rules go in the `ips` array, separate from `rules`:
262
272
  "name": "Rate Limit POST to API",
263
273
  "active": true,
264
274
  "conditionGroup": [
265
- { "conditions": [
266
- { "type": "path", "op": "pre", "value": "/api/" },
267
- { "type": "method", "op": "eq", "value": "POST" }
268
- ]}
275
+ {
276
+ "conditions": [
277
+ { "type": "path", "op": "pre", "value": "/api/" },
278
+ { "type": "method", "op": "eq", "value": "POST" }
279
+ ]
280
+ }
269
281
  ],
270
282
  "action": {
271
283
  "mitigate": {
@@ -284,10 +296,12 @@ IP rules go in the `ips` array, separate from `rules`:
284
296
  "name": "Block Missing API Key",
285
297
  "active": true,
286
298
  "conditionGroup": [
287
- { "conditions": [
288
- { "type": "path", "op": "pre", "value": "/api/" },
289
- { "type": "header", "op": "nex", "value": true, "key": "X-API-Key" }
290
- ]}
299
+ {
300
+ "conditions": [
301
+ { "type": "path", "op": "pre", "value": "/api/" },
302
+ { "type": "header", "op": "nex", "value": true, "key": "X-API-Key" }
303
+ ]
304
+ }
291
305
  ],
292
306
  "action": { "mitigate": { "action": "deny" } }
293
307
  }
@@ -327,9 +341,7 @@ IP rules go in the `ips` array, separate from `rules`:
327
341
  {
328
342
  "name": "Redirect Legacy API",
329
343
  "active": true,
330
- "conditionGroup": [
331
- { "conditions": [{ "type": "path", "op": "pre", "value": "/v1/api/" }] }
332
- ],
344
+ "conditionGroup": [{ "conditions": [{ "type": "path", "op": "pre", "value": "/v1/api/" }] }],
333
345
  "action": {
334
346
  "mitigate": {
335
347
  "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
  ],