@gfargo/doorman 3.14.0 → 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/dist/bin/run.js +44 -44
- package/dist/bin/run.mjs +45 -45
- package/package.json +1 -1
- package/skills/doorman/references/rules.md +85 -73
- package/skills/doorman/references/templates.md +15 -4
package/package.json
CHANGED
|
@@ -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
|
-
{
|
|
17
|
-
|
|
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
|
|
26
|
-
|
|
27
|
-
| `id`
|
|
28
|
-
| `name`
|
|
29
|
-
| `description`
|
|
30
|
-
| `active`
|
|
31
|
-
| `conditionGroup` | array
|
|
32
|
-
| `action`
|
|
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
|
|
56
|
-
|
|
57
|
-
| `type`
|
|
58
|
-
| `op`
|
|
59
|
-
| `value` | string/number/array | Yes
|
|
60
|
-
| `key`
|
|
61
|
-
| `neg`
|
|
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
|
|
66
|
-
|
|
67
|
-
| `path`
|
|
68
|
-
| `method`
|
|
69
|
-
| `host`
|
|
70
|
-
| `user_agent`
|
|
71
|
-
| `ip_address`
|
|
72
|
-
| `header`
|
|
73
|
-
| `query`
|
|
74
|
-
| `cookie`
|
|
75
|
-
| `geo_country`
|
|
76
|
-
| `geo_city`
|
|
77
|
-
| `geo_continent`
|
|
78
|
-
| `geo_country_region` | Region/state code
|
|
79
|
-
| `geo_as_number`
|
|
80
|
-
| `scheme`
|
|
81
|
-
| `protocol`
|
|
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
|
|
93
|
-
|
|
94
|
-
| `eq`
|
|
95
|
-
| `pre`
|
|
96
|
-
| `suf`
|
|
97
|
-
| `sub`
|
|
98
|
-
| `inc`
|
|
99
|
-
| `re`
|
|
100
|
-
| `ex`
|
|
101
|
-
| `nex`
|
|
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
|
|
158
|
-
|
|
159
|
-
| `requests`
|
|
160
|
-
| `window`
|
|
161
|
-
| `characteristics`
|
|
162
|
-
| `mitigationTimeout`
|
|
163
|
-
| `countingExpression` | string
|
|
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
|
|
179
|
-
|
|
180
|
-
| `location`
|
|
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
|
|
214
|
-
|
|
215
|
-
| `id`
|
|
216
|
-
| `ip`
|
|
217
|
-
| `hostname` | string | No
|
|
218
|
-
| `action`
|
|
219
|
-
| `notes`
|
|
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
|
-
{
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
{
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
{
|
|
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
|
],
|