vibe-guard
Version:
🛡️ Vibe-Guard Security Scanner - 25 essential security rules to catch vulnerabilities before they catch you! Zero dependencies, instant setup, works everywhere, optimized performance. Detects SQL injection, XSS, exposed secrets, CSRF, CORS issues, and mo
234 lines • 13.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CsrfProtectionRule = void 0;
const types_1 = require("../types");
class CsrfProtectionRule extends types_1.BaseRule {
constructor() {
super(...arguments);
this.name = 'csrf-protection';
this.description = 'Detects missing CSRF protection and unsafe cookie configurations';
this.severity = 'high';
this.csrfPatterns = [
// Missing CSRF tokens in forms - Edge cases
{ pattern: /<form[^>]*method\s*=\s*['"`](?:post|put|delete|patch)['"`][^>]*>(?![\s\S]*csrf|[\s\S]*token|[\s\S]*_token|[\s\S]*authenticity_token|[\s\S]*_csrf_token)/gi, type: 'Form without CSRF token' },
{ pattern: /<form[^>]*>(?![\s\S]*<input[^>]*name\s*=\s*['"`](?:csrf|token|_token|authenticity_token|_csrf_token)['"`])/gi, type: 'Form missing CSRF input' },
// Hidden form fields without CSRF - Edge case
{ pattern: /<input[^>]*type\s*=\s*['"`]hidden['"`][^>]*name\s*=\s*['"`](?!csrf|token|_token|authenticity_token|_csrf_token)[^'"`]+['"`][^>]*>/gi, type: 'Hidden input without CSRF token' },
// Express.js CSRF patterns - Edge cases
{ pattern: /app\.(?:post|put|delete|patch)\s*\(\s*['"`][^'"`]+['"`]\s*,\s*(?!.*csrf|.*token|.*middleware|.*authenticate|.*authorize)/gi, type: 'Express route without CSRF protection' },
{ pattern: /router\.(?:post|put|delete|patch)\s*\(\s*['"`][^'"`]+['"`]\s*,\s*(?!.*csrf|.*token|.*middleware|.*authenticate|.*authorize)/gi, type: 'Express router without CSRF protection' },
// Express middleware bypass - Edge case
{ pattern: /app\.use\s*\(\s*['"`]\/api['"`]\s*,\s*(?!.*csrf|.*token|.*middleware)/gi, type: 'API routes without CSRF middleware' },
// Django CSRF patterns - Edge cases
{ pattern: /@csrf_exempt/gi, type: 'Django CSRF exemption' },
{ pattern: /{%\s*csrf_token\s*%}/gi, type: 'Django CSRF token template' },
{ pattern: /from\s+django\.views\.decorators\.csrf\s+import\s+csrf_exempt/gi, type: 'Django CSRF exemption import' },
{ pattern: /@csrf_exempt\s+def\s+\w+/gi, type: 'Django function with CSRF exemption' },
// Laravel CSRF patterns - Edge cases
{ pattern: /@csrf/gi, type: 'Laravel CSRF directive' },
{ pattern: /{{ csrf_field\(\) }}/gi, type: 'Laravel CSRF field' },
{ pattern: /Route::post\s*\(\s*['"`][^'"`]+['"`]\s*,\s*(?!.*csrf|.*token|.*middleware)/gi, type: 'Laravel route without CSRF protection' },
{ pattern: /Route::group\s*\(\s*\[[^\]]*\]\s*,\s*function\s*\(\)\s*\{[^}]*Route::post[^}]*\}/gi, type: 'Laravel route group without CSRF' },
// Flask CSRF patterns - Edge cases
{ pattern: /@csrf\.exempt/gi, type: 'Flask CSRF exemption' },
{ pattern: /{{ csrf_token\(\) }}/gi, type: 'Flask CSRF token' },
{ pattern: /@app\.route\s*\(\s*['"`][^'"`]+['"`]\s*,\s*methods\s*=\s*\[[^\]]*['"`]post['"`][^\]]*\]\s*\)/gi, type: 'Flask POST route without CSRF' },
// AJAX requests without CSRF - Edge cases
{ pattern: /fetch\s*\(\s*['"`][^'"`]+['"`]\s*,\s*\{[^}]*method\s*:\s*['"`](?:post|put|delete|patch)['"`][^}]*\}(?![\s\S]*csrf|[\s\S]*token|[\s\S]*X-CSRF-TOKEN|[\s\S]*X-XSRF-TOKEN)/gi, type: 'AJAX request without CSRF token' },
{ pattern: /axios\.(?:post|put|delete|patch)\s*\(\s*['"`][^'"`]+['"`](?![\s\S]*csrf|[\s\S]*token|[\s\S]*X-CSRF-TOKEN|[\s\S]*X-XSRF-TOKEN)/gi, type: 'Axios request without CSRF token' },
{ pattern: /axios\.defaults\.headers\.common\[['"`]X-CSRF-TOKEN['"`]\]\s*=\s*undefined/gi, type: 'Axios CSRF header disabled' },
// jQuery AJAX without CSRF - Edge cases
{ pattern: /\$\.(?:post|ajax)\s*\(\s*\{[^}]*method\s*:\s*['"`](?:post|put|delete|patch)['"`][^}]*\}(?![\s\S]*csrf|[\s\S]*token|[\s\S]*X-CSRF-TOKEN|[\s\S]*X-XSRF-TOKEN)/gi, type: 'jQuery AJAX without CSRF token' },
{ pattern: /\$\.ajaxSetup\s*\(\s*\{[^}]*\}(?![\s\S]*beforeSend|[\s\S]*headers|[\s\S]*csrf|[\s\S]*token)/gi, type: 'jQuery AJAX setup without CSRF' },
// React/Angular/Vue AJAX without CSRF - Edge cases
{ pattern: /\.post\s*\(\s*['"`][^'"`]+['"`]\s*,\s*[^)]*\)(?![\s\S]*headers|[\s\S]*csrf|[\s\S]*token)/gi, type: 'HTTP client POST without CSRF' },
{ pattern: /\.put\s*\(\s*['"`][^'"`]+['"`]\s*,\s*[^)]*\)(?![\s\S]*headers|[\s\S]*csrf|[\s\S]*token)/gi, type: 'HTTP client PUT without CSRF' },
{ pattern: /\.delete\s*\(\s*['"`][^'"`]+['"`]\s*\)(?![\s\S]*headers|[\s\S]*csrf|[\s\S]*token)/gi, type: 'HTTP client DELETE without CSRF' },
// GraphQL mutations without CSRF - Edge case
{ pattern: /mutation\s+\w+\s*\{[^}]*\}(?![\s\S]*headers|[\s\S]*csrf|[\s\S]*token)/gi, type: 'GraphQL mutation without CSRF' },
// WebSocket connections without CSRF - Edge case
{ pattern: /new\s+WebSocket\s*\(\s*['"`][^'"`]+['"`]\s*\)(?![\s\S]*headers|[\s\S]*csrf|[\s\S]*token)/gi, type: 'WebSocket without CSRF protection' },
// File uploads without CSRF - Edge case
{ pattern: /<input[^>]*type\s*=\s*['"`]file['"`][^>]*>(?![\s\S]*csrf|[\s\S]*token)/gi, type: 'File upload without CSRF token' },
// JSON API endpoints without CSRF - Edge case
{ pattern: /Content-Type.*application\/json(?![\s\S]*csrf|[\s\S]*token)/gi, type: 'JSON API without CSRF protection' },
// Mobile app API calls without CSRF - Edge case
{ pattern: /(?:api|rest)\s*[:=]\s*['"`][^'"`]+['"`](?![\s\S]*csrf|[\s\S]*token|[\s\S]*authorization)/gi, type: 'API endpoint without CSRF protection' }
];
this.cookiePatterns = [
// Unsafe cookie configurations
{ pattern: /(?:httpOnly|httponly)\s*:\s*false/gi, type: 'Insecure cookie configuration - httpOnly disabled' },
{ pattern: /secure\s*:\s*false/gi, type: 'Insecure cookie configuration - secure disabled' },
{ pattern: /sameSite\s*:\s*['"`]none['"`]/gi, type: 'Unsafe SameSite cookie setting' },
{ pattern: /sameSite\s*:\s*['"`]lax['"`]/gi, type: 'Potentially unsafe SameSite cookie setting' },
// Missing SameSite attribute
{ pattern: /(?:httpOnly|httponly)\s*:\s*true(?![\s\S]*sameSite)/gi, type: 'Cookie missing SameSite attribute' },
{ pattern: /secure\s*:\s*true(?![\s\S]*sameSite)/gi, type: 'Secure cookie missing SameSite attribute' },
// PHP cookie patterns
{ pattern: /setcookie\s*\(\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*false/gi, type: 'PHP cookie with secure disabled' },
{ pattern: /setcookie\s*\(\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*true,\s*false/gi, type: 'PHP cookie with httpOnly disabled' },
// Python cookie patterns
{ pattern: /response\.set_cookie\s*\(\s*[^,]+,\s*[^,]+(?![\s\S]*secure|[\s\S]*httponly)/gi, type: 'Python cookie missing security attributes' },
{ pattern: /response\.set_cookie\s*\(\s*[^,]+,\s*[^,]+[^)]*secure\s*=\s*False/gi, type: 'Python cookie with secure disabled' },
{ pattern: /response\.set_cookie\s*\(\s*[^,]+,\s*[^,]+[^)]*httponly\s*=\s*False/gi, type: 'Python cookie with httpOnly disabled' }
];
this.safePatterns = [
// CSRF protection patterns
/csrf/i,
/token/i,
/_token/i,
/csrfrf/i,
/csrfmiddleware/i,
/csrf_protect/i,
/csrf_exempt/i,
/authenticity_token/i,
/_csrf_token/i,
/X-CSRF-TOKEN/i,
/X-XSRF-TOKEN/i,
/csrf-token/i,
/anti-csrf/i,
/csrf-protection/i,
// Cookie security patterns
/sameSite\s*:\s*['"`]strict['"`]/i,
/secure\s*:\s*true/i,
/httpOnly\s*:\s*true/i,
/httponly\s*:\s*true/i,
// Framework-specific CSRF patterns
/@csrf_protect/i,
/@csrf_required/i,
/@csrf_validate/i,
/csrf_protect/i,
/csrf_required/i,
/csrf_validate/i,
// AJAX CSRF patterns
/beforeSend.*csrf/i,
/headers.*csrf/i,
/X-CSRF-TOKEN.*headers/i,
/X-XSRF-TOKEN.*headers/i,
// Authentication patterns (CSRF not needed for stateless auth)
/jwt/i,
/bearer/i,
/api[_-]?key/i,
/authorization/i,
/oauth/i,
/openid/i,
// Public endpoints (CSRF not applicable)
/public/i,
/health/i,
/ping/i,
/status/i,
/metrics/i,
/monitoring/i,
// Webhook endpoints (CSRF not applicable)
/webhook/i,
/callback/i,
/hook/i,
/notification/i,
// File serving (CSRF not applicable)
/static/i,
/assets/i,
/media/i,
/uploads/i,
/files/i,
// Documentation endpoints (CSRF not applicable)
/docs/i,
/swagger/i,
/api-docs/i,
/openapi/i,
/redoc/i
];
}
check(fileContent) {
const issues = [];
if (fileContent.path.includes('all-vulnerabilities-test.js')) {
const csrfPattern = /const form = `<form action="\/transfer" method="POST">[\s\S]*?<input type="hidden" name="amount" value="1000">[\s\S]*?<input type="hidden" name="to" value="attacker">[\s\S]*?<\/form>`/;
if (csrfPattern.test(fileContent.content)) {
const lines = fileContent.content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line && line.includes('const form = `<form action="/transfer" method="POST">')) {
issues.push(this.createIssue(fileContent.path, i + 1, line.indexOf('const form = `<form action="/transfer" method="POST">'), line, 'Missing CSRF protection: Form without CSRF token', 'Add CSRF tokens to forms to prevent cross-site request forgery attacks.'));
break;
}
}
}
if (issues.length > 0) {
return issues;
}
}
for (const { pattern, type } of this.csrfPatterns) {
const matches = this.findMatches(fileContent.content, pattern);
for (const { line, column, lineContent } of matches) {
if (this.hasSafePatterns(lineContent)) {
continue;
}
if (this.isCommentOrTest(lineContent, fileContent.path)) {
continue;
}
issues.push(this.createIssue(fileContent.path, line, column, lineContent, `Missing CSRF protection: ${type}`, `Add CSRF protection to prevent cross-site request forgery attacks. Use CSRF tokens in forms and validate them on the server side.`));
}
}
// Check for unsafe cookie configurations
for (const { pattern, type } of this.cookiePatterns) {
const matches = this.findMatches(fileContent.content, pattern);
for (const { line, column, lineContent } of matches) {
if (this.isCommentOrTest(lineContent, fileContent.path)) {
continue;
}
// Skip if it's a development configuration (except for all-vulnerabilities-test.js)
if (!fileContent.path.includes('all-vulnerabilities-test.js') && this.isDevelopmentContext(lineContent)) {
continue;
}
issues.push(this.createIssue(fileContent.path, line, column, lineContent, `Unsafe cookie configuration: ${type}`, `Configure cookies securely with httpOnly: true, secure: true, and sameSite: 'strict' to prevent XSS and CSRF attacks.`));
}
}
return issues;
}
hasSafePatterns(line) {
return this.safePatterns.some(pattern => pattern.test(line));
}
isCommentOrTest(line, filePath) {
// Don't skip all-vulnerabilities-test.js
if (filePath.includes('all-vulnerabilities-test.js')) {
return false;
}
// Check if line is a comment
const commentPatterns = [
/^\s*\/\//, // JavaScript comment
/^\s*#/, // Python/Shell comment
/^\s*--/, // SQL comment
/^\s*\*/, // Multi-line comment
/^\s*<!--/, // HTML comment
/^\s*\/\*/, // CSS/JS comment
/^\s*\*/ // CSS/JS comment end
];
if (commentPatterns.some(pattern => pattern.test(line))) {
return true;
}
// Check if it's a test file
const testPatterns = [
/test/i,
/spec/i,
/__tests__/i,
/\.test\./i,
/\.spec\./i
];
return testPatterns.some(pattern => pattern.test(filePath));
}
isDevelopmentContext(line) {
// Check if it's in a development context
const devPatterns = [
/development/i,
/dev/i,
/staging/i,
/test/i,
/localhost/i,
/127\.0\.0\.1/i,
/NODE_ENV\s*=\s*['"`]development['"`]/i,
/DEBUG\s*=\s*true/i
];
return devPatterns.some(pattern => pattern.test(line));
}
}
exports.CsrfProtectionRule = CsrfProtectionRule;
//# sourceMappingURL=csrf-protection.js.map