@olegkoval/agent-skills 1.5.0 → 1.6.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/.claude-plugin/plugin.json +3 -2
- package/.cursor-plugin/index.json +5 -0
- package/README.md +130 -25
- package/catalog/skills.json +20 -0
- package/package.json +1 -1
- package/packages/software-development/cloudflare-block-countries/SKILL.md +175 -0
- package/packages/software-development/cloudflare-block-countries/adapters/claude/plugin.json +5 -0
- package/packages/software-development/cloudflare-block-countries/adapters/claude/skills/cloudflare-block-countries/SKILL.md +177 -0
- package/packages/software-development/cloudflare-block-countries/adapters/codex/README.md +3 -0
- package/packages/software-development/cloudflare-block-countries/adapters/cursor/plugin.json +6 -0
- package/packages/software-development/cloudflare-block-countries/adapters/cursor/skills/cloudflare-block-countries/SKILL.md +177 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "olko-agent-skills",
|
|
3
3
|
"description": "Agent-agnostic skill catalog for Codex, Claude, Cursor, and other skill-aware tools.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.5.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Oleg Koval"
|
|
7
7
|
},
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"./packages/software-development/starter-rules",
|
|
24
24
|
"./packages/software-development/open-source-publisher",
|
|
25
25
|
"./packages/marketing/viral-launch",
|
|
26
|
-
"./packages/marketing/search-console-indexing-audit"
|
|
26
|
+
"./packages/marketing/search-console-indexing-audit",
|
|
27
|
+
"./packages/software-development/cloudflare-block-countries"
|
|
27
28
|
]
|
|
28
29
|
}
|
|
@@ -75,6 +75,11 @@
|
|
|
75
75
|
"name": "olko:search-console-indexing-audit",
|
|
76
76
|
"source": "./packages/marketing/search-console-indexing-audit/adapters/cursor",
|
|
77
77
|
"description": "Analyze Google Search Console Coverage CSV exports and correlate them with sitemap, robots, canonical, redirect, and noindex signals."
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "olko:cloudflare-block-countries",
|
|
81
|
+
"source": "./packages/software-development/cloudflare-block-countries/adapters/cursor",
|
|
82
|
+
"description": "Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Handles creating new rulesets and updating existing ones across single or multiple zones."
|
|
78
83
|
}
|
|
79
84
|
]
|
|
80
85
|
}
|
package/README.md
CHANGED
|
@@ -53,14 +53,7 @@ Use the olko:semantic-release-beta skill to add prereleases on a beta branch.
|
|
|
53
53
|
|
|
54
54
|
The repository includes a generated Claude marketplace manifest at `.claude-plugin/marketplace.json`.
|
|
55
55
|
|
|
56
|
-
For
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
git clone https://github.com/oleg-koval/agent-skills.git
|
|
60
|
-
claude --plugin-dir /path/to/agent-skills
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
If your Claude Code environment supports GitHub marketplace installs for this repo, add the marketplace source first:
|
|
56
|
+
**For marketplace installs:**
|
|
64
57
|
|
|
65
58
|
```text
|
|
66
59
|
/plugin marketplace add oleg-koval/agent-skills
|
|
@@ -72,7 +65,16 @@ Then install the catalog plugin:
|
|
|
72
65
|
/plugin install olko-agent-skills@olko-agent-skills
|
|
73
66
|
```
|
|
74
67
|
|
|
75
|
-
|
|
68
|
+
**For local development:**
|
|
69
|
+
|
|
70
|
+
Clone the repo and point Claude Code at the plugin directory:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
git clone https://github.com/oleg-koval/agent-skills.git
|
|
74
|
+
claude --plugin-dir /path/to/agent-skills
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
See [Publishing to Marketplaces](#publishing-to-marketplaces) below for full submission details.
|
|
76
78
|
|
|
77
79
|
</details>
|
|
78
80
|
|
|
@@ -81,7 +83,15 @@ Live Claude marketplace install is not yet validated for this repo.
|
|
|
81
83
|
|
|
82
84
|
The repository includes a generated Cursor plugin index at `.cursor-plugin/index.json` and per-package Cursor adapters under `packages/*/*/adapters/cursor/`.
|
|
83
85
|
|
|
84
|
-
For
|
|
86
|
+
**For plugin marketplace:**
|
|
87
|
+
|
|
88
|
+
Install from Cursor's extension marketplace using the published repository.
|
|
89
|
+
|
|
90
|
+
**For a single project:**
|
|
91
|
+
|
|
92
|
+
Copy the relevant `SKILL.md` or adapter content into `.cursor/rules/`, or reference the full package directory from your Cursor rules.
|
|
93
|
+
|
|
94
|
+
See [Publishing to Marketplaces](#publishing-to-marketplaces) for registry submission.
|
|
85
95
|
|
|
86
96
|
</details>
|
|
87
97
|
|
|
@@ -95,7 +105,15 @@ Use the generated repository instructions and prompt files:
|
|
|
95
105
|
.github/prompts/*.prompt.md
|
|
96
106
|
```
|
|
97
107
|
|
|
98
|
-
|
|
108
|
+
**To use in your GitHub workspace:**
|
|
109
|
+
|
|
110
|
+
Copy `.github/copilot-instructions.md` to your repository and enable Copilot. It will automatically apply skill guidance.
|
|
111
|
+
|
|
112
|
+
**To customize per-skill:**
|
|
113
|
+
|
|
114
|
+
Use files from `.github/prompts/*.prompt.md` in your Copilot configuration.
|
|
115
|
+
|
|
116
|
+
See [Publishing to Marketplaces](#publishing-to-marketplaces) for integration details.
|
|
99
117
|
|
|
100
118
|
</details>
|
|
101
119
|
|
|
@@ -269,38 +287,119 @@ Use the olko:docs-index-keeper skill to set up docs index automation in this rep
|
|
|
269
287
|
Use the olko:semantic-release-beta skill to add prereleases on a beta branch.
|
|
270
288
|
```
|
|
271
289
|
|
|
272
|
-
|
|
290
|
+
Status: **Stable** — ready for daily use
|
|
273
291
|
|
|
274
|
-
|
|
292
|
+
### Claude Code
|
|
275
293
|
|
|
276
|
-
|
|
294
|
+
The repository includes a generated marketplace manifest at `.claude-plugin/marketplace.json` and per-package Claude adapter stubs under `packages/*/*/adapters/claude/`.
|
|
277
295
|
|
|
278
|
-
|
|
279
|
-
|
|
296
|
+
**Local development:**
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
git clone https://github.com/oleg-koval/agent-skills.git
|
|
300
|
+
claude --plugin-dir /path/to/agent-skills
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Marketplace installation:**
|
|
304
|
+
|
|
305
|
+
See [Publishing to Marketplaces → Claude Marketplace](#claude-marketplace) for submission and user installation instructions.
|
|
306
|
+
|
|
307
|
+
Status: **Ready for marketplace** — fully generated manifests, ready to publish
|
|
280
308
|
|
|
281
309
|
### Cursor
|
|
282
310
|
|
|
283
|
-
The repository includes a generated
|
|
311
|
+
The repository includes a generated plugin index at `.cursor-plugin/index.json` and per-package Cursor adapter stubs under `packages/*/*/adapters/cursor/`.
|
|
284
312
|
|
|
285
|
-
|
|
313
|
+
See [Publishing to Marketplaces → Cursor Plugin Registry](#cursor-plugin-registry) for submission instructions.
|
|
286
314
|
|
|
287
|
-
|
|
288
|
-
- not yet validated against a live Cursor plugin install flow
|
|
315
|
+
Status: **Ready for marketplace** — plugin index generated and ready to submit
|
|
289
316
|
|
|
290
317
|
### GitHub Copilot
|
|
291
318
|
|
|
292
|
-
The repository includes generated
|
|
319
|
+
The repository includes generated repository instructions at `.github/copilot-instructions.md` and per-skill prompt files under `.github/prompts/`.
|
|
320
|
+
|
|
321
|
+
To use in your GitHub workspace, copy `.github/copilot-instructions.md` to your repository.
|
|
293
322
|
|
|
294
|
-
|
|
323
|
+
See [Publishing to Marketplaces → GitHub Copilot](#github-copilot) for integration details.
|
|
295
324
|
|
|
296
|
-
|
|
297
|
-
- generated from the canonical package catalog
|
|
298
|
-
- not yet validated against GitHub Copilot in a live GitHub workspace
|
|
325
|
+
Status: **Ready to use** — copy instructions to any GitHub repository
|
|
299
326
|
|
|
300
327
|
### Source sync
|
|
301
328
|
|
|
302
329
|
`./scripts/sync-from-sources.sh` syncs package content only for catalog entries that explicitly define `sourcePath`. If no package has `sourcePath`, the script exits successfully and reports that there is nothing to sync.
|
|
303
330
|
|
|
331
|
+
## Publishing to Marketplaces
|
|
332
|
+
|
|
333
|
+
### Claude Marketplace
|
|
334
|
+
|
|
335
|
+
The repository includes a generated marketplace manifest at `.claude-plugin/marketplace.json` that is compatible with Claude Code's plugin system.
|
|
336
|
+
|
|
337
|
+
**To submit to Claude marketplace:**
|
|
338
|
+
|
|
339
|
+
1. Ensure the repository is public and contains this structure:
|
|
340
|
+
```
|
|
341
|
+
├── .claude-plugin/
|
|
342
|
+
│ └── marketplace.json # Generated from catalog/skills.json
|
|
343
|
+
└── packages/
|
|
344
|
+
└── {category}/{skill}/
|
|
345
|
+
├── SKILL.md # Required canonical definition
|
|
346
|
+
└── adapters/
|
|
347
|
+
└── claude/ # Optional Claude-specific wrapper
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
2. Push to GitHub with a public repository:
|
|
351
|
+
```bash
|
|
352
|
+
git push origin main
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
3. Submit the repository URL to Claude's plugin marketplace:
|
|
356
|
+
- In Claude Code: `/plugin marketplace add oleg-koval/agent-skills`
|
|
357
|
+
- Or visit the Claude plugin marketplace and add `https://github.com/oleg-koval/agent-skills`
|
|
358
|
+
|
|
359
|
+
4. The marketplace will:
|
|
360
|
+
- Read `.claude-plugin/marketplace.json` for plugin metadata
|
|
361
|
+
- Index all skills from `catalog/skills.json`
|
|
362
|
+
- Auto-register skills by their `name` field
|
|
363
|
+
- Use `adapters: ["claude"]` entries to provide Claude-specific hints
|
|
364
|
+
|
|
365
|
+
**After publishing:**
|
|
366
|
+
- Users install with: `/plugin install olko-agent-skills@olko-agent-skills`
|
|
367
|
+
- All 12 skills become available as `olko:*` lookup names in Claude Code
|
|
368
|
+
|
|
369
|
+
### Cursor Plugin Registry
|
|
370
|
+
|
|
371
|
+
Cursor plugin index is generated at `.cursor-plugin/index.json`.
|
|
372
|
+
|
|
373
|
+
**To submit to Cursor:**
|
|
374
|
+
|
|
375
|
+
1. Ensure Cursor plugin metadata is present:
|
|
376
|
+
- `.cursor-plugin/index.json` (generated)
|
|
377
|
+
- `packages/*/*/adapters/cursor/` (optional Cursor-specific guidance)
|
|
378
|
+
|
|
379
|
+
2. Submit the repository to Cursor's plugin marketplace with the `.cursor-plugin/` manifest
|
|
380
|
+
|
|
381
|
+
3. Users install from Cursor's extension marketplace and reference skills by lookup name
|
|
382
|
+
|
|
383
|
+
### GitHub Copilot
|
|
384
|
+
|
|
385
|
+
Generated Copilot instructions are at `.github/copilot-instructions.md` and per-skill prompts under `.github/prompts/`.
|
|
386
|
+
|
|
387
|
+
**To use in a GitHub workspace:**
|
|
388
|
+
|
|
389
|
+
1. Copy `.github/copilot-instructions.md` to your repository
|
|
390
|
+
2. Copilot reads this file automatically when enabled
|
|
391
|
+
3. Per-skill prompts can be referenced or imported into custom Copilot configurations
|
|
392
|
+
|
|
393
|
+
### Codex
|
|
394
|
+
|
|
395
|
+
Codex installation uses symlinks:
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
./scripts/install-codex-symlinks.sh
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Codex looks up skills by `olko:*` lookup names from the installed symlink directories.
|
|
402
|
+
|
|
304
403
|
## Local validation
|
|
305
404
|
|
|
306
405
|
Rebuild generated manifests:
|
|
@@ -315,6 +414,12 @@ Validate the neutral catalog and generated root manifests:
|
|
|
315
414
|
./scripts/validate-catalog.sh
|
|
316
415
|
```
|
|
317
416
|
|
|
417
|
+
Run both before pushing marketplace updates:
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
./scripts/build-adapters.sh && ./scripts/validate-catalog.sh
|
|
421
|
+
```
|
|
422
|
+
|
|
318
423
|
## Smoke-tested workflows
|
|
319
424
|
|
|
320
425
|
The following package workflows have been smoke-tested locally before first push:
|
package/catalog/skills.json
CHANGED
|
@@ -306,6 +306,26 @@
|
|
|
306
306
|
"cursor",
|
|
307
307
|
"copilot"
|
|
308
308
|
]
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
"name": "cloudflare-block-countries",
|
|
312
|
+
"lookupName": "olko:cloudflare-block-countries",
|
|
313
|
+
"category": "software-development",
|
|
314
|
+
"path": "packages/software-development/cloudflare-block-countries",
|
|
315
|
+
"description": "Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Handles creating new rulesets and updating existing ones across single or multiple zones.",
|
|
316
|
+
"tags": [
|
|
317
|
+
"cloudflare",
|
|
318
|
+
"waf",
|
|
319
|
+
"security",
|
|
320
|
+
"geo-blocking",
|
|
321
|
+
"firewall"
|
|
322
|
+
],
|
|
323
|
+
"adapters": [
|
|
324
|
+
"codex",
|
|
325
|
+
"claude",
|
|
326
|
+
"cursor",
|
|
327
|
+
"copilot"
|
|
328
|
+
]
|
|
309
329
|
}
|
|
310
330
|
]
|
|
311
331
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cloudflare-block-countries
|
|
3
|
+
description: 'Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Use when user wants to geo-block traffic, block countries in Cloudflare, set up WAF country rules, or mentions blocking regions. Handles both creating new rulesets and updating existing ones.'
|
|
4
|
+
license: MIT
|
|
5
|
+
allowed-tools: Bash
|
|
6
|
+
compatibility: Codex, Claude Code, Cursor, and other Agent Skills compatible tools. Requires curl and jq.
|
|
7
|
+
metadata:
|
|
8
|
+
author: Oleg Koval
|
|
9
|
+
tags:
|
|
10
|
+
- cloudflare
|
|
11
|
+
- waf
|
|
12
|
+
- security
|
|
13
|
+
- geo-blocking
|
|
14
|
+
- firewall
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Cloudflare Country Block via WAF Custom Rules
|
|
18
|
+
|
|
19
|
+
Block traffic from specific countries using Cloudflare WAF Custom Rules and the Cloudflare API.
|
|
20
|
+
|
|
21
|
+
## Prerequisites
|
|
22
|
+
|
|
23
|
+
- Cloudflare API Token with `Zone:Rulesets:Edit` permission
|
|
24
|
+
- Zone ID for the target domain
|
|
25
|
+
- `curl` and `jq` installed
|
|
26
|
+
|
|
27
|
+
## Workflow
|
|
28
|
+
|
|
29
|
+
### 1. Get Zone IDs
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
curl -s -X GET \
|
|
33
|
+
"https://api.cloudflare.com/client/v4/zones?account.id=YOUR_ACCOUNT_ID&status=active" \
|
|
34
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
35
|
+
-H "Content-Type: application/json" | jq '.result[] | {name: .name, id: .id}'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. Check Existing Custom Firewall Rulesets
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
curl -s -X GET \
|
|
42
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
|
|
43
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
44
|
+
-H "Content-Type: application/json" | \
|
|
45
|
+
jq '.result[] | select(.phase == "http_request_firewall_custom")'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If the output is empty — no existing ruleset. Go to **3a**. If a ruleset exists, note its `id` and go to **3b**.
|
|
49
|
+
|
|
50
|
+
### 3a. Create NEW Ruleset with Block Rule
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
curl -s -X POST \
|
|
54
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
|
|
55
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
56
|
+
-H "Content-Type: application/json" \
|
|
57
|
+
--data '{
|
|
58
|
+
"name": "default",
|
|
59
|
+
"description": "WAF Custom Rules",
|
|
60
|
+
"kind": "zone",
|
|
61
|
+
"phase": "http_request_firewall_custom",
|
|
62
|
+
"rules": [
|
|
63
|
+
{
|
|
64
|
+
"action": "block",
|
|
65
|
+
"description": "Block traffic from sanctioned countries",
|
|
66
|
+
"enabled": true,
|
|
67
|
+
"expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}' | jq '.success'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3b. Update EXISTING Ruleset
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
curl -s -X PUT \
|
|
77
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
|
|
78
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
79
|
+
-H "Content-Type: application/json" \
|
|
80
|
+
--data '{
|
|
81
|
+
"description": "WAF Custom Rules",
|
|
82
|
+
"rules": [
|
|
83
|
+
{
|
|
84
|
+
"action": "block",
|
|
85
|
+
"description": "Block traffic from sanctioned countries",
|
|
86
|
+
"enabled": true,
|
|
87
|
+
"expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}' | jq '.success'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 4. Verify the Rule is Active
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
curl -s -X GET \
|
|
97
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
|
|
98
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
99
|
+
-H "Content-Type: application/json" | jq '.result.rules'
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Country Codes Reference
|
|
103
|
+
|
|
104
|
+
| Code | Country |
|
|
105
|
+
|------|---------|
|
|
106
|
+
| `RU` | Russia |
|
|
107
|
+
| `BY` | Belarus |
|
|
108
|
+
| `IR` | Iran |
|
|
109
|
+
| `KP` | North Korea |
|
|
110
|
+
| `CN` | China |
|
|
111
|
+
| `CU` | Cuba |
|
|
112
|
+
| `SY` | Syria |
|
|
113
|
+
| `VE` | Venezuela |
|
|
114
|
+
|
|
115
|
+
## WAF Expression Examples
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
# Block multiple countries
|
|
119
|
+
(ip.src.country in {"RU" "BY" "IR" "KP"})
|
|
120
|
+
|
|
121
|
+
# Block country + specific ASN
|
|
122
|
+
(ip.src.country eq "RU") or (ip.geoip.asnum eq 12345)
|
|
123
|
+
|
|
124
|
+
# Block all except allowlisted IPs
|
|
125
|
+
(ip.src.country in {"RU" "BY"}) and not (ip.src in {1.2.3.4/32})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Multi-Zone Script
|
|
129
|
+
|
|
130
|
+
To apply the same block rule across multiple zones:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
#!/usr/bin/env bash
|
|
134
|
+
API_TOKEN="YOUR_API_TOKEN"
|
|
135
|
+
ZONES=("ZONE_ID_1" "ZONE_ID_2" "ZONE_ID_3")
|
|
136
|
+
EXPRESSION='(ip.src.country in {"RU" "BY" "IR" "KP"})'
|
|
137
|
+
|
|
138
|
+
for ZONE_ID in "${ZONES[@]}"; do
|
|
139
|
+
EXISTING=$(curl -s -X GET \
|
|
140
|
+
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets" \
|
|
141
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
142
|
+
-H "Content-Type: application/json" | \
|
|
143
|
+
jq -r '.result[] | select(.phase == "http_request_firewall_custom") | .id')
|
|
144
|
+
|
|
145
|
+
if [ -z "$EXISTING" ]; then
|
|
146
|
+
METHOD="POST"
|
|
147
|
+
URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets"
|
|
148
|
+
DATA=$(jq -n --arg expr "$EXPRESSION" '{
|
|
149
|
+
name: "default", description: "WAF Custom Rules",
|
|
150
|
+
kind: "zone", phase: "http_request_firewall_custom",
|
|
151
|
+
rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
|
|
152
|
+
}')
|
|
153
|
+
else
|
|
154
|
+
METHOD="PUT"
|
|
155
|
+
URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$EXISTING"
|
|
156
|
+
DATA=$(jq -n --arg expr "$EXPRESSION" '{
|
|
157
|
+
description: "WAF Custom Rules",
|
|
158
|
+
rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
|
|
159
|
+
}')
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
RESULT=$(curl -s -X "$METHOD" "$URL" \
|
|
163
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
164
|
+
-H "Content-Type: application/json" \
|
|
165
|
+
--data "$DATA" | jq '.success')
|
|
166
|
+
|
|
167
|
+
echo "Zone $ZONE_ID: $RESULT"
|
|
168
|
+
done
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Notes
|
|
172
|
+
|
|
173
|
+
- `PUT` on an existing ruleset **replaces all rules** in it. Include all rules in the payload if the ruleset already has other rules.
|
|
174
|
+
- WAF Custom Rules require a Cloudflare plan that supports rulesets (Free includes basic WAF; Pro/Business/Enterprise for full custom rules).
|
|
175
|
+
- Changes are applied globally within seconds; no cache purge needed.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
name: cloudflare-block-countries
|
|
5
|
+
description: 'Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Use when user wants to geo-block traffic, block countries in Cloudflare, set up WAF country rules, or mentions blocking regions. Handles both creating new rulesets and updating existing ones.'
|
|
6
|
+
license: MIT
|
|
7
|
+
allowed-tools: Bash
|
|
8
|
+
compatibility: Codex, Claude Code, Cursor, and other Agent Skills compatible tools. Requires curl and jq.
|
|
9
|
+
metadata:
|
|
10
|
+
author: Oleg Koval
|
|
11
|
+
tags:
|
|
12
|
+
- cloudflare
|
|
13
|
+
- waf
|
|
14
|
+
- security
|
|
15
|
+
- geo-blocking
|
|
16
|
+
- firewall
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Cloudflare Country Block via WAF Custom Rules
|
|
20
|
+
|
|
21
|
+
Block traffic from specific countries using Cloudflare WAF Custom Rules and the Cloudflare API.
|
|
22
|
+
|
|
23
|
+
## Prerequisites
|
|
24
|
+
|
|
25
|
+
- Cloudflare API Token with `Zone:Rulesets:Edit` permission
|
|
26
|
+
- Zone ID for the target domain
|
|
27
|
+
- `curl` and `jq` installed
|
|
28
|
+
|
|
29
|
+
## Workflow
|
|
30
|
+
|
|
31
|
+
### 1. Get Zone IDs
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
curl -s -X GET \
|
|
35
|
+
"https://api.cloudflare.com/client/v4/zones?account.id=YOUR_ACCOUNT_ID&status=active" \
|
|
36
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
37
|
+
-H "Content-Type: application/json" | jq '.result[] | {name: .name, id: .id}'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Check Existing Custom Firewall Rulesets
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
curl -s -X GET \
|
|
44
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
|
|
45
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
46
|
+
-H "Content-Type: application/json" | \
|
|
47
|
+
jq '.result[] | select(.phase == "http_request_firewall_custom")'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If the output is empty — no existing ruleset. Go to **3a**. If a ruleset exists, note its `id` and go to **3b**.
|
|
51
|
+
|
|
52
|
+
### 3a. Create NEW Ruleset with Block Rule
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
curl -s -X POST \
|
|
56
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
|
|
57
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
58
|
+
-H "Content-Type: application/json" \
|
|
59
|
+
--data '{
|
|
60
|
+
"name": "default",
|
|
61
|
+
"description": "WAF Custom Rules",
|
|
62
|
+
"kind": "zone",
|
|
63
|
+
"phase": "http_request_firewall_custom",
|
|
64
|
+
"rules": [
|
|
65
|
+
{
|
|
66
|
+
"action": "block",
|
|
67
|
+
"description": "Block traffic from sanctioned countries",
|
|
68
|
+
"enabled": true,
|
|
69
|
+
"expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}' | jq '.success'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3b. Update EXISTING Ruleset
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
curl -s -X PUT \
|
|
79
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
|
|
80
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
81
|
+
-H "Content-Type: application/json" \
|
|
82
|
+
--data '{
|
|
83
|
+
"description": "WAF Custom Rules",
|
|
84
|
+
"rules": [
|
|
85
|
+
{
|
|
86
|
+
"action": "block",
|
|
87
|
+
"description": "Block traffic from sanctioned countries",
|
|
88
|
+
"enabled": true,
|
|
89
|
+
"expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}' | jq '.success'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 4. Verify the Rule is Active
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
curl -s -X GET \
|
|
99
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
|
|
100
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
101
|
+
-H "Content-Type: application/json" | jq '.result.rules'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Country Codes Reference
|
|
105
|
+
|
|
106
|
+
| Code | Country |
|
|
107
|
+
|------|---------|
|
|
108
|
+
| `RU` | Russia |
|
|
109
|
+
| `BY` | Belarus |
|
|
110
|
+
| `IR` | Iran |
|
|
111
|
+
| `KP` | North Korea |
|
|
112
|
+
| `CN` | China |
|
|
113
|
+
| `CU` | Cuba |
|
|
114
|
+
| `SY` | Syria |
|
|
115
|
+
| `VE` | Venezuela |
|
|
116
|
+
|
|
117
|
+
## WAF Expression Examples
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
# Block multiple countries
|
|
121
|
+
(ip.src.country in {"RU" "BY" "IR" "KP"})
|
|
122
|
+
|
|
123
|
+
# Block country + specific ASN
|
|
124
|
+
(ip.src.country eq "RU") or (ip.geoip.asnum eq 12345)
|
|
125
|
+
|
|
126
|
+
# Block all except allowlisted IPs
|
|
127
|
+
(ip.src.country in {"RU" "BY"}) and not (ip.src in {1.2.3.4/32})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Multi-Zone Script
|
|
131
|
+
|
|
132
|
+
To apply the same block rule across multiple zones:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
#!/usr/bin/env bash
|
|
136
|
+
API_TOKEN="YOUR_API_TOKEN"
|
|
137
|
+
ZONES=("ZONE_ID_1" "ZONE_ID_2" "ZONE_ID_3")
|
|
138
|
+
EXPRESSION='(ip.src.country in {"RU" "BY" "IR" "KP"})'
|
|
139
|
+
|
|
140
|
+
for ZONE_ID in "${ZONES[@]}"; do
|
|
141
|
+
EXISTING=$(curl -s -X GET \
|
|
142
|
+
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets" \
|
|
143
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
144
|
+
-H "Content-Type: application/json" | \
|
|
145
|
+
jq -r '.result[] | select(.phase == "http_request_firewall_custom") | .id')
|
|
146
|
+
|
|
147
|
+
if [ -z "$EXISTING" ]; then
|
|
148
|
+
METHOD="POST"
|
|
149
|
+
URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets"
|
|
150
|
+
DATA=$(jq -n --arg expr "$EXPRESSION" '{
|
|
151
|
+
name: "default", description: "WAF Custom Rules",
|
|
152
|
+
kind: "zone", phase: "http_request_firewall_custom",
|
|
153
|
+
rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
|
|
154
|
+
}')
|
|
155
|
+
else
|
|
156
|
+
METHOD="PUT"
|
|
157
|
+
URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$EXISTING"
|
|
158
|
+
DATA=$(jq -n --arg expr "$EXPRESSION" '{
|
|
159
|
+
description: "WAF Custom Rules",
|
|
160
|
+
rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
|
|
161
|
+
}')
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
RESULT=$(curl -s -X "$METHOD" "$URL" \
|
|
165
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
166
|
+
-H "Content-Type: application/json" \
|
|
167
|
+
--data "$DATA" | jq '.success')
|
|
168
|
+
|
|
169
|
+
echo "Zone $ZONE_ID: $RESULT"
|
|
170
|
+
done
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Notes
|
|
174
|
+
|
|
175
|
+
- `PUT` on an existing ruleset **replaces all rules** in it. Include all rules in the payload if the ruleset already has other rules.
|
|
176
|
+
- WAF Custom Rules require a Cloudflare plan that supports rulesets (Free includes basic WAF; Pro/Business/Enterprise for full custom rules).
|
|
177
|
+
- Changes are applied globally within seconds; no cache purge needed.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "olko:cloudflare-block-countries",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Handles creating new rulesets and updating existing ones across single or multiple zones.",
|
|
5
|
+
"skills": "skills/"
|
|
6
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
name: cloudflare-block-countries
|
|
5
|
+
description: 'Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Use when user wants to geo-block traffic, block countries in Cloudflare, set up WAF country rules, or mentions blocking regions. Handles both creating new rulesets and updating existing ones.'
|
|
6
|
+
license: MIT
|
|
7
|
+
allowed-tools: Bash
|
|
8
|
+
compatibility: Codex, Claude Code, Cursor, and other Agent Skills compatible tools. Requires curl and jq.
|
|
9
|
+
metadata:
|
|
10
|
+
author: Oleg Koval
|
|
11
|
+
tags:
|
|
12
|
+
- cloudflare
|
|
13
|
+
- waf
|
|
14
|
+
- security
|
|
15
|
+
- geo-blocking
|
|
16
|
+
- firewall
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Cloudflare Country Block via WAF Custom Rules
|
|
20
|
+
|
|
21
|
+
Block traffic from specific countries using Cloudflare WAF Custom Rules and the Cloudflare API.
|
|
22
|
+
|
|
23
|
+
## Prerequisites
|
|
24
|
+
|
|
25
|
+
- Cloudflare API Token with `Zone:Rulesets:Edit` permission
|
|
26
|
+
- Zone ID for the target domain
|
|
27
|
+
- `curl` and `jq` installed
|
|
28
|
+
|
|
29
|
+
## Workflow
|
|
30
|
+
|
|
31
|
+
### 1. Get Zone IDs
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
curl -s -X GET \
|
|
35
|
+
"https://api.cloudflare.com/client/v4/zones?account.id=YOUR_ACCOUNT_ID&status=active" \
|
|
36
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
37
|
+
-H "Content-Type: application/json" | jq '.result[] | {name: .name, id: .id}'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Check Existing Custom Firewall Rulesets
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
curl -s -X GET \
|
|
44
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
|
|
45
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
46
|
+
-H "Content-Type: application/json" | \
|
|
47
|
+
jq '.result[] | select(.phase == "http_request_firewall_custom")'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If the output is empty — no existing ruleset. Go to **3a**. If a ruleset exists, note its `id` and go to **3b**.
|
|
51
|
+
|
|
52
|
+
### 3a. Create NEW Ruleset with Block Rule
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
curl -s -X POST \
|
|
56
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
|
|
57
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
58
|
+
-H "Content-Type: application/json" \
|
|
59
|
+
--data '{
|
|
60
|
+
"name": "default",
|
|
61
|
+
"description": "WAF Custom Rules",
|
|
62
|
+
"kind": "zone",
|
|
63
|
+
"phase": "http_request_firewall_custom",
|
|
64
|
+
"rules": [
|
|
65
|
+
{
|
|
66
|
+
"action": "block",
|
|
67
|
+
"description": "Block traffic from sanctioned countries",
|
|
68
|
+
"enabled": true,
|
|
69
|
+
"expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}' | jq '.success'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3b. Update EXISTING Ruleset
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
curl -s -X PUT \
|
|
79
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
|
|
80
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
81
|
+
-H "Content-Type: application/json" \
|
|
82
|
+
--data '{
|
|
83
|
+
"description": "WAF Custom Rules",
|
|
84
|
+
"rules": [
|
|
85
|
+
{
|
|
86
|
+
"action": "block",
|
|
87
|
+
"description": "Block traffic from sanctioned countries",
|
|
88
|
+
"enabled": true,
|
|
89
|
+
"expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}' | jq '.success'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 4. Verify the Rule is Active
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
curl -s -X GET \
|
|
99
|
+
"https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
|
|
100
|
+
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
|
101
|
+
-H "Content-Type: application/json" | jq '.result.rules'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Country Codes Reference
|
|
105
|
+
|
|
106
|
+
| Code | Country |
|
|
107
|
+
|------|---------|
|
|
108
|
+
| `RU` | Russia |
|
|
109
|
+
| `BY` | Belarus |
|
|
110
|
+
| `IR` | Iran |
|
|
111
|
+
| `KP` | North Korea |
|
|
112
|
+
| `CN` | China |
|
|
113
|
+
| `CU` | Cuba |
|
|
114
|
+
| `SY` | Syria |
|
|
115
|
+
| `VE` | Venezuela |
|
|
116
|
+
|
|
117
|
+
## WAF Expression Examples
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
# Block multiple countries
|
|
121
|
+
(ip.src.country in {"RU" "BY" "IR" "KP"})
|
|
122
|
+
|
|
123
|
+
# Block country + specific ASN
|
|
124
|
+
(ip.src.country eq "RU") or (ip.geoip.asnum eq 12345)
|
|
125
|
+
|
|
126
|
+
# Block all except allowlisted IPs
|
|
127
|
+
(ip.src.country in {"RU" "BY"}) and not (ip.src in {1.2.3.4/32})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Multi-Zone Script
|
|
131
|
+
|
|
132
|
+
To apply the same block rule across multiple zones:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
#!/usr/bin/env bash
|
|
136
|
+
API_TOKEN="YOUR_API_TOKEN"
|
|
137
|
+
ZONES=("ZONE_ID_1" "ZONE_ID_2" "ZONE_ID_3")
|
|
138
|
+
EXPRESSION='(ip.src.country in {"RU" "BY" "IR" "KP"})'
|
|
139
|
+
|
|
140
|
+
for ZONE_ID in "${ZONES[@]}"; do
|
|
141
|
+
EXISTING=$(curl -s -X GET \
|
|
142
|
+
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets" \
|
|
143
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
144
|
+
-H "Content-Type: application/json" | \
|
|
145
|
+
jq -r '.result[] | select(.phase == "http_request_firewall_custom") | .id')
|
|
146
|
+
|
|
147
|
+
if [ -z "$EXISTING" ]; then
|
|
148
|
+
METHOD="POST"
|
|
149
|
+
URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets"
|
|
150
|
+
DATA=$(jq -n --arg expr "$EXPRESSION" '{
|
|
151
|
+
name: "default", description: "WAF Custom Rules",
|
|
152
|
+
kind: "zone", phase: "http_request_firewall_custom",
|
|
153
|
+
rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
|
|
154
|
+
}')
|
|
155
|
+
else
|
|
156
|
+
METHOD="PUT"
|
|
157
|
+
URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$EXISTING"
|
|
158
|
+
DATA=$(jq -n --arg expr "$EXPRESSION" '{
|
|
159
|
+
description: "WAF Custom Rules",
|
|
160
|
+
rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
|
|
161
|
+
}')
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
RESULT=$(curl -s -X "$METHOD" "$URL" \
|
|
165
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
166
|
+
-H "Content-Type: application/json" \
|
|
167
|
+
--data "$DATA" | jq '.success')
|
|
168
|
+
|
|
169
|
+
echo "Zone $ZONE_ID: $RESULT"
|
|
170
|
+
done
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Notes
|
|
174
|
+
|
|
175
|
+
- `PUT` on an existing ruleset **replaces all rules** in it. Include all rules in the payload if the ruleset already has other rules.
|
|
176
|
+
- WAF Custom Rules require a Cloudflare plan that supports rulesets (Free includes basic WAF; Pro/Business/Enterprise for full custom rules).
|
|
177
|
+
- Changes are applied globally within seconds; no cache purge needed.
|