plugins/cloudflare/skills/zero-trust/SKILL.md
Use this skill when the user asks about Cloudflare Zero Trust, Access policies, identity-based access control, protecting self-hosted apps, WARP client, Gateway DNS filtering, device posture, or managing Zero Trust resources with Pulumi. For tunnel setup (the transport layer), also recall the cloudflare-tunnels skill. For deploying Access-protected apps via docker-compose, recall the arcane plugin's arcane-gitops skill.
npx skillsauth add nsheaps/ai-mktpl cloudflare-zero-trustInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Cloudflare Zero Trust (formerly Cloudflare for Teams) replaces traditional VPNs with identity-aware access policies. Every request is authenticated and authorized at Cloudflare's edge before reaching your application.
User → Cloudflare Edge → Access Policy Check → Tunnel → Your App
↓
Identity Provider (IdP)
(Google, GitHub, Okta, SAML, etc.)
The typical Zero Trust stack:
cloudflare-tunnels skill)| Concept | Description |
| --------------------- | ------------------------------------------------------------------- |
| Application | A hostname or path to protect (e.g., app.example.com) |
| Policy | Rules that determine who can access an application |
| Identity Provider | Where users authenticate (Google, GitHub, Okta, one-time PIN, etc.) |
| Service Token | Machine-to-machine auth (no human login) |
| mTLS | Mutual TLS certificate-based auth |
| Session Duration | How long an authenticated session lasts before re-auth |
| Provider | Type | Setup Complexity | | ------------------------ | --------- | ------------------------- | | One-time PIN (email) | Built-in | None — works immediately | | Google | OAuth | Add client ID/secret | | GitHub | OAuth | Add client ID/secret | | Okta | SAML/OIDC | Configure in Okta admin | | Azure AD | SAML/OIDC | Configure in Azure portal | | SAML (generic) | SAML | Any SAML 2.0 IdP | | OpenID Connect (generic) | OIDC | Any OIDC provider |
Policies use Include (match any), Exclude (deny if matched), and Require (must match all):
| Rule Type | Examples |
| --------------------- | ------------------------------------------ |
| Email | [email protected] |
| Email domain | @example.com |
| IP range | 192.168.1.0/24 |
| Country | US, GB, etc. |
| Authentication method | Specific IdP |
| Service token | Machine-to-machine |
| Device posture | WARP enrolled, OS version, disk encryption |
| External evaluation | Custom API check |
Typical flow for protecting a self-hosted app behind a tunnel:
1. Deploy app via docker-compose (on same network as cloudflared)
2. Create tunnel ingress rule: app.example.com → http://my-app:8080
3. Create Access Application: app.example.com
4. Create Access Policy: Allow @example.com
5. Add DNS CNAME: app.example.com → <tunnel-id>.cfargotunnel.com
If using Arcane for GitOps, the docker-compose stack goes in hosts/<hostname>/<app>/docker-compose.yaml and the Access configuration is managed via Pulumi (see below).
Gateway provides network-level security for devices running the WARP client:
Block malicious domains, content categories, or specific domains:
Block security threats: dns.security_category in {68 178 80}
Block social media: dns.content_category in {171}
Block specific domain: dns.fqdn == "example.com"
Allow exception: dns.fqdn == "allowed.example.com"
Inspect and filter HTTP traffic (requires WARP + TLS inspection):
Block file uploads: http.request.method == "POST" and http.upload
Block malware downloads: http.response.content_type matches "application/.*executable"
Control traffic at the network layer:
Block SSH to external: net.dst.port == 22 and not net.dst.ip in {10.0.0.0/8}
WARP is the client-side agent that routes device traffic through Cloudflare:
Install: https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/download-warp/
# macOS (MDM)
# Deploy via Jamf, Kandji, etc. with a JSON config:
{
"organization": "your-team-name",
"auth_client_id": "<service-token-id>.access",
"auth_client_secret": "<service-token-secret>"
}
import * as cloudflare from "@pulumi/cloudflare";
const cfConfig = new pulumi.Config("cloudflare");
const accountId = cfConfig.require("accountId");
// Look up the zone
const zone = cloudflare.getZoneOutput({ name: "example.com", accountId });
// Protect an application
const app = new cloudflare.ZeroTrustAccessApplication("internal-app", {
zoneId: zone.id,
name: "Internal Dashboard",
domain: "dashboard.example.com",
type: "self_hosted",
sessionDuration: "24h",
autoRedirectToIdentity: true,
// Optional: custom logo, cors settings, etc.
appLauncherVisible: true,
logoUrl: "https://example.com/logo.png",
});
// Allow team members by email domain
const teamPolicy = new cloudflare.ZeroTrustAccessPolicy("allow-team", {
applicationId: app.id,
zoneId: zone.id,
name: "Allow Team",
precedence: 1,
decision: "allow",
includes: [
{
emailDomains: ["example.com"],
},
],
});
// Allow specific external users
const externalPolicy = new cloudflare.ZeroTrustAccessPolicy("allow-external", {
applicationId: app.id,
zoneId: zone.id,
name: "Allow External Partners",
precedence: 2,
decision: "allow",
includes: [
{
emails: ["[email protected]"],
},
],
});
// Service token for machine-to-machine access (CI/CD, APIs)
const serviceToken = new cloudflare.ZeroTrustAccessServiceToken("api-token", {
accountId,
name: "CI/CD Pipeline",
duration: "8760h", // 1 year
});
const apiPolicy = new cloudflare.ZeroTrustAccessPolicy("allow-service-token", {
applicationId: app.id,
zoneId: zone.id,
name: "Allow Service Token",
precedence: 3,
decision: "non_identity",
includes: [
{
serviceTokens: [serviceToken.id],
},
],
});
export const serviceTokenClientId = serviceToken.clientId;
export const serviceTokenClientSecret = serviceToken.clientSecret;
// Block malware and phishing domains
const blockMalware = new cloudflare.ZeroTrustGatewayPolicy("block-malware", {
accountId,
name: "Block Security Threats",
action: "block",
enabled: true,
filters: ["dns"],
traffic: "any(dns.security_category[*] in {68 178 80 83})",
description: "Block malware, phishing, cryptomining, and command-and-control domains",
});
// Block adult content
const blockAdult = new cloudflare.ZeroTrustGatewayPolicy("block-adult", {
accountId,
name: "Block Adult Content",
action: "block",
enabled: true,
filters: ["dns"],
traffic: "any(dns.content_category[*] in {4})",
precedence: 2,
});
// Allow-list specific domains
const allowDocs = new cloudflare.ZeroTrustGatewayPolicy("allow-docs", {
accountId,
name: "Allow Documentation Sites",
action: "allow",
enabled: true,
filters: ["dns"],
traffic: 'dns.fqdn in {"docs.example.com" "wiki.example.com"}',
precedence: 1, // Higher precedence = evaluated first
});
// 1. Create tunnel (see cloudflare-tunnels skill)
const tunnel = new cloudflare.ZeroTrustTunnelCloudflared("app-tunnel", {
accountId,
name: "app-tunnel",
secret: tunnelSecret,
});
// 2. Configure tunnel ingress
const tunnelConfig = new cloudflare.ZeroTrustTunnelCloudflaredConfig("app-tunnel-config", {
accountId,
tunnelId: tunnel.id,
config: {
ingressRules: [
{ hostname: "dashboard.example.com", service: "http://dashboard:3000" },
{ service: "http_status:404" },
],
},
});
// 3. DNS record
const dns = new cloudflare.Record("dashboard-dns", {
zoneId: zone.id,
name: "dashboard",
type: "CNAME",
content: pulumi.interpolate`${tunnel.id}.cfargotunnel.com`,
proxied: true,
});
// 4. Access protection
const accessApp = new cloudflare.ZeroTrustAccessApplication("dashboard", {
zoneId: zone.id,
name: "Dashboard",
domain: "dashboard.example.com",
type: "self_hosted",
sessionDuration: "24h",
});
const accessPolicy = new cloudflare.ZeroTrustAccessPolicy("dashboard-policy", {
applicationId: accessApp.id,
zoneId: zone.id,
name: "Allow Team",
precedence: 1,
decision: "allow",
includes: [{ emailDomains: ["example.com"] }],
});
Many self-hosted apps have admin panels that should be locked down:
| App | Path/Hostname | Policy |
| -------------- | ----------------------- | --------------------------------- |
| Nextcloud | nextcloud.example.com | Allow @example.com |
| n8n | n8n.example.com | Allow specific emails |
| Grafana | grafana.example.com | Allow @example.com |
| Portainer | portainer.example.com | Allow admin email only |
| Home Assistant | ha.example.com | Allow @example.com + require WARP |
If your app exposes a public API alongside a protected dashboard:
// Protect the whole app
const app = new cloudflare.ZeroTrustAccessApplication("my-app", {
domain: "app.example.com",
// ...
});
// But allow unauthenticated access to /api/*
const bypassPolicy = new cloudflare.ZeroTrustAccessPolicy("bypass-api", {
applicationId: app.id,
zoneId: zone.id,
name: "Bypass API",
precedence: 0,
decision: "bypass",
includes: [{ anyValidServiceToken: {} }],
});
| Feature | Free (50 users) | Pay-as-you-go | | ----------------- | --------------- | -------------- | | Access | Included | $7/user/month | | Gateway | Included | $7/user/month | | WARP (Zero Trust) | Included | $7/user/month | | Browser Isolation | — | $10/user/month |
The free plan (50 users) is generous for personal and small-team use.
tools
Reference material for Claude Code internals — the on-disk layout under ~/.claude and project-scope .claude, the plugin cache, session-env propagation, and the full hook lifecycle. Auto-recall when working on Claude-Code-related tasks: writing or debugging hooks, authoring plugins, inspecting session state, troubleshooting why an env var is or isn't visible to a Bash tool call, or when paths under ~/.claude or ~/.claude/plugins/ come up.
development
Manage GitHub App installation tokens in Claude Code sessions. Use when tokens expire, auth errors occur in long-running sessions, or when setting up GitHub App credentials for agent teams. <example>my github token expired</example> <example>refresh the github app token</example> <example>check token status</example> <example>set up github app authentication for this session</example>
tools
Auto-detect project formatting tools and configure edit-utils settings
tools
Use this skill when the user asks about 1Password, secrets management, retrieving credentials, using op CLI, service accounts, secret references, vault operations, or any task involving the 1Password CLI (op). Also use when needing to inject secrets into environment variables, read passwords or API keys from 1Password, or manage 1Password items from the command line.