From cloudflare
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 claudepluginhub nsheaps/ai-mktpl --plugin cloudflareThis skill uses the workspace's default tool permissions.
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.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides agent creation for Claude Code plugins with file templates, frontmatter specs (name, description, model), triggering examples, system prompts, and best practices.
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 |
| 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 |
|---|---|
user@example.com | |
| 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: ["partner@other.com"],
},
],
});
// 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.