dancon-secure-cfworker/SKILL.md
Generate secure Cloudflare Worker code in TypeScript that avoids all weaknesses covered by OWASP Top 10 (2025) and CWE Top 25 (2025). Use this skill whenever the user asks to create, write, scaffold, or generate a Cloudflare Worker, CF Worker, edge function, or serverless function on Cloudflare. Also trigger when the user asks to build a secure API, secure endpoint, secure webhook handler, or any TypeScript code targeting the Workers runtime. Always use this skill over generic code generation when the target is Cloudflare Workers.
npx skillsauth add danielyan-consulting/skills dancon-secure-cfworkerInstall 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.
This skill generates security-hardened Cloudflare Worker code in TypeScript. Every piece of generated code systematically addresses the applicable weaknesses catalogued in the OWASP Top 10 (2025) and CWE Top 25 (2025), applying defence-in-depth principles tailored to the Workers runtime.
references/security-checklist.md -- it contains the full mapping of OWASP/CWE items to concrete Workers-specific mitigations. Consult it while generating code so nothing is missed."YOUR_SECRET_HERE" or references to environment variables (e.g. env.API_SECRET) instead. If you identify a secret the user has accidentally shared, warn them and do not repeat it.Every generated Worker follows these foundational principles. They are not arbitrary rules -- each one exists because violating it opens a door to one or more OWASP/CWE weaknesses.
All data arriving from outside the Worker (request bodies, query parameters, headers, path segments, WebSocket messages) is untrusted. Validate and sanitise everything before use. This directly addresses:
Implementation pattern:
// Use a validation library (e.g. zod) or hand-roll strict schemas
import { z } from "zod";
const CreateUserSchema = z.object({
email: z.string().email().max(254),
name: z.string().min(1).max(100).regex(/^[\p{L}\p{N}\s\-'.]+$/u),
age: z.number().int().min(13).max(150),
});
// In your handler:
const parseResult = CreateUserSchema.safeParse(body);
if (!parseResult.success) {
return errorResponse(400, "Invalid input", parseResult.error.flatten());
}
const validated = parseResult.data; // Use only this from here on
When constructing D1 queries, always use parameterised statements:
// CORRECT -- parameterised
const result = await env.DB.prepare(
"SELECT * FROM users WHERE email = ?1"
).bind(validated.email).first();
// NEVER do this:
// await env.DB.prepare(`SELECT * FROM users WHERE email = '${email}'`).first();
Every route and resource access check must be explicit. Never rely on obscurity or client-side checks. This addresses:
Implementation pattern:
// Authenticate first, authorise second
async function requireAuth(
request: Request,
env: Env
): Promise<AuthenticatedUser> {
const token = request.headers.get("Authorization")?.replace("Bearer ", "");
if (!token) {
throw new HttpError(401, "Authentication required");
}
// Validate token (JWT verification, session lookup, etc.)
const user = await verifyToken(token, env);
if (!user) {
throw new HttpError(401, "Invalid or expired token");
}
return user;
}
// Resource-level authorisation -- never trust user-supplied IDs blindly
async function requireOwnership(
user: AuthenticatedUser,
resourceOwnerId: string
): Promise<void> {
if (user.id !== resourceOwnerId && user.role !== "admin") {
throw new HttpError(403, "Access denied");
}
}
CORS must be explicit and restrictive:
function corsHeaders(request: Request, env: Env): HeadersInit {
const origin = request.headers.get("Origin") ?? "";
const allowed = env.ALLOWED_ORIGINS?.split(",") ?? [];
if (!allowed.includes(origin)) {
return {}; // No CORS headers -- browser blocks it
}
return {
"Access-Control-Allow-Origin": origin, // Never use "*" with credentials
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Max-Age": "86400",
};
}
This addresses OWASP A07:2025 Identification and Authentication Failures and CWE-287 (Improper Authentication), CWE-306 (Missing Authentication for Critical Function).
crypto.subtle.verify() or crypto.subtle.importKey(). Never decode without verifying the signature.// Timing-safe token comparison
async function timingSafeCompare(a: string, b: string): Promise<boolean> {
const encoder = new TextEncoder();
const keyData = encoder.encode("comparison-key");
const key = await crypto.subtle.importKey(
"raw", keyData, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]
);
const sigA = await crypto.subtle.sign("HMAC", key, encoder.encode(a));
const sigB = await crypto.subtle.sign("HMAC", key, encoder.encode(b));
return crypto.subtle.timingSafeEqual(sigA, sigB);
}
This addresses OWASP A04:2025 Cryptographic Failures and CWE-327 (Use of Broken Crypto), CWE-328 (Use of Weak Hash), CWE-916 (Insufficient Password Hashing).
crypto.subtle) for all cryptographic operations.crypto.randomUUID() for identifiers and crypto.getRandomValues() for random bytes.Math.random() for anything security-sensitive.wrangler secret put, access them via env.This addresses OWASP A02:2025 Security Misconfiguration and OWASP A03:2025 Software Supply Chain Failures.
wrangler secret put and .dev.vars (gitignored).compatibility_date current for latest runtime security patches.nodejs_compat for access to node:crypto and other secure built-ins.npm audit before deployment.This addresses OWASP A09:2025 Security Logging and Alerting Failures, OWASP A10:2025 Mishandling of Exceptional Conditions, and CWE-209 (Information Exposure Through Error Message), CWE-532 (Log Injection).
// Structured error response -- never leak internals
function errorResponse(
status: number,
message: string,
details?: unknown
): Response {
// In production, strip details
const body: Record<string, unknown> = {
error: message,
status,
};
// Only include details in non-production
if (details && !isProduction()) {
body.details = details;
}
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
// Structured logging -- sanitise user input before logging
function secureLog(
level: "info" | "warn" | "error",
message: string,
context: Record<string, unknown>
): void {
// Strip or encode control characters to prevent log injection
const sanitised = Object.fromEntries(
Object.entries(context).map(([k, v]) => [
k,
typeof v === "string"
? v.replace(/[\x00-\x1f\x7f]/g, "")
: v,
])
);
console.log(JSON.stringify({ level, message, ...sanitised, ts: Date.now() }));
}
Every response should include security headers:
function securityHeaders(): HeadersInit {
return {
"Content-Type": "application/json",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"Referrer-Policy": "strict-origin-when-cross-origin",
"Permissions-Policy": "camera=(), microphone=(), geolocation=()",
"Cache-Control": "no-store",
"Strict-Transport-Security": "max-age=63072000; includeSubDomains; preload",
};
}
Workers reuse V8 isolates across requests. Storing request-scoped data in module-level variables causes cross-request data leaks -- a direct path to information disclosure (CWE-200, CWE-212).
// WRONG -- leaks data between requests
let currentUser: User | null = null;
// CORRECT -- request-scoped
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const user = await authenticate(request, env); // scoped to this invocation
return handleRequest(request, env, ctx, user);
},
};
This addresses CWE-400 (Uncontrolled Resource Consumption) and contributes to OWASP A10:2025.
// Enforce body size limit
async function readBodyWithLimit(
request: Request,
maxBytes: number
): Promise<ArrayBuffer> {
const contentLength = parseInt(request.headers.get("Content-Length") ?? "0");
if (contentLength > maxBytes) {
throw new HttpError(413, "Request body too large");
}
const reader = request.body?.getReader();
if (!reader) throw new HttpError(400, "Missing request body");
const chunks: Uint8Array[] = [];
let totalSize = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
totalSize += value.byteLength;
if (totalSize > maxBytes) {
reader.cancel();
throw new HttpError(413, "Request body too large");
}
chunks.push(value);
}
const result = new Uint8Array(totalSize);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.byteLength;
}
return result.buffer;
}
When generating a Worker, produce the following files:
src/index.ts -- the main Worker entry point using ES modules format.src/types.ts -- shared types and interfaces (Env, request/response shapes).src/middleware.ts -- reusable middleware (auth, validation, CORS, rate limiting, error handling).src/routes.ts or route-specific files -- business logic, cleanly separated.wrangler.jsonc -- configuration with current compatibility_date, nodejs_compat, observability enabled, and bindings declared.tsconfig.json -- strict TypeScript configuration..dev.vars.example -- template for local secrets (never real values).For simpler Workers (single-purpose, few routes), consolidate into a single src/index.ts but maintain the same security patterns.
Before finalising generated code, verify every item on this list. Each maps to one or more OWASP/CWE items (see references/security-checklist.md for the full mapping):
env, never hardcodedMath.random() is never used for security purposescompatibility_date is set to a recent date// src/index.ts
import { z } from "zod";
interface Env {
DB: D1Database;
API_SECRET: string;
ALLOWED_ORIGINS: string;
}
class HttpError extends Error {
constructor(
public status: number,
message: string
) {
super(message);
}
}
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
try {
// CORS preflight
if (request.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: {
...corsHeaders(request, env),
...securityHeaders(),
},
});
}
const url = new URL(request.url);
const response = await routeRequest(url, request, env, ctx);
// Apply security headers to every response
const headers = new Headers(response.headers);
for (const [k, v] of Object.entries(securityHeaders())) {
headers.set(k, v);
}
for (const [k, v] of Object.entries(corsHeaders(request, env))) {
headers.set(k, v);
}
return new Response(response.body, {
status: response.status,
headers,
});
} catch (err) {
if (err instanceof HttpError) {
return errorResponse(err.status, err.message);
}
secureLog("error", "Unhandled exception", {
path: new URL(request.url).pathname,
method: request.method,
});
return errorResponse(500, "Internal server error");
}
},
};
// ... (implement corsHeaders, securityHeaders, errorResponse,
// secureLog, routeRequest as shown in the patterns above)
When the user's requirements involve any of these patterns, apply the corresponding security measures:
| User Requirement | Security Consideration | OWASP/CWE Reference | |---|---|---| | File upload (R2) | Validate content type, enforce size limits, scan filenames for path traversal | A01, CWE-22 | | User authentication | Use proven JWT libraries, timing-safe comparison, rate limit login attempts | A07, CWE-287 | | Database queries (D1) | Parameterised queries only, limit result sets, validate column names if dynamic | A05, CWE-89 | | Webhook receiver | Verify signatures (HMAC), validate payload schema, idempotency keys | A04, A07, CWE-345 | | HTML generation | Context-aware output encoding, Content-Security-Policy headers | A05, CWE-79 | | External API calls | Allowlist target URLs (prevent SSRF), timeout, validate responses | A01, CWE-918 | | Caching (KV) | Never cache sensitive data without encryption, set appropriate TTLs | A04, CWE-524 | | WebSockets (DO) | Authenticate on connection, validate every message, rate limit per connection | A01, A07, CWE-20 | | Scheduled tasks (Cron) | Validate cron handler is not externally triggerable, log execution | A01, A09 |
These anti-patterns appear frequently and each one opens a real vulnerability:
Content-Type headers without verifying the actual body format* for Access-Control-Allow-Origin when credentials are involvedeval(), new Function(), or setTimeout with string argumentspassThroughOnException() without understanding it sends raw requests to origin on failuredevelopment
ALWAYS use this skill whenever generating, writing, reviewing, editing, or modifying Go (.go) code in any context. This skill ensures all generated Go code avoids the CWE Top 25 2025 weaknesses that apply to Go, and that every piece of Go code includes appropriate input validation, thorough error handling, and safe error messages that never leak passwords, tokens, API keys, or other secrets. The Go `unsafe` package is absolutely prohibited and must never appear in generated code. Trigger on ANY Go code generation -- there are no exceptions. Even trivial examples and one-off snippets must follow these rules. If the user asks for Go code, read this skill first.
development
Parallel OWASP Top 10:2025 security review of a web application codebase using 10 specialist agents. Trigger whenever the user asks for a security review, security audit, OWASP review, vulnerability assessment, code security scan, or threat analysis of a web app codebase. Also trigger on mentions of "OWASP Top 10", "security vulnerabilities", "code audit", "AppSec", or requests to check code for injection, XSS, access control, auth, or crypto issues. Trigger for casual requests like "is my code secure?", "check for vulnerabilities", or "any security issues?". Launches 10 parallel agents (one per OWASP category) producing a report with context-sensitive remediations. Secrets found are flagged but always shown as REDACTED.
development
Scan a codebase to find every instance of missing or inadequate input validation for data from external or untrusted sources, then propose context-appropriate fixes using whitelisting, regex, type coercion, size/range checks, encoding, etc. Use whenever the user asks to audit, review, or harden input validation in any codebase regardless of language. Trigger on: "check my inputs", "find injection risks", "validate user input", "security audit inputs", "input sanitisation review", "taint analysis", "harden my API inputs", "check for missing validation", "is my app safe from injection?". Platform- and language-independent.
development
Scan a codebase for missing or inadequate security-aware error handling and propose context-appropriate fixes. Use when the user asks to audit, review, scan, or check error handling in code; mentions "error handling audit", "exception handling review", "security error handling"; uploads a codebase wanting a security review focused on error handling; or says things like "find missing try/catch", "check for unhandled exceptions", "detect empty catch blocks", "identify information leakage in error messages", or "make my error handling more secure".