From harness-claude
Prevents CSRF attacks in web apps using SameSite cookies, csurf synchronizer tokens, double-submit cookies, and origin validation for Express/NestJS with cookie-based auth and state-changing endpoints.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Prevent cross-site request forgery by validating request origin and requiring unpredictable tokens for state-changing operations
Implements CSRF protection using synchronizer tokens, double-submit cookies, and SameSite attributes. Secures web forms, state-changing endpoints, and authentication layers.
Implements CSRF protection using synchronizer tokens, double-submit cookies, SameSite attributes, and origin validation for forms and state-changing operations in Node.js/Express and Flask.
Detects CSRF vulnerabilities in HTML forms, session cookies, and middleware for Django, Flask, Express, Spring Boot, Go, and Rust web apps. Provides framework-specific fixes and verification steps.
Share bugs, ideas, or general feedback.
Prevent cross-site request forgery by validating request origin and requiring unpredictable tokens for state-changing operations
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,
});
Strict — cookie never sent on cross-site requests (strongest, may break OAuth flows)Lax — cookie sent on top-level GET navigations, not POST; good balanceNone — must pair with Secure: true and add CSRF tokensFor 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>
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),
});
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();
}
CSRF is only possible when:
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:
SameSite=Strict + CSRF tokenSameSite=Strict + double-submit cookieNestJS — CsrfGuard:
import { CsrfGuard } from '@tekuconcept/nestjs-csrf';
@UseGuards(CsrfGuard)
@Post('transfer')
transfer(@Body() dto: TransferDto) { ... }
https://owasp.org/www-project-top-ten/