mcpbeat

Cloudflare Workers Security

secondsky/cloudflare-workers-security

Cloudflare Workers security with authentication, CORS, rate limiting, input validation. Use for securing APIs, JWT/API keys, or encountering auth failures, CORS errors, XSS/injection vulnerabilities.

This is a copy. The original lives at comeonoliver/cloudflare-workers-security.

33k tokens
context cost
the whole folder, loaded on every use
12
files
ships runnable scripts
0
copies elsewhere
how many repositories repackaged it
202
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/secondsky/claude-skills --skill cloudflare-workers-security

The instruction itself

15 sections, as written by the author

Cloudflare Workers Security

Comprehensive security patterns for protecting Workers and APIs.

Quick Security Checklist

// 1. Validate all input
const validated = schema.parse(await request.json());

// 2. Authenticate requests
const user = await verifyToken(request.headers.get('Authorization'));
if (!user) return new Response('Unauthorized', { status: 401 });

// 3. Rate limit
const limited = await rateLimiter.check(clientIP);
if (!limited.allowed) return new Response('Too Many Requests', { status: 429 });

// 4. Add security headers
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'DENY');

// 5. Use HTTPS-only cookies
headers.set('Set-Cookie', 'session=xxx; Secure; HttpOnly; SameSite=Strict');

Critical Rules

  • Never trust client input - Validate and sanitize everything
  • Use secure secrets - Store in Wrangler secrets, never in code
  • Implement rate limiting - Protect against abuse
  • Set security headers - Prevent common attacks
  • Use CORS properly - Don't use * in production

Top 10 Security Errors

| Vulnerability | Symptom | Prevention |

|---------------|---------|------------|

| Missing auth | Unauthorized access | Verify tokens on every request |

| SQL injection | Data breach | Use parameterized queries with D1 |

| XSS | Script injection | Sanitize output, set CSP |

| CORS misconfiguration | Blocked requests or open access | Configure specific origins |

| Secrets in code | Exposed credentials | Use wrangler secret |

| Missing rate limits | DoS vulnerability | Implement per-IP limits |

| Weak tokens | Session hijacking | Use crypto.subtle for signing |

| Missing HTTPS | Data interception | Enforce HTTPS redirects |

| Insecure headers | Clickjacking, MIME attacks | Set security headers |

| Excessive permissions | Blast radius | Principle of least privilege |

Authentication Patterns

JWT Verification

async function verifyJWT(token: string, secret: string): Promise<{ valid: boolean; payload?: unknown }> {
  try {
    const [headerB64, payloadB64, signatureB64] = token.split('.');

    // Verify signature
    const key = await crypto.subtle.importKey(
      'raw',
      new TextEncoder().encode(secret),
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['verify']
    );

    const signature = Uint8Array.from(atob(signatureB64.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
    const data = new TextEncoder().encode(`${headerB64}.${payloadB64}`);

    const valid = await crypto.subtle.verify('HMAC', key, signature, data);
    if (!valid) return { valid: false };

    // Decode payload
    const payload = JSON.parse(atob(payloadB64.replace(/-/g, '+').replace(/_/g, '/')));

    // Check expiration
    if (payload.exp && Date.now() / 1000 > payload.exp) {
      return { valid: false };
    }

    return { valid: true, payload };
  } catch {
    return { valid: false };
  }
}

API Key Validation

async function validateApiKey(
  request: Request,
  env: Env
): Promise<{ valid: boolean; clientId?: string }> {
  const apiKey = request.headers.get('X-API-Key');
  if (!apiKey) return { valid: false };

  // Hash the key for lookup (never store plain keys)
  const keyHash = await sha256(apiKey);

  // Lookup in KV or D1
  const client = await env.KV.get(`apikey:${keyHash}`, 'json');
  if (!client) return { valid: false };

  return { valid: true, clientId: client.id };
}

async function sha256(str: string): Promise<string> {
  const buffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str));
  return [...new Uint8Array(buffer)].map(b => b.toString(16).padStart(2, '0')).join('');
}

Input Validation

With Zod

import { z } from 'zod';

const UserSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).max(150).optional(),
});

async function handleCreate(request: Request): Promise<Response> {
  try {
    const body = await request.json();
    const user = UserSchema.parse(body);

    // Safe to use validated data
    return Response.json({ success: true, user });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return Response.json({ error: 'Validation failed', details: error.errors }, { status: 400 });
    }
    throw error;
  }
}

Security Headers

function addSecurityHeaders(response: Response): Response {
  const headers = new Headers(response.headers);

  // Prevent MIME type sniffing
  headers.set('X-Content-Type-Options', 'nosniff');

  // Prevent clickjacking
  headers.set('X-Frame-Options', 'DENY');

  // XSS protection
  headers.set('X-XSS-Protection', '1; mode=block');

  // Content Security Policy
  headers.set('Content-Security-Policy', "default-src 'self'; script-src 'self'");

  // HSTS
  headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');

  // Referrer policy
  headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');

  return new Response(response.body, { status: response.status, headers });
}

CORS Configuration

const ALLOWED_ORIGINS = ['https://app.example.com', 'https://admin.example.com'];

function handleCORS(request: Request, response: Response): Response {
  const origin = request.headers.get('Origin');

  if (!origin || !ALLOWED_ORIGINS.includes(origin)) {
    return response; // No CORS headers
  }

  const headers = new Headers(response.headers);
  headers.set('Access-Control-Allow-Origin', origin);
  headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  headers.set('Access-Control-Allow-Credentials', 'true');
  headers.set('Access-Control-Max-Age', '86400');

  return new Response(response.body, { status: response.status, headers });
}

When to Load References

Load specific references based on the task:

  • Implementing authentication? → Load references/authentication.md
  • CORS issues? → Load references/cors-security.md
  • Validating input? → Load references/input-validation.md
  • Managing secrets? → Load references/secrets-management.md
  • Rate limiting? → Load references/rate-limiting.md
  • Security headers? → Load references/security-headers.md

Templates

| Template | Purpose | Use When |

|----------|---------|----------|

| templates/auth-middleware.ts | JWT/API key auth | Adding authentication |

| templates/cors-handler.ts | CORS middleware | Handling cross-origin |

| templates/rate-limiter.ts | Rate limiting | Preventing abuse |

| templates/secure-worker.ts | Full secure setup | Starting secure project |

Scripts

| Script | Purpose | Command |

|--------|---------|---------|

| scripts/security-audit.sh | Audit security | ./security-audit.sh <url> |

Resources

  • Security: https://developers.cloudflare.com/workers/platform/security/
  • WAF: https://developers.cloudflare.com/waf/
  • Rate Limiting: https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/

How to use it

Copy the folder

Take secondsky/cloudflare-workers-security from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.