Install
$ agentstack add skill-michael-ltm-cloudflare-skill-cloudflare-ops ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Cloudflare Operations
AI-driven Cloudflare management — DNS, SSL, Workers, Pages, Storage, and more.
⚠ SENSITIVE DATA — MANDATORY OUTPUT RULES
These rules apply to EVERY response. No exceptions.
Never display these in chat output:
| Sensitive field | Instead say | |---|---| | API Token (api_token) | cf_token=*** | | Account ID (account_id) | account_id=*** | | Any *_TOKEN, *_KEY, *_SECRET | *** | | Contents of .cloudflare/credentials.json | Show key names only, mask values |
When constructing shell commands for explanations:
# ✗ Never write this in chat:
curl -H "Authorization: Bearer eyJhbGc..." ...
# ✓ Write this instead (run the actual command silently via tool):
# [cloudflare] Running: list DNS records for awapi.org
Checklist before every response:
- [ ] No raw API tokens shown
- [ ] No Account IDs shown
- [ ] Credentials file content masked
Session Start — Read Credentials First
Before doing anything, check workspace for saved credentials:
ls .cloudflare/credentials.json 2>/dev/null && echo "FOUND" || echo "NOT_FOUND"
If FOUND → Read file silently, load tokens into memory. Tell user: "Loaded Cloudflare credentials for account [label]." Do NOT display token or account ID values.
If NOT_FOUND → Follow [Credential Setup](#credential-setup) below. Ask the user for credentials interactively.
Credential Setup (First Time)
When no credentials file exists, ask the user:
> "To get started, I need your Cloudflare API credentials. Please provide: > 1. API Token — create at https://dash.cloudflare.com/profile/api-tokens > (Required permissions: Zone:DNS:Edit, Zone:SSL:Edit, Zone:Zone:Read, Account:Workers:Edit, Account:R2:Edit, Account:D1:Edit) > 2. Account ID — found in Cloudflare Dashboard right sidebar > 3. Label — a name for this account (e.g. 'personal', 'work') > > I'll save these locally to .cloudflare/credentials.json and add it to .gitignore automatically."
After user provides credentials, write the file using a tool (never echo secrets in terminal):
# Write credentials file (use Write tool, not Shell echo)
import json, os
os.makedirs(".cloudflare", exist_ok=True)
creds = {
"_note": "Cloudflare credentials — DO NOT COMMIT — managed by cloudflare-ops skill",
"default": "ACCOUNT_LABEL",
"accounts": {
"ACCOUNT_LABEL": {
"label": "USER_PROVIDED_LABEL",
"api_token": "USER_PROVIDED_TOKEN",
"account_id": "USER_PROVIDED_ACCOUNT_ID",
"email": "optional"
}
}
}
json.dump(creds, open(".cloudflare/credentials.json", "w"), indent=2)
Then protect from git:
# Auto-add to .gitignore
grep -q ".cloudflare/" .gitignore 2>/dev/null || echo ".cloudflare/" >> .gitignore
Confirm to user: "Credentials saved to .cloudflare/credentials.json and added to .gitignore. The file will never be committed."
Load Credentials (Every Session)
import json
def load_cf_creds(account=None):
d = json.load(open(".cloudflare/credentials.json"))
acct = d["accounts"][account or d["default"]]
return acct["api_token"], acct["account_id"]
CF_TOKEN, CF_ACCOUNT = load_cf_creds()
CF_API = "https://api.cloudflare.com/client/v4"
DNS Management
Get Zone ID
ZONE_ID=$(curl -s -H "Authorization: Bearer $CF_TOKEN" \
"$CF_API/zones?name=DOMAIN.COM" | \
python3 -c "import json,sys; print(json.load(sys.stdin)['result'][0]['id'])")
List DNS records
curl -s -H "Authorization: Bearer $CF_TOKEN" "$CF_API/zones/$ZONE_ID/dns_records" | \
python3 -c "
import json,sys
for r in json.load(sys.stdin)['result']:
print(r['type'], r['name'], r['content'], 'proxied' if r.get('proxied') else 'dns-only')
"
Add record
curl -s -X POST -H "Authorization: Bearer $CF_TOKEN" -H "Content-Type: application/json" \
"$CF_API/zones/$ZONE_ID/dns_records" \
-d '{"type":"A","name":"sub","content":"1.2.3.4","proxied":false,"ttl":1}'
# proxied: true = orange cloud (CDN), false = gray cloud (DNS only)
Update / Delete record
# Update
curl -s -X PATCH -H "Authorization: Bearer $CF_TOKEN" -H "Content-Type: application/json" \
"$CF_API/zones/$ZONE_ID/dns_records/RECORD_ID" \
-d '{"content":"new.ip","proxied":true}'
# Delete
curl -s -X DELETE -H "Authorization: Bearer $CF_TOKEN" \
"$CF_API/zones/$ZONE_ID/dns_records/RECORD_ID"
SSL / TLS
Set SSL mode
# mode: off | flexible | full | strict
curl -s -X PATCH -H "Authorization: Bearer $CF_TOKEN" -H "Content-Type: application/json" \
"$CF_API/zones/$ZONE_ID/settings/ssl" -d '{"value":"strict"}'
Create Origin Certificate (15-year, for Full Strict)
curl -s -X POST -H "Authorization: Bearer $CF_TOKEN" -H "Content-Type: application/json" \
"$CF_API/certificates" \
-d '{"hostnames":["DOMAIN.COM","*.DOMAIN.COM"],"requested_validity":5475,"request_type":"origin-rsa","csr":""}'
# Save: certificate field → origin.crt, private_key → origin.key
# NEVER display private_key in chat
Workers
Deploy via Wrangler
export CLOUDFLARE_API_TOKEN=$CF_TOKEN # set env var, don't put in wrangler.toml
wrangler deploy
Deploy via API
curl -s -X PUT -H "Authorization: Bearer $CF_TOKEN" \
-F "metadata={\"main_module\":\"worker.js\"};type=application/json" \
-F "worker.js=@worker.js;type=application/javascript+module" \
"$CF_API/accounts/$CF_ACCOUNT/workers/scripts/WORKER_NAME"
Secrets
# Use wrangler (interactive, never echoes to terminal)
wrangler secret put SECRET_NAME
Pages
wrangler pages project create MY_PROJECT
wrangler pages deploy ./dist --project-name MY_PROJECT
R2 Object Storage
wrangler r2 bucket create BUCKET_NAME
wrangler r2 object put BUCKET/path/file.txt --file ./local.txt
wrangler r2 object list BUCKET_NAME
D1 Database
wrangler d1 create MY_DB
wrangler d1 execute MY_DB --command "SELECT * FROM users"
wrangler d1 execute MY_DB --file schema.sql
wrangler d1 export MY_DB --output backup.sql
KV
wrangler kv namespace create MY_KV
wrangler kv key put --namespace-id=NS_ID "key" "value"
wrangler kv key get --namespace-id=NS_ID "key"
Multi-Account
.cloudflare/credentials.json supports multiple accounts:
{
"default": "personal",
"accounts": {
"personal": { "label": "Personal", "api_token": "***", "account_id": "***" },
"work": { "label": "Work", "api_token": "***", "account_id": "***" }
}
}
Switch: load_cf_creds(account="work")
To add an account, ask the user for token + account ID, then append to the file using Write tool.
Wrangler Config Template
# wrangler.toml — safe to commit (no secrets)
name = "my-app"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "DATABASE_ID" # not secret
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-bucket"
[[kv_namespaces]]
binding = "KV"
id = "NAMESPACE_ID" # not secret
Secrets go in wrangler secret put — never in wrangler.toml.
Reference
- API reference: https://developers.cloudflare.com/api/
- Wrangler: https://developers.cloudflare.com/workers/wrangler/
- Workers: https://developers.cloudflare.com/workers/
- R2: https://developers.cloudflare.com/r2/
- D1: https://developers.cloudflare.com/d1/
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: michael-ltm
- Source: michael-ltm/cloudflare-skill
- License: Apache-2.0
- Homepage: https://github.com/michael-ltm/cloudflare-skill
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.