install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/owasp-csrf-protection" ~/.claude/skills/intense-visions-harness-engineering-owasp-csrf-protection-997568 && rm -rf "$T"
manifest:
agents/skills/codex/owasp-csrf-protection/SKILL.mdsource content
OWASP CSRF Protection
Prevent cross-site request forgery by validating request origin and requiring unpredictable tokens for state-changing operations
When to Use
- Building any application that uses cookie-based session authentication
- Implementing state-changing API endpoints (POST, PUT, DELETE, PATCH)
- Setting session cookie attributes
- Adding CSRF middleware to an Express or NestJS application
- Auditing forms and AJAX requests for missing CSRF protection
Instructions
SameSite Cookies — First Line of Defense
Modern browsers support
SameSite=Strict or SameSite=Lax which blocks CSRF for most cases:
res.cookie('session_id', sessionId, { httpOnly: true, secure: true, sameSite: 'strict', // cross-site requests never send this cookie maxAge: 15 * 60 * 1000, });
— cookie never sent on cross-site requests (strongest, may break OAuth flows)Strict
— cookie sent on top-level GET navigations, not POST; good balanceLax
— must pair withNone
and add CSRF tokensSecure: true
Synchronizer Token Pattern
For forms and traditional server-rendered pages:
import csrf from 'csurf'; // Express — csurf middleware const csrfProtection = csrf({ cookie: { sameSite: 'strict', httpOnly: true } }); app.get('/form', csrfProtection, (req, res) => { // Pass token to the template res.render('form', { csrfToken: req.csrfToken() }); }); app.post('/submit', csrfProtection, (req, res) => { // csurf validates _csrf field automatically res.json({ ok: true }); });
<!-- Include hidden CSRF field in every form --> <form method="POST" action="/submit"> <input type="hidden" name="_csrf" value="<%= csrfToken %>" /> <!-- form fields --> </form>
Double-Submit Cookie Pattern (Stateless APIs)
For SPAs where the backend is stateless:
import crypto from 'crypto'; function csrfMiddleware(req: Request, res: Response, next: NextFunction) { if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next(); const cookieToken = req.cookies['csrf-token']; const headerToken = req.headers['x-csrf-token']; if (!cookieToken || !headerToken || cookieToken !== headerToken) { return res.status(403).json({ error: 'CSRF validation failed' }); } next(); } // On session creation, set the cookie function setCSRFCookie(res: Response) { const token = crypto.randomBytes(32).toString('hex'); res.cookie('csrf-token', token, { secure: true, sameSite: 'strict', // NOT httpOnly — frontend JS must be able to read it }); }
// SPA — send CSRF token on every mutating request const csrfToken = document.cookie .split('; ') .find((row) => row.startsWith('csrf-token=')) ?.split('=')[1]; fetch('/api/update', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken ?? '', }, body: JSON.stringify(data), });
Origin Header Validation
Simplest approach for API-only backends that don't serve HTML:
const ALLOWED_ORIGINS = new Set(['https://app.example.com', 'https://www.example.com']); function originValidator(req: Request, res: Response, next: NextFunction) { if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next(); const origin = req.headers.origin || req.headers.referer; if (!origin) return res.status(403).json({ error: 'Missing origin' }); const url = new URL(origin); const originBase = `${url.protocol}//${url.host}`; if (!ALLOWED_ORIGINS.has(originBase)) { return res.status(403).json({ error: 'Invalid origin' }); } next(); }
Details
CSRF is only possible when:
- The browser automatically sends credentials (cookies, Basic auth) with cross-origin requests
- The server cannot distinguish legitimate from forged requests
APIs using Bearer tokens in Authorization header are immune to CSRF — browsers do not automatically send Authorization headers cross-origin. Only protect cookie-authenticated routes.
Defense selection guide:
- Cookie session app →
+ CSRF tokenSameSite=Strict - SPA + cookie session →
+ double-submit cookieSameSite=Strict - REST API + Bearer JWT → no CSRF protection needed
- Server-rendered forms → synchronizer token pattern
NestJS — CsrfGuard:
import { CsrfGuard } from '@tekuconcept/nestjs-csrf'; @UseGuards(CsrfGuard) @Post('transfer') transfer(@Body() dto: TransferDto) { ... }
Source
https://owasp.org/www-project-top-ten/
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.