@gfargo/doorman 3.9.2 → 3.10.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.9.2",
3
+ "version": "3.10.1",
4
4
  "description": "Manage firewall rules as code across multiple providers",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,18 +1,18 @@
1
1
  ---
2
2
  name: doorman
3
- description: Manage Vercel and Cloudflare WAF firewall rules as code with the Doorman CLI. Use for any firewall-as-code task — creating rules, IP blocking, rate limiting, bot protection, geo-blocking, syncing local config to providers, validating configurations, exporting documentation, CI/CD automation, or translating rules between Vercel and Cloudflare formats.
3
+ description: Manage Vercel, Cloudflare, and Fastly WAF firewall rules as code with the Doorman CLI. Use for any firewall-as-code task — creating rules, IP blocking, rate limiting, bot protection, geo-blocking, syncing local config to providers, validating configurations, exporting documentation, CI/CD automation, or translating rules between provider formats.
4
4
  license: MIT
5
- compatibility: Node.js >= 20. Requires provider API tokens (VERCEL_TOKEN or CLOUDFLARE_API_TOKEN). Install globally via npm install -g @gfargo/doorman.
5
+ compatibility: Node.js >= 20. Requires provider API tokens (VERCEL_TOKEN, CLOUDFLARE_API_TOKEN, or FASTLY_API_TOKEN). Install globally via npm install -g @gfargo/doorman.
6
6
  metadata:
7
7
  author: gfargo
8
- version: "1.0"
8
+ version: '1.0'
9
9
  homepage: https://github.com/gfargo/doorman
10
- npm: "@gfargo/doorman"
10
+ npm: '@gfargo/doorman'
11
11
  ---
12
12
 
13
13
  # Doorman — Firewall Rules as Code
14
14
 
15
- Doorman is a CLI for managing WAF (Web Application Firewall) rules as code across Vercel and Cloudflare. Configuration lives in `.doorman.json`, syncs bidirectionally with provider APIs, and integrates into CI/CD pipelines.
15
+ Doorman is a CLI for managing WAF (Web Application Firewall) rules as code across Vercel, Cloudflare, and Fastly Next-Gen WAF. Configuration lives in `.doorman.json`, syncs bidirectionally with provider APIs, and integrates into CI/CD pipelines.
16
16
 
17
17
  ## Command Quick Reference
18
18
 
@@ -44,7 +44,7 @@ doorman export --format markdown # Export as markdown|json|yaml|terraform
44
44
  doorman remove --name "Old Rule" # Remove rules by name/ID
45
45
  ```
46
46
 
47
- All commands accept `--provider vercel|cloudflare` and `--config <path>`.
47
+ All commands accept `--provider vercel|cloudflare|fastly` and `--config <path>`.
48
48
 
49
49
  ## Environment Variables
50
50
 
@@ -58,6 +58,10 @@ VERCEL_TEAM_ID=team_xxx
58
58
  CLOUDFLARE_API_TOKEN=your_token
59
59
  CLOUDFLARE_ZONE_ID=zone_xxx
60
60
  CLOUDFLARE_ACCOUNT_ID=acc_xxx # optional, enables Lists API for bulk IP management
61
+
62
+ # Fastly Next-Gen WAF (beta)
63
+ FASTLY_API_TOKEN=your_token
64
+ FASTLY_WORKSPACE_ID=workspace_xxx
61
65
  ```
62
66
 
63
67
  ## Config Structure
@@ -72,7 +76,7 @@ CLOUDFLARE_ACCOUNT_ID=acc_xxx # optional, enables Lists API for bulk IP manage
72
76
  }
73
77
  ```
74
78
 
75
- For Cloudflare, add `provider` and `providers` fields instead of `projectId`/`teamId`.
79
+ For Cloudflare or Fastly, add `provider` and `providers` fields instead of `projectId`/`teamId`.
76
80
 
77
81
  ## Core Workflow
78
82
 
@@ -93,9 +97,7 @@ doorman backup && doorman validate && doorman diff && doorman sync && doorman st
93
97
  {
94
98
  "name": "Block Admin",
95
99
  "active": true,
96
- "conditionGroup": [
97
- { "conditions": [{ "type": "path", "op": "pre", "value": "/admin" }] }
98
- ],
100
+ "conditionGroup": [{ "conditions": [{ "type": "path", "op": "pre", "value": "/admin" }] }],
99
101
  "action": { "mitigate": { "action": "deny" } }
100
102
  }
101
103
  ```
@@ -112,12 +114,13 @@ doorman backup && doorman validate && doorman diff && doorman sync && doorman st
112
114
 
113
115
  Load the relevant reference file for detailed documentation:
114
116
 
115
- | Task | Reference |
116
- |------|-----------|
117
- | Writing rules — full field docs, operators, actions, IP blocking, patterns | [references/rules.md](references/rules.md) |
118
- | Cloudflare-specific setup, Lists API, expression translation, limitations | [references/cloudflare.md](references/cloudflare.md) |
119
- | Available templates and what they protect against | [references/templates.md](references/templates.md) |
120
- | CI/CD integration, automation, export formats, validation in pipelines | [references/cicd.md](references/cicd.md) |
117
+ | Task | Reference |
118
+ | ------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
119
+ | Writing rules — full field docs, operators, actions, IP blocking, patterns | [references/rules.md](references/rules.md) |
120
+ | Cloudflare-specific setup, Lists API, expression translation, limitations | [references/cloudflare.md](references/cloudflare.md) |
121
+ | Fastly-specific setup, condition/action mapping, rate-limit signal requirement, limitations | [references/fastly.md](references/fastly.md) |
122
+ | Available templates and what they protect against | [references/templates.md](references/templates.md) |
123
+ | CI/CD integration, automation, export formats, validation in pipelines | [references/cicd.md](references/cicd.md) |
121
124
 
122
125
  ## Principles
123
126
 
@@ -170,6 +170,7 @@ doorman status --format json
170
170
  ```
171
171
 
172
172
  The health score evaluates:
173
+
173
174
  - Rule descriptions present
174
175
  - ID conventions followed (`rule_` prefix)
175
176
  - Operator complexity (penalizes unnecessary regex)
@@ -202,25 +203,26 @@ doorman sync --provider cloudflare --config .doorman.json
202
203
  ```bash
203
204
  # Migrate rules between providers
204
205
  doorman download --provider vercel --config .doorman.json
205
- doorman validate --provider cloudflare --config .doorman.json
206
+ doorman validate --config .doorman.json
206
207
  doorman sync --provider cloudflare --config .doorman.json
207
208
  ```
208
209
 
209
210
  ## Command Flags Reference
210
211
 
211
- Global flags available on all commands:
212
+ Common flags (availability varies by command):
212
213
 
213
- | Flag | Description |
214
- |------|-------------|
215
- | `--config <path>` | Path to config file (default: auto-discovered `.doorman.json`) |
216
- | `--provider vercel\|cloudflare` | Override provider detection |
217
- | `--format json\|table\|yaml\|markdown\|terraform` | Output format (command-dependent) |
218
- | `--verbose` | Show detailed output |
219
- | `--output <path>` | Write output to file instead of stdout |
214
+ | Flag | Description |
215
+ | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
216
+ | `--config <path>` | Path to config file (default: auto-discovered `.doorman.json`) |
217
+ | `--provider vercel\|cloudflare\|fastly` | Override provider detection — `sync`/`diff`/`download`/`list`/`status`/`watch`/`backup`/`export` only |
218
+ | `--format json\|table\|yaml\|markdown\|terraform` | Output format (command-dependent) |
219
+ | `--verbose` | Show detailed output |
220
+ | `--output <path>` | Write output to file instead of stdout |
220
221
 
221
222
  ## Error Handling in Automation
222
223
 
223
224
  Doorman exit codes:
225
+
224
226
  - `0` — success
225
227
  - `1` — error (validation failure, API error, config not found)
226
228
 
@@ -15,6 +15,7 @@ CLOUDFLARE_ACCOUNT_ID=acc_xxx # Optional — enables Lists API for bulk IP
15
15
  ### API Token Permissions
16
16
 
17
17
  Create a Custom Token at https://dash.cloudflare.com/profile/api-tokens with:
18
+
18
19
  - **Zone > Firewall Services > Edit** — for custom rulesets
19
20
  - **Account > Account Filter Lists > Edit** — for Lists API (bulk IP management, requires `CLOUDFLARE_ACCOUNT_ID`)
20
21
 
@@ -37,13 +38,14 @@ Multi-provider config with explicit provider declaration:
37
38
  }
38
39
  ```
39
40
 
40
- Or pass `--provider cloudflare` to any command to override the default.
41
+ Or pass `--provider cloudflare` to any provider-aware command (`sync`, `diff`, `download`, `list`, `status`, `watch`, `backup`, `export`) to override auto-detection.
41
42
 
42
43
  ## Usage
43
44
 
45
+ `doorman init` only supports Vercel today — it has no `--provider` flag and doesn't prompt for Cloudflare credentials. Create `.doorman.json` by hand using the config shape above, then:
46
+
44
47
  ```bash
45
- doorman init --provider cloudflare # Initialize Cloudflare config
46
- doorman validate --provider cloudflare # Validate against Cloudflare constraints
48
+ doorman validate # Validate auto-detects Cloudflare from the config's `provider` field
47
49
  doorman sync --provider cloudflare # Deploy to Cloudflare
48
50
  doorman download --provider cloudflare # Pull rules from Cloudflare
49
51
  doorman diff --provider cloudflare # Compare local vs live
@@ -56,33 +58,33 @@ Doorman translates its unified rule format into Cloudflare Wirefilter expression
56
58
 
57
59
  ### Field Mapping
58
60
 
59
- | Doorman Type | Cloudflare Field |
60
- |-------------|------------------|
61
- | `path` | `http.request.uri.path` |
62
- | `method` | `http.request.method` |
63
- | `host` | `http.host` |
64
- | `user_agent` | `http.user_agent` |
65
- | `ip_address` | `ip.src` |
66
- | `header` | `http.request.headers["key"]` |
67
- | `query` | `http.request.uri.query` |
68
- | `cookie` | `http.cookie` |
69
- | `geo_country` | `ip.geoip.country` |
70
- | `geo_city` | `ip.geoip.city` |
71
- | `geo_continent` | `ip.geoip.continent` |
72
- | `geo_country_region` | `ip.geoip.subdivision_1` |
73
- | `geo_as_number` | `ip.geoip.asnum` |
74
- | `scheme` | `ssl` (boolean) |
61
+ | Doorman Type | Cloudflare Field |
62
+ | -------------------- | ----------------------------- |
63
+ | `path` | `http.request.uri.path` |
64
+ | `method` | `http.request.method` |
65
+ | `host` | `http.host` |
66
+ | `user_agent` | `http.user_agent` |
67
+ | `ip_address` | `ip.src` |
68
+ | `header` | `http.request.headers["key"]` |
69
+ | `query` | `http.request.uri.query` |
70
+ | `cookie` | `http.cookie` |
71
+ | `geo_country` | `ip.geoip.country` |
72
+ | `geo_city` | `ip.geoip.city` |
73
+ | `geo_continent` | `ip.geoip.continent` |
74
+ | `geo_country_region` | `ip.geoip.subdivision_1` |
75
+ | `geo_as_number` | `ip.geoip.asnum` |
76
+ | `scheme` | `ssl` (boolean) |
75
77
 
76
78
  ### Action Mapping
77
79
 
78
- | Doorman Action | Cloudflare Action |
79
- |---------------|-------------------|
80
- | `deny` | `block` |
81
- | `challenge` | `managed_challenge` |
82
- | `rate_limit` | `block` + `ratelimit` config |
83
- | `redirect` | `redirect` + `from_value` params |
84
- | `log` | `log` |
85
- | `bypass` | `skip` |
80
+ | Doorman Action | Cloudflare Action |
81
+ | -------------- | -------------------------------- |
82
+ | `deny` | `block` |
83
+ | `challenge` | `managed_challenge` |
84
+ | `rate_limit` | `block` + `ratelimit` config |
85
+ | `redirect` | `redirect` + `from_value` params |
86
+ | `log` | `log` |
87
+ | `bypass` | `skip` |
86
88
 
87
89
  ## Lists API (Bulk IP Management)
88
90
 
@@ -97,16 +99,16 @@ Without `CLOUDFLARE_ACCOUNT_ID`, IP blocking falls back to individual WAF rules
97
99
 
98
100
  ## Limitations & Differences
99
101
 
100
- | Feature | Vercel | Cloudflare | Notes |
101
- |---------|--------|------------|-------|
102
- | `re` operator | All plans | Enterprise only | Use `sub`/`pre`/`suf` as alternatives |
103
- | `environment` type | Yes | No | Vercel-specific concept |
104
- | `ja3_digest`/`ja4_digest` | Yes | No | Vercel TLS fingerprints |
105
- | `region` type | Yes | No | Vercel edge region |
106
- | IP Lists (bulk) | Individual rules | Lists API | Cloudflare needs `accountId` |
107
- | Max custom rules | ~100 | 5-125 (plan dependent) | Free: 5, Pro: 20, Business: 100, Enterprise: 125+ |
108
- | Rate limit | Via rule action | Separate phase | Different underlying mechanism |
109
- | Rule order | Parallel evaluation | Sequential (first match wins) | Ordering matters on Cloudflare |
102
+ | Feature | Vercel | Cloudflare | Notes |
103
+ | ------------------------- | ------------------- | ----------------------------- | ------------------------------------------------- |
104
+ | `re` operator | All plans | Enterprise only | Use `sub`/`pre`/`suf` as alternatives |
105
+ | `environment` type | Yes | No | Vercel-specific concept |
106
+ | `ja3_digest`/`ja4_digest` | Yes | No | Vercel TLS fingerprints |
107
+ | `region` type | Yes | No | Vercel edge region |
108
+ | IP Lists (bulk) | Individual rules | Lists API | Cloudflare needs `accountId` |
109
+ | Max custom rules | ~100 | 5-125 (plan dependent) | Free: 5, Pro: 20, Business: 100, Enterprise: 125+ |
110
+ | Rate limit | Via rule action | Separate phase | Different underlying mechanism |
111
+ | Rule order | Parallel evaluation | Sequential (first match wins) | Ordering matters on Cloudflare |
110
112
 
111
113
  ## Translation Warnings
112
114
 
@@ -117,7 +119,7 @@ The `RuleTranslator` surfaces warnings when a translation is lossy:
117
119
  - **Duration differences** — `actionDuration` maps differently between providers
118
120
  - **Negation edge cases** — complex negated conditions may produce subtly different behavior in Wirefilter
119
121
 
120
- Run `doorman validate --provider cloudflare` to surface warnings before deploying.
122
+ Run `doorman validate` to surface warnings before deploying — it auto-detects Cloudflare from the config's `provider` field.
121
123
 
122
124
  ## Cloudflare-Specific Validation
123
125
 
@@ -166,9 +168,7 @@ The `CloudflareOptimizer` consolidates rules for efficient deployment:
166
168
  "name": "Rate Limit API",
167
169
  "description": "Limit API requests to 100/min per IP",
168
170
  "active": true,
169
- "conditionGroup": [
170
- { "conditions": [{ "type": "path", "op": "pre", "value": "/api/" }] }
171
- ],
171
+ "conditionGroup": [{ "conditions": [{ "type": "path", "op": "pre", "value": "/api/" }] }],
172
172
  "action": {
173
173
  "mitigate": {
174
174
  "action": "rate_limit",
@@ -181,8 +181,6 @@ The `CloudflareOptimizer` consolidates rules for efficient deployment:
181
181
  }
182
182
  }
183
183
  ],
184
- "ips": [
185
- { "ip": "203.0.113.0/24", "action": "deny", "notes": "Known attack subnet" }
186
- ]
184
+ "ips": [{ "ip": "203.0.113.0/24", "action": "deny", "notes": "Known attack subnet" }]
187
185
  }
188
186
  ```
@@ -0,0 +1,169 @@
1
+ # Fastly Next-Gen WAF Provider Reference
2
+
3
+ Fastly Next-Gen WAF support in Doorman (beta). Manage Next-Gen WAF (formerly Signal Sciences) workspace rules through the same config-as-code workflow. Classic Fastly VCL services are **not** supported — only the Next-Gen WAF's structured rules API.
4
+
5
+ ## Setup
6
+
7
+ ### Environment Variables
8
+
9
+ ```bash
10
+ FASTLY_API_TOKEN=your_token # Required — Fastly API token
11
+ FASTLY_WORKSPACE_ID=workspace_xxx # Required — Next-Gen WAF workspace to manage rules for
12
+ ```
13
+
14
+ ### API Token Permissions
15
+
16
+ Create a token at https://manage.fastly.com/account/personal/tokens with access to the Next-Gen WAF product for the target workspace.
17
+
18
+ ### Config Shape
19
+
20
+ Multi-provider config with explicit provider declaration:
21
+
22
+ ```json
23
+ {
24
+ "$schema": "https://doorman.griffen.codes/schema.json",
25
+ "provider": "fastly",
26
+ "providers": {
27
+ "fastly": {
28
+ "workspaceId": "your_workspace_id"
29
+ }
30
+ },
31
+ "rules": [],
32
+ "ips": []
33
+ }
34
+ ```
35
+
36
+ Or pass `--provider fastly` to any provider-aware command (`sync`, `diff`, `download`, `list`, `status`, `watch`, `backup`, `export`) to override auto-detection.
37
+
38
+ ## Usage
39
+
40
+ `doorman init` only supports Vercel today — it has no `--provider` flag and doesn't prompt for Fastly credentials. Create `.doorman.json` by hand using the config shape above, then:
41
+
42
+ ```bash
43
+ doorman validate # Validate — auto-detects Fastly from the config's `provider` field
44
+ doorman sync --provider fastly # Deploy to Fastly
45
+ doorman download --provider fastly # Pull rules from Fastly
46
+ doorman diff --provider fastly # Compare local vs live
47
+ doorman list --provider fastly # Show deployed rules
48
+ ```
49
+
50
+ ## Rule Translation
51
+
52
+ Doorman translates its unified rule format into Fastly's structured `request`-type rules automatically. The translation is bidirectional — `doorman download` pulls Fastly rules back into the unified format.
53
+
54
+ ### Field Mapping
55
+
56
+ | Doorman Field | Fastly Condition | Notes |
57
+ | ------------- | -------------------------- | ------------------------------------------------------- |
58
+ | `path` | `path` | |
59
+ | `method` | `method` | |
60
+ | `host` | `domain` | |
61
+ | `user_agent` | `user_agent` | |
62
+ | `ip` | `ip` | |
63
+ | `country` | `country` | Country-level only — no region/city/continent condition |
64
+ | `scheme` | `scheme` | |
65
+ | `header` | `request_header` multival | Requires `key` (the header name) |
66
+ | `query` | `query_parameter` multival | Requires `key` (the parameter name) |
67
+ | `cookie` | `request_cookie` multival | Requires `key` (the cookie name) |
68
+
69
+ Fields with no Fastly equivalent (`region`, `city`, `asn`, `referer`, `port`, `target_path`, `environment`, `ja3_digest`, `ja4_digest`) are dropped with a translation warning rather than silently mistranslated — the rule still syncs with its remaining conditions.
70
+
71
+ ### Operator Mapping
72
+
73
+ | Doorman Operator | Fastly Operator | Notes |
74
+ | -------------------------- | ------------------------------------------- | -------------------------------------------------------------- |
75
+ | `eq` | `equals` (or `does_not_equal` if `negated`) | |
76
+ | `contains` | `contains` (or `does_not_contain`) | |
77
+ | `matches` | `matches` (or `does_not_match`) | Regex syntax may need adjustment |
78
+ | `starts_with`, `ends_with` | `like` (or `not_like`) | Fastly's wildcard matching isn't a true prefix/suffix operator |
79
+ | `in` | `in_list` (or `not_in_list`) | |
80
+ | `gt`, `lt` | `greater_equal`, `lesser_equal` | No strict `>`/`<` on Fastly — boundary value also matches |
81
+
82
+ ### Action Mapping
83
+
84
+ | Doorman Action | Fastly Action | Notes |
85
+ | --------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------- |
86
+ | `block`, `deny` | `block` | |
87
+ | `allow` | `allow` | |
88
+ | `challenge` | `browser_challenge` | |
89
+ | `redirect` | `redirect` | Uses `redirect_url` + `response_code` |
90
+ | `rate_limit` | Rule `type: rate_limit` + `block_signal` action | See Rate Limiting below |
91
+ | `log` | `allow` (with a warning) | Fastly has no dedicated log-only action; `request_logging` is set at the rule level regardless |
92
+ | `bypass` | `allow` (with a warning) | No equivalent action |
93
+
94
+ ## Condition Grouping
95
+
96
+ Fastly's condition model is bounded to two levels — a rule's top-level conditions can be plain, or grouped, but a group's own members can never contain another group. This matches Doorman's flat `group` model exactly:
97
+
98
+ - Conditions sharing a `group` index are AND'd together.
99
+ - Distinct `group` values are OR'd against each other.
100
+ - A single implicit group (the common case) becomes flat top-level conditions.
101
+
102
+ ## Rate Limiting
103
+
104
+ Fastly rate-limit rules are a distinct rule type (`type: "rate_limit"`) with their own `rate_limit` block (`threshold`, `interval`, `duration`, a counting `signal`). Doorman's `window` (e.g. `"60s"`, `"5m"`) is rounded to the nearest interval Fastly supports (60s / 600s / 3600s).
105
+
106
+ **Fastly rate-limit rules count requests against a named custom signal that must already exist in the workspace** — Doorman does not create it automatically. Create a signal named `doorman-rate-limit-<rule-id>` before syncing a rate-limit rule, or the sync will be rejected. A translation warning is emitted every time to make this easy to miss less easily.
107
+
108
+ ## IP Blocking
109
+
110
+ Fastly has no per-IP-rule resource — Doorman manages two workspace **lists** of type `ip` (`doorman-managed-deny`, `doorman-managed-allow`), each replaced wholesale on sync (the same atomic-replace shape as Cloudflare's ruleset write, not Vercel's per-item writes). `hostname`/`notes` on an IP rule have no Fastly equivalent and are dropped with a warning — only the address is kept.
111
+
112
+ ## No Draft/Publish Step
113
+
114
+ Unlike classic Fastly VCL services, Next-Gen WAF rule and list writes take effect immediately — there is no draft-then-activate version to manage. A rule's `enabled` flag is the only gate on whether it's live.
115
+
116
+ ## Limitations & Differences
117
+
118
+ | Feature | Vercel | Fastly | Notes |
119
+ | -------------------------- | ----------------------------- | ---------------------- | ------------------------------------------------------------------------- |
120
+ | Geo targeting | Country/city/continent/region | Country only | No sub-country condition field |
121
+ | Rule ordering | Best-effort (insertion order) | None | Next-Gen WAF rules are evaluated independently — `priority` has no effect |
122
+ | Managed/vendor rule groups | CRS (enterprise) | Templated signal rules | Not yet configurable through Doorman for either provider (#183) |
123
+ | Signal/exclusion rules | — | `type: signal` | Not managed by Doorman — see below |
124
+
125
+ Fastly rules of type `signal` or `templated_signal` (which tag or exclude WAF signals rather than allow/block/redirect a request) are skipped entirely by `doorman download`/`fetchConfig` rather than forced into a lossy `UnifiedRule` — they simply don't appear in the local config.
126
+
127
+ ## Example: Full Fastly Config
128
+
129
+ ```json
130
+ {
131
+ "$schema": "https://doorman.griffen.codes/schema.json",
132
+ "provider": "fastly",
133
+ "providers": {
134
+ "fastly": {
135
+ "workspaceId": "abc123def456"
136
+ }
137
+ },
138
+ "rules": [
139
+ {
140
+ "id": "rule_block_bots",
141
+ "name": "Block Bad Bots",
142
+ "enabled": true,
143
+ "conditions": [
144
+ { "field": "user_agent", "operator": "contains", "value": "AhrefsBot", "group": 0 },
145
+ { "field": "user_agent", "operator": "contains", "value": "SemrushBot", "group": 1 }
146
+ ],
147
+ "action": { "type": "block" }
148
+ },
149
+ {
150
+ "id": "rule_admin_header",
151
+ "name": "Require internal header on admin paths",
152
+ "enabled": true,
153
+ "conditions": [
154
+ { "field": "path", "operator": "starts_with", "value": "/admin", "group": 0 },
155
+ {
156
+ "field": "header",
157
+ "operator": "eq",
158
+ "value": "internal",
159
+ "key": "X-Access-Level",
160
+ "negated": true,
161
+ "group": 0
162
+ }
163
+ ],
164
+ "action": { "type": "block" }
165
+ }
166
+ ],
167
+ "ips": [{ "ip": "203.0.113.0/24", "action": "deny" }]
168
+ }
169
+ ```
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "security",
3
- "description": "Web application firewall management as code with Doorman: create rules, block IPs, rate limit, protect against bots, and deploy WAF configs to Vercel and Cloudflare.",
3
+ "description": "Web application firewall management as code with Doorman: create rules, block IPs, rate limit, protect against bots, and deploy WAF configs to Vercel, Cloudflare, and Fastly.",
4
4
  "version": "1.0.0",
5
5
  "author": {
6
6
  "name": "Griffen Fargo",
@@ -13,6 +13,7 @@
13
13
  "waf",
14
14
  "vercel",
15
15
  "cloudflare",
16
+ "fastly",
16
17
  "doorman",
17
18
  "iac",
18
19
  "rules",