vibe-guard
Version:
██ Vibe-Guard Security Scanner - 28 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, contain
511 lines • 25.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SqlInjectionRule = void 0;
const types_1 = require("../types");
class SqlInjectionRule extends types_1.BaseRule {
constructor() {
super(...arguments);
this.name = 'sql-injection';
this.description = 'Detects potential SQL injection vulnerabilities with context-aware analysis';
this.severity = 'high';
this.sqlInjectionPatterns = [
// Critical - Database query with raw concatenation and request input
{
pattern: /\b(?:query|sql|execute|CommandText)\s*[:=]\s*['"`][^'"`]*['"`]\s*[+]\s*\b(?:req\.|request\.|input\.|form\.|queryParam\.|body\.|params\.)/gi,
type: 'Database query with raw concatenation and request input',
severity: 'critical',
description: 'Critical SQL injection risk: User input directly concatenated into database queries',
validation: (text) => this.validateTaintedInput(text)
},
{
pattern: /\b(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|EXEC)\s+[^'"`]*['"`]\s*[+]\s*\b(?:req\.|request\.|input\.|form\.|queryParam\.|body\.|params\.)/gi,
type: 'SQL statement with tainted input concatenation',
severity: 'critical',
description: 'Critical SQL injection risk: SQL statement with user input concatenation',
validation: (text) => this.validateSqlStatement(text)
},
// High - Template literals and string interpolation
{
pattern: /\b(?:query|sql|execute)\s*[:=]\s*`[^`]*\$\{[^}]+\}[^`]*`/gi,
type: 'Template literal SQL with variables',
severity: 'high',
description: 'High SQL injection risk: Template literals with user input in database queries',
validation: (text) => this.validateTemplateLiteral(text)
},
{
pattern: /\bf["`][^`"]*\$\{[^}]*\}[^`"]*["`]/gi,
type: 'Python f-string SQL injection',
severity: 'high',
description: 'High SQL injection risk: Python f-string with user input in SQL context',
validation: (text) => this.validateFStringInjection(text)
},
{
pattern: /\b(?:query|sql|execute)\s*[:=]\s*['"`][^'"`]*\{[^}]*\}[^'"`]*['"`]\.format\(/gi,
type: 'Python format string SQL injection',
severity: 'high',
description: 'High SQL injection risk: Python .format() with user input in SQL context',
validation: (text) => this.validateFormatStringInjection(text)
},
{
pattern: /\b(?:query|sql|execute)\s*[:=]\s*['"`][^'"`]*%s[^'"`]*['"`]\s*%\s*\b(?:req\.|request\.|input\.|form\.|queryParam\.|body\.|params\.)/gi,
type: 'Python %s substitution SQL injection',
severity: 'high',
description: 'High SQL injection risk: Python %s substitution with user input',
validation: (text) => this.validatePercentSubstitution(text)
},
// Medium - ORM concatenation and JDBC patterns
{
pattern: /\b\.where\s*\(\s*['"`][^'"`]*['"`]\s*[+]\s*\b(?:req\.|request\.|input\.|form\.|queryParam\.|body\.|params\.)/gi,
type: 'ORM where clause with concatenation',
severity: 'medium',
description: 'Medium SQL injection risk: ORM where clause with user input concatenation',
validation: (text) => this.validateORMQuery(text)
},
{
pattern: /\bStatement\s*\.\s*executeQuery\s*\(\s*['"`][^'"`]*['"`]\s*[+]\s*\b(?:req\.|request\.|input\.|form\.|queryParam\.|body\.|params\.)/gi,
type: 'Java JDBC string concatenation',
severity: 'medium',
description: 'Medium SQL injection risk: Java JDBC with string concatenation',
validation: (text) => this.validateJDBCConcatenation(text)
},
{
pattern: /\bCommandText\s*[:=]\s*['"`][^'"`]*['"`]\s*[+]\s*\b(?:req\.|request\.|input\.|form\.|queryParam\.|body\.|params\.)/gi,
type: 'C# ADO.NET CommandText concatenation',
severity: 'medium',
description: 'Medium SQL injection risk: C# ADO.NET CommandText with string concatenation',
validation: (text) => this.validateADOConcatenation(text)
},
// Low - Suspicious patterns in migrations/tests
{
pattern: /\b(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|EXEC)\s+[^'"`]*['"`]\s*[+]/gi,
type: 'SQL statement with concatenation',
severity: 'low',
description: 'Low SQL injection risk: SQL statement with concatenation (may be safe in migrations)',
validation: (text) => this.validateSqlConcatenation(text)
},
{
pattern: /\b(?:query|sql|execute)\s*[:=]\s*['"`][^'"`]*['"`]\s*[+]/gi,
type: 'Database query with concatenation',
severity: 'low',
description: 'Low SQL injection risk: Database query with concatenation (may be safe in migrations)',
validation: (text) => this.validateQueryConcatenation(text)
}
];
this.safePatterns = [
// Parameterized queries
/\?\s*,/g,
/\$\d+/g,
/:\w+/g,
/@\w+/g,
/prepare\s*\(/i,
/bind\s*\(/i,
/params\s*\[/i,
/placeholder\s*\(/i,
// ORM safe patterns
/\.findOne\s*\(\s*\{/gi,
/\.findAll\s*\(\s*\{/gi,
/\.create\s*\(\s*\{/gi,
/\.update\s*\(\s*\{/gi,
/\.destroy\s*\(\s*\{/gi,
// Query builders
/\.select\s*\(/gi,
/\.from\s*\(/gi,
/\.where\s*\(\s*\{/gi,
/\.andWhere\s*\(/gi,
/\.orWhere\s*\(/gi
];
}
check(fileContent) {
const issues = [];
const language = this.detectLanguage(fileContent.path);
const framework = this.detectFramework(fileContent.content, language);
for (const { pattern, type, severity, description, validation } of this.sqlInjectionPatterns) {
const matches = this.findMatches(fileContent.content, pattern);
for (const { match, line, column, lineContent } of matches) {
const matchedText = match[0];
const context = this.analyzeContext(fileContent, line, column, language, framework);
// Skip if in safe context (but not migrations - we want to flag those with lower severity)
if (this.isSafeContext(context) && !context.isInMigration) {
continue;
}
// Validate the pattern
if (!validation(matchedText)) {
continue;
}
// Determine final severity based on context
const finalSeverity = this.determineSeverity(severity, context);
issues.push(this.createIssue(fileContent.path, line, column, lineContent, `Potential SQL injection: ${type} - ${description}`, this.getRemediationMessage(type, context, finalSeverity), finalSeverity));
}
}
return issues;
}
analyzeContext(fileContent, line, column, language, framework) {
const lines = fileContent.lines;
const currentLine = lines[line - 1] || '';
const surroundingLines = lines.slice(Math.max(0, line - 3), line + 2);
return {
isInComment: this.isInComment(currentLine, language),
isInString: this.isInString(currentLine, column),
isInTestFile: this.isInTestFile(fileContent.path),
isInDocumentation: this.isInDocumentation(surroundingLines),
isInMigration: this.isInMigration(fileContent.path),
surroundingCode: surroundingLines.join('\n'),
language,
framework,
hasParameterizedQueries: this.hasParameterizedQueries(surroundingLines),
isORMUsage: this.isORMUsage(surroundingLines, framework)
};
}
determineSeverity(baseSeverity, context) {
// Downgrade severity in migration/test contexts instead of skipping
if (context.isInMigration || context.isInTestFile) {
switch (baseSeverity) {
case 'critical': return 'high';
case 'high': return 'medium';
case 'medium': return 'low';
case 'low': return 'low';
default: return baseSeverity;
}
}
return baseSeverity;
}
isSafeContext(context) {
// Safe if in comment
if (context.isInComment)
return true;
// Safe if in documentation
if (context.isInDocumentation)
return true;
// Safe if using parameterized queries
if (context.hasParameterizedQueries)
return true;
// Safe if using ORM properly
if (context.isORMUsage)
return true;
return false;
}
detectLanguage(filePath) {
const ext = filePath.split('.').pop()?.toLowerCase();
const languageMap = {
'js': 'javascript',
'jsx': 'javascript',
'ts': 'typescript',
'tsx': 'typescript',
'py': 'python',
'php': 'php',
'rb': 'ruby',
'java': 'java',
'cs': 'csharp'
};
return languageMap[ext || ''] || 'unknown';
}
detectFramework(content, language) {
if (language === 'javascript' || language === 'typescript') {
if (content.includes('sequelize') || content.includes('Sequelize'))
return 'sequelize';
if (content.includes('prisma') || content.includes('Prisma'))
return 'prisma';
if (content.includes('typeorm') || content.includes('TypeORM'))
return 'typeorm';
if (content.includes('mongoose') || content.includes('Mongoose'))
return 'mongoose';
if (content.includes('knex') || content.includes('Knex'))
return 'knex';
}
if (language === 'python') {
if (content.includes('sqlalchemy') || content.includes('SQLAlchemy'))
return 'sqlalchemy';
if (content.includes('django.db') || content.includes('models.Model'))
return 'django';
}
if (language === 'java') {
if (content.includes('hibernate') || content.includes('Hibernate'))
return 'hibernate';
if (content.includes('jpa') || content.includes('JPA'))
return 'jpa';
}
return undefined;
}
isInComment(line, language) {
const trimmed = line.trim();
if (language === 'javascript' || language === 'typescript') {
return trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*');
}
if (language === 'python') {
return trimmed.startsWith('#');
}
if (language === 'php') {
return trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('#');
}
return false;
}
isInString(line, column) {
const before = line.substring(0, column);
const quotes = (before.match(/['"`]/g) || []).length;
return quotes % 2 === 1;
}
isInTestFile(filePath) {
return filePath.includes('test') || filePath.includes('spec') || filePath.includes('mock');
}
isInDocumentation(lines) {
return lines.some(line => line.includes('@example') ||
line.includes('TODO') ||
line.includes('FIXME') ||
line.includes('NOTE:'));
}
isInMigration(filePath) {
return filePath.includes('migration') || filePath.includes('migrate') || filePath.includes('schema');
}
hasParameterizedQueries(lines) {
return lines.some(line => this.safePatterns.some(pattern => pattern.test(line)));
}
isORMUsage(lines, framework) {
if (!framework)
return false;
const ormPatterns = {
'sequelize': /\.findOne|\.findAll|\.create|\.update|\.destroy/gi,
'prisma': /\.findFirst|\.findMany|\.create|\.update|\.delete/gi,
'typeorm': /\.findOne|\.find|\.save|\.remove/gi,
'mongoose': /\.find|\.findOne|\.create|\.updateOne/gi,
'sqlalchemy': /\.query\.|\.filter|\.filter_by/gi,
'django': /\.objects\.|\.filter|\.get/gi
};
const pattern = ormPatterns[framework];
return pattern ? lines.some(line => pattern.test(line)) : false;
}
// Advanced validation methods for tainted input detection
validateTaintedInput(text) {
const taintedPatterns = [
/\breq\./i,
/\brequest\./i,
/\binput\./i,
/\bform\./i,
/\bqueryParam\./i,
/\bbody\./i,
/\bparams\./i
];
return taintedPatterns.some(pattern => pattern.test(text)) && text.includes('+');
}
validateSqlStatement(text) {
const sqlKeywords = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'ALTER', 'EXEC'];
const taintedPatterns = [
/\breq\./i,
/\brequest\./i,
/\binput\./i,
/\bform\./i,
/\bqueryParam\./i,
/\bbody\./i,
/\bparams\./i
];
return sqlKeywords.some(keyword => text.toUpperCase().includes(keyword)) &&
taintedPatterns.some(pattern => pattern.test(text)) &&
text.includes('+');
}
validateTemplateLiteral(text) {
const taintedPatterns = [
/\breq\./i,
/\brequest\./i,
/\binput\./i,
/\bform\./i,
/\bqueryParam\./i,
/\bbody\./i,
/\bparams\./i
];
return text.includes('${') && taintedPatterns.some(pattern => pattern.test(text));
}
validateFStringInjection(text) {
const taintedPatterns = [
/\breq\./i,
/\brequest\./i,
/\binput\./i,
/\bform\./i,
/\bqueryParam\./i,
/\bbody\./i,
/\bparams\./i
];
return text.includes('f"') || text.includes("f'") && taintedPatterns.some(pattern => pattern.test(text));
}
validateFormatStringInjection(text) {
const taintedPatterns = [
/\breq\./i,
/\brequest\./i,
/\binput\./i,
/\bform\./i,
/\bqueryParam\./i,
/\bbody\./i,
/\bparams\./i
];
return text.includes('.format(') && taintedPatterns.some(pattern => pattern.test(text));
}
validatePercentSubstitution(text) {
const taintedPatterns = [
/\breq\./i,
/\brequest\./i,
/\binput\./i,
/\bform\./i,
/\bqueryParam\./i,
/\bbody\./i,
/\bparams\./i
];
return text.includes('%s') && text.includes('%') && taintedPatterns.some(pattern => pattern.test(text));
}
validateORMQuery(text) {
const taintedPatterns = [
/\breq\./i,
/\brequest\./i,
/\binput\./i,
/\bform\./i,
/\bqueryParam\./i,
/\bbody\./i,
/\bparams\./i
];
return text.includes('+') && text.includes('.where') && taintedPatterns.some(pattern => pattern.test(text));
}
validateJDBCConcatenation(text) {
const taintedPatterns = [
/\breq\./i,
/\brequest\./i,
/\binput\./i,
/\bform\./i,
/\bqueryParam\./i,
/\bbody\./i,
/\bparams\./i
];
return text.includes('Statement') && text.includes('executeQuery') &&
taintedPatterns.some(pattern => pattern.test(text)) && text.includes('+');
}
validateADOConcatenation(text) {
const taintedPatterns = [
/\breq\./i,
/\brequest\./i,
/\binput\./i,
/\bform\./i,
/\bqueryParam\./i,
/\bbody\./i,
/\bparams\./i
];
return text.includes('CommandText') && taintedPatterns.some(pattern => pattern.test(text)) && text.includes('+');
}
validateSqlConcatenation(text) {
const sqlKeywords = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'ALTER', 'EXEC'];
return sqlKeywords.some(keyword => text.toUpperCase().includes(keyword)) && text.includes('+');
}
validateQueryConcatenation(text) {
return (text.includes('query') || text.includes('sql') || text.includes('execute')) && text.includes('+');
}
getRemediationMessage(type, context, severity) {
const messages = {
'Database query with raw concatenation and request input': {
'critical': 'CRITICAL: Never concatenate user input directly into database queries. Use parameterized queries or prepared statements. This is a severe security vulnerability.',
'high': 'HIGH: Use parameterized queries instead of string concatenation. Replace concatenation with placeholders.',
'medium': 'MEDIUM: Consider using parameterized queries for better security.',
'low': 'LOW: Review query construction for potential injection vulnerabilities.'
},
'SQL statement with tainted input concatenation': {
'critical': 'CRITICAL: SQL statement with user input concatenation is extremely dangerous. Use parameterized queries immediately.',
'high': 'HIGH: Use parameterized queries instead of concatenating user input into SQL statements.',
'medium': 'MEDIUM: Consider using parameterized queries for SQL statements.',
'low': 'LOW: Review SQL statement construction.'
},
'Template literal SQL with variables': {
'critical': 'CRITICAL: Template literals with user input in SQL are dangerous. Use parameterized queries.',
'high': 'HIGH: Avoid template literals with user input in SQL. Use parameterized queries.',
'medium': 'MEDIUM: Consider using parameterized queries instead of template literals.',
'low': 'LOW: Review template literal usage in SQL.'
},
'Python f-string SQL injection': {
'critical': 'CRITICAL: Never use f-strings with user input in SQL. Use parameterized queries.',
'high': 'HIGH: Avoid f-strings with user input in SQL. Use parameterized queries.',
'medium': 'MEDIUM: Consider using parameterized queries instead of f-strings.',
'low': 'LOW: Review f-string usage in SQL.'
},
'Python format string SQL injection': {
'critical': 'CRITICAL: Never use .format() with user input in SQL. Use parameterized queries.',
'high': 'HIGH: Avoid .format() with user input in SQL. Use parameterized queries.',
'medium': 'MEDIUM: Consider using parameterized queries instead of .format().',
'low': 'LOW: Review .format() usage in SQL.'
},
'Python %s substitution SQL injection': {
'critical': 'CRITICAL: Never use %s substitution with user input in SQL. Use parameterized queries.',
'high': 'HIGH: Avoid %s substitution with user input in SQL. Use parameterized queries.',
'medium': 'MEDIUM: Consider using parameterized queries instead of %s substitution.',
'low': 'LOW: Review %s substitution usage in SQL.'
},
'ORM where clause with concatenation': {
'critical': 'CRITICAL: ORM where clauses with user input concatenation are dangerous. Use parameterized ORM methods.',
'high': 'HIGH: Use parameterized ORM methods instead of concatenation in where clauses.',
'medium': 'MEDIUM: Consider using parameterized ORM methods.',
'low': 'LOW: Review ORM where clause construction.'
},
'Java JDBC string concatenation': {
'critical': 'CRITICAL: JDBC with string concatenation is dangerous. Use PreparedStatement with placeholders.',
'high': 'HIGH: Use PreparedStatement with placeholders instead of string concatenation.',
'medium': 'MEDIUM: Consider using PreparedStatement for JDBC queries.',
'low': 'LOW: Review JDBC query construction.'
},
'C# ADO.NET CommandText concatenation': {
'critical': 'CRITICAL: ADO.NET CommandText with concatenation is dangerous. Use parameterized queries.',
'high': 'HIGH: Use parameterized queries instead of CommandText concatenation.',
'medium': 'MEDIUM: Consider using parameterized queries with ADO.NET.',
'low': 'LOW: Review ADO.NET query construction.'
}
};
let suggestion = messages[type]?.[severity] || 'Use parameterized queries or prepared statements instead of string concatenation.';
if (context.framework) {
suggestion += this.getFrameworkSpecificAdvice(context.framework, type);
}
if (context.language) {
suggestion += this.getLanguageSpecificAdvice(context.language, type);
}
return suggestion;
}
getFrameworkSpecificAdvice(framework, type) {
const advice = {
'sequelize': {
'Database query with raw concatenation and request input': ' For Sequelize, use: Model.findOne({ where: { id: req.params.id } })',
'ORM where clause with concatenation': ' For Sequelize, use parameterized where clauses: { where: { column: value } }'
},
'prisma': {
'Database query with raw concatenation and request input': ' For Prisma, use: prisma.user.findFirst({ where: { id: req.params.id } })',
'ORM where clause with concatenation': ' For Prisma, use parameterized queries: { where: { column: value } }'
},
'typeorm': {
'Database query with raw concatenation and request input': ' For TypeORM, use: repository.findOne({ where: { id: req.params.id } })',
'ORM where clause with concatenation': ' For TypeORM, use parameterized queries: { where: { column: value } }'
},
'sqlalchemy': {
'Database query with raw concatenation and request input': ' For SQLAlchemy, use: session.query(User).filter(User.id == user_id)',
'Python f-string SQL injection': ' For SQLAlchemy, use parameterized queries: session.query(User).filter(User.id == user_id)'
},
'django': {
'Database query with raw concatenation and request input': ' For Django, use: User.objects.filter(id=user_id)',
'Python format string SQL injection': ' For Django, use ORM: User.objects.filter(id=user_id)'
}
};
return advice[framework]?.[type] || ` For ${framework}, use framework-specific parameterized query methods.`;
}
getLanguageSpecificAdvice(language, type) {
const advice = {
'python': {
'Python f-string SQL injection': ' In Python, use parameterized queries with libraries like psycopg2, sqlite3, or ORMs.',
'Python format string SQL injection': ' In Python, use parameterized queries with libraries like psycopg2, sqlite3, or ORMs.',
'Python %s substitution SQL injection': ' In Python, use parameterized queries with libraries like psycopg2, sqlite3, or ORMs.'
},
'javascript': {
'Template literal SQL with variables': ' In JavaScript, use parameterized queries with libraries like mysql2, pg, or ORMs.',
'Database query with raw concatenation and request input': ' In JavaScript, use parameterized queries with libraries like mysql2, pg, or ORMs.'
},
'java': {
'Java JDBC string concatenation': ' In Java, use PreparedStatement with placeholders (?) and setParameter methods.',
'Database query with raw concatenation and request input': ' In Java, use PreparedStatement with placeholders (?) and setParameter methods.'
},
'csharp': {
'C# ADO.NET CommandText concatenation': ' In C#, use SqlCommand with parameters (@param) and AddParameter methods.',
'Database query with raw concatenation and request input': ' In C#, use SqlCommand with parameters (@param) and AddParameter methods.'
}
};
return advice[language]?.[type] || ` In ${language}, use language-specific parameterized query methods.`;
}
}
exports.SqlInjectionRule = SqlInjectionRule;
//# sourceMappingURL=sql-injection.js.map