vibe-guard
Version:
🛡️ Vibe-Guard Security Scanner - Catch security issues before they catch you!
158 lines • 6.93 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MissingSecurityHeadersRule = void 0;
const types_1 = require("../types");
class MissingSecurityHeadersRule extends types_1.BaseRule {
constructor() {
super(...arguments);
this.name = 'missing-security-headers';
this.description = 'Detects missing HTTP security headers';
this.severity = 'medium';
this.securityHeaders = [
'Content-Security-Policy',
'X-Frame-Options',
'X-Content-Type-Options',
'X-XSS-Protection',
'Strict-Transport-Security',
'Referrer-Policy',
'Permissions-Policy',
'X-Permitted-Cross-Domain-Policies'
];
this.serverPatterns = [
// Express.js patterns
{ pattern: /app\.(?:get|post|put|delete|patch|use)\s*\(/gi, type: 'Express route handler' },
{ pattern: /router\.(?:get|post|put|delete|patch|use)\s*\(/gi, type: 'Express router' },
{ pattern: /app\.listen\s*\(/gi, type: 'Express server' },
// Next.js API routes
{ pattern: /export\s+(?:default\s+)?(?:async\s+)?function\s+handler/gi, type: 'Next.js API handler' },
{ pattern: /export\s+(?:const|let|var)\s+\w+\s*=\s*(?:async\s+)?\([^)]*req[^)]*res[^)]*\)/gi, type: 'Next.js API function' },
// Node.js HTTP server
{ pattern: /createServer\s*\(\s*(?:async\s+)?\([^)]*req[^)]*res[^)]*\)/gi, type: 'Node.js HTTP server' },
{ pattern: /http\.createServer/gi, type: 'HTTP server creation' },
// Framework response patterns
{ pattern: /res\.(?:send|json|render|redirect)/gi, type: 'Response method' },
{ pattern: /response\.(?:send|json|render|redirect)/gi, type: 'Response method' },
// Flask patterns
{ pattern: /@app\.route/gi, type: 'Flask route' },
{ pattern: /return\s+(?:render_template|jsonify|redirect)/gi, type: 'Flask response' },
// Django patterns
{ pattern: /def\s+\w+\s*\([^)]*request[^)]*\)/gi, type: 'Django view function' },
{ pattern: /HttpResponse\s*\(/gi, type: 'Django HTTP response' },
// PHP patterns
{ pattern: /header\s*\(\s*['"`][^'"`]*['"`]/gi, type: 'PHP header function' }
];
}
check(fileContent) {
const issues = [];
// Only check server/web application files
if (!this.isWebApplicationFile(fileContent.path)) {
return issues;
}
// Check if file contains server/route patterns
const hasServerCode = this.hasServerCode(fileContent.content);
if (!hasServerCode) {
return issues;
}
// Check for missing security headers
this.checkMissingHeaders(fileContent, issues);
return issues;
}
isWebApplicationFile(filePath) {
const webFiles = [
/\.js$/i,
/\.ts$/i,
/\.jsx$/i,
/\.tsx$/i,
/\.py$/i,
/\.php$/i,
/\.rb$/i,
/\.go$/i,
/\.java$/i,
/\.cs$/i
];
// Skip test files
const testPatterns = [
/test/i,
/spec/i,
/\.test\./i,
/\.spec\./i,
/__tests__/i
];
if (testPatterns.some(pattern => pattern.test(filePath))) {
return false;
}
return webFiles.some(pattern => pattern.test(filePath));
}
hasServerCode(content) {
return this.serverPatterns.some(({ pattern }) => pattern.test(content));
}
checkMissingHeaders(fileContent, issues) {
const content = fileContent.content;
const missingHeaders = [];
for (const header of this.securityHeaders) {
if (!this.hasSecurityHeader(content, header)) {
missingHeaders.push(header);
}
}
if (missingHeaders.length > 0) {
// Find a good location to report the issue (first route handler or server setup)
const location = this.findReportLocation(fileContent);
if (location) {
issues.push(this.createIssue(fileContent.path, location.line, location.column, location.lineContent, `Missing security headers: ${missingHeaders.join(', ')}`, `Add security headers to protect against common attacks. Consider using helmet.js for Express or implementing headers manually: ${this.getHeaderRecommendations(missingHeaders)}`));
}
}
}
hasSecurityHeader(content, header) {
const headerPatterns = [
// Express.js patterns
new RegExp(`res\\.(?:set|header)\\s*\\(\\s*['"\`]${header}['"\`]`, 'gi'),
new RegExp(`res\\.setHeader\\s*\\(\\s*['"\`]${header}['"\`]`, 'gi'),
// Helmet.js patterns
/helmet\s*\(\s*\)/gi,
/helmet\./gi,
// Manual header setting
new RegExp(`['"\`]${header}['"\`]\\s*:\\s*['"\`]`, 'gi'),
// PHP patterns
new RegExp(`header\\s*\\(\\s*['"\`]${header}:`, 'gi'),
// Python Flask patterns
new RegExp(`response\\.headers\\[['"\`]${header}['"\`]\\]`, 'gi'),
// Django patterns
new RegExp(`response\\[['"\`]${header}['"\`]\\]`, 'gi')
];
return headerPatterns.some(pattern => pattern.test(content));
}
findReportLocation(fileContent) {
for (const { pattern } of this.serverPatterns) {
const matches = this.findMatches(fileContent.content, pattern);
if (matches.length > 0) {
const firstMatch = matches[0];
if (firstMatch) {
return {
line: firstMatch.line,
column: firstMatch.column,
lineContent: firstMatch.lineContent
};
}
}
}
return null;
}
getHeaderRecommendations(missingHeaders) {
const recommendations = [];
if (missingHeaders.includes('Content-Security-Policy')) {
recommendations.push("CSP: \"default-src 'self'\"");
}
if (missingHeaders.includes('X-Frame-Options')) {
recommendations.push("X-Frame-Options: 'DENY'");
}
if (missingHeaders.includes('X-Content-Type-Options')) {
recommendations.push("X-Content-Type-Options: 'nosniff'");
}
if (missingHeaders.includes('Strict-Transport-Security')) {
recommendations.push("HSTS: 'max-age=31536000; includeSubDomains'");
}
return recommendations.slice(0, 2).join(', ') + (recommendations.length > 2 ? '...' : '');
}
}
exports.MissingSecurityHeadersRule = MissingSecurityHeadersRule;
//# sourceMappingURL=missing-security-headers.js.map