vibe-guard
Version:
🛡️ Vibe-Guard Security Scanner - Catch security issues before they catch you!
98 lines • 4.45 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MissingAuthenticationRule = void 0;
const types_1 = require("../types");
class MissingAuthenticationRule extends types_1.BaseRule {
constructor() {
super(...arguments);
this.name = 'missing-authentication';
this.description = 'Detects potentially unprotected routes and endpoints';
this.severity = 'high';
this.routePatterns = [
// Express.js
{ pattern: /app\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*(?!.*auth|.*login|.*verify|.*middleware)/gi, framework: 'Express' },
{ pattern: /router\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*(?!.*auth|.*login|.*verify|.*middleware)/gi, framework: 'Express' },
// Next.js API routes
{ pattern: /export\s+(?:default\s+)?(?:async\s+)?function\s+handler\s*\([^)]*\)\s*\{(?![\s\S]*auth|[\s\S]*login|[\s\S]*verify)/gi, framework: 'Next.js' },
// Flask
{ pattern: /@app\.route\s*\(\s*['"`]([^'"`]+)['"`](?:[^)]*)\)\s*\n\s*def\s+\w+\s*\([^)]*\)\s*:(?![\s\S]*auth|[\s\S]*login|[\s\S]*verify)/gi, framework: 'Flask' },
// FastAPI
{ pattern: /@app\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]\s*\)\s*\n\s*(?:async\s+)?def\s+\w+\s*\([^)]*\)\s*:(?![\s\S]*auth|[\s\S]*login|[\s\S]*verify)/gi, framework: 'FastAPI' },
// Laravel
{ pattern: /Route::(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*(?!.*auth|.*login|.*verify|.*middleware)/gi, framework: 'Laravel' }
];
this.protectedPatterns = [
/auth/i,
/login/i,
/verify/i,
/middleware/i,
/guard/i,
/protect/i,
/secure/i,
/jwt/i,
/token/i,
/session/i,
/permission/i,
/role/i
];
this.publicEndpoints = [
/\/public/i,
/\/health/i,
/\/ping/i,
/\/status/i,
/\/docs/i,
/\/swagger/i,
/\/api-docs/i,
/\/favicon/i,
/\/robots\.txt/i,
/\/sitemap/i,
/\/login/i,
/\/register/i,
/\/signup/i,
/\/forgot-password/i,
/\/reset-password/i
];
}
check(fileContent) {
const issues = [];
for (const { pattern, framework } of this.routePatterns) {
const matches = this.findMatches(fileContent.content, pattern);
for (const { match, line, column, lineContent } of matches) {
const route = this.extractRoute(match);
// Skip if it's a known public endpoint
if (this.isPublicEndpoint(route)) {
continue;
}
// Skip if the surrounding code suggests authentication
if (this.hasAuthenticationContext(fileContent.content, line)) {
continue;
}
issues.push(this.createIssue(fileContent.path, line, column, lineContent, `Potentially unprotected ${framework} route: ${route}`, `Add authentication middleware or verify that this endpoint should be publicly accessible. Consider using authentication guards, middleware, or decorators.`));
}
}
return issues;
}
extractRoute(match) {
// Try to find the route path in the match
for (let i = 1; i < match.length; i++) {
const matchGroup = match[i];
if (matchGroup && matchGroup.startsWith('/')) {
return matchGroup;
}
}
return match[0] || '';
}
isPublicEndpoint(route) {
return this.publicEndpoints.some(pattern => pattern.test(route));
}
hasAuthenticationContext(content, lineNumber) {
const lines = content.split('\n');
const contextRange = 10; // Check 10 lines before and after
const startLine = Math.max(0, lineNumber - contextRange - 1);
const endLine = Math.min(lines.length, lineNumber + contextRange);
const contextLines = lines.slice(startLine, endLine).join('\n');
return this.protectedPatterns.some(pattern => pattern.test(contextLines));
}
}
exports.MissingAuthenticationRule = MissingAuthenticationRule;
//# sourceMappingURL=missing-authentication.js.map