@sun-asterisk/sunlint
Version:
☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards
151 lines (130 loc) • 5.71 kB
JavaScript
/**
* Hybrid analyzer for S015 - Insecure TLS Certificate Detection
* Uses AST analysis with regex fallback for comprehensive coverage
*/
const S015ASTAnalyzer = require('./ast-analyzer');
class S015Analyzer {
constructor() {
this.ruleId = 'S015';
this.ruleName = 'Insecure TLS Certificate';
this.description = 'Prevent usage of insecure TLS certificate configurations';
this.astAnalyzer = new S015ASTAnalyzer();
}
async analyze(files, language, options = {}) {
const violations = [];
if (options.verbose) {
console.log(`🔍 Running S015 analysis on ${files.length} files...`);
}
// Use AST analysis as primary method
const astViolations = await this.astAnalyzer.analyze(files, language, options);
violations.push(...astViolations);
// Add regex-based patterns for edge cases AST might miss
for (const filePath of files) {
try {
const content = require('fs').readFileSync(filePath, 'utf8');
const regexViolations = this.analyzeWithRegexPatterns(content, filePath, options);
// Filter out duplicates (same line, same type)
const filteredRegexViolations = regexViolations.filter(regexViolation =>
!astViolations.some(astViolation =>
astViolation.line === regexViolation.line &&
astViolation.filePath === regexViolation.filePath
)
);
violations.push(...filteredRegexViolations);
} catch (error) {
if (options.verbose) {
console.warn(`⚠️ S015 regex analysis failed for ${filePath}: ${error.message}`);
}
}
}
if (options.verbose && violations.length > 0) {
console.log(`📊 S015 found ${violations.length} violations`);
}
return violations;
}
analyzeWithRegexPatterns(content, filePath, options = {}) {
const violations = [];
const lines = content.split('\n');
lines.forEach((line, index) => {
const lineNumber = index + 1;
// Pattern 1: Direct rejectUnauthorized: false
if (/rejectUnauthorized\s*:\s*false/i.test(line)) {
violations.push({
ruleId: this.ruleId,
severity: 'error',
message: 'Untrusted/self-signed/expired certificate accepted. Only use trusted certificates in production.',
line: lineNumber,
column: line.search(/rejectUnauthorized/i) + 1,
filePath: filePath,
type: 'reject_unauthorized_false'
});
}
// Pattern 2: process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
if (/NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*['"]0['"]/.test(line)) {
violations.push({
ruleId: this.ruleId,
severity: 'error',
message: 'TLS certificate rejection disabled globally. This affects all HTTPS requests.',
line: lineNumber,
column: line.search(/NODE_TLS_REJECT_UNAUTHORIZED/) + 1,
filePath: filePath,
type: 'global_tls_disable'
});
}
// Pattern 3: Insecure TLS versions in configuration
const tlsVersionPattern = /secureProtocol\s*:\s*['"](?:SSLv2|SSLv3|TLSv1_method|TLSv1)['"]|minVersion\s*:\s*['"](?:TLSv1|TLSv1\.0|TLSv1\.1|SSLv3)['"]|maxVersion\s*:\s*['"](?:TLSv1|TLSv1\.0|TLSv1\.1)['"]|version\s*:\s*['"](?:TLSv1|TLSv1\.0|TLSv1\.1|SSLv3)['"]/i;
if (tlsVersionPattern.test(line)) {
violations.push({
ruleId: this.ruleId,
severity: 'error',
message: 'Insecure TLS/SSL version detected. Use TLS 1.2 or TLS 1.3 only.',
line: lineNumber,
column: line.search(tlsVersionPattern) + 1,
filePath: filePath,
type: 'insecure_tls_version'
});
}
// Pattern 4: Weak cipher suites
const weakCipherPattern = /ciphers\s*:\s*['"][^'"]*(?:NULL|RC4|DES|MD5|EXPORT)[^'"]*['"]|cipher\s*:\s*['"][^'"]*(?:NULL|RC4|DES|MD5|EXPORT)[^'"]*['"]/i;
if (weakCipherPattern.test(line)) {
violations.push({
ruleId: this.ruleId,
severity: 'warning',
message: 'Weak cipher detected in TLS configuration. Use strong ciphers only.',
line: lineNumber,
column: line.search(weakCipherPattern) + 1,
filePath: filePath,
type: 'weak_cipher'
});
}
// Pattern 5: Disabled certificate verification in various contexts
const certVerificationPattern = /checkServerIdentity\s*:\s*(?:false|null)|verify\s*:\s*false|strictSSL\s*:\s*false|secureOptions\s*:\s*0/i;
if (certVerificationPattern.test(line)) {
violations.push({
ruleId: this.ruleId,
severity: 'warning',
message: 'Certificate verification disabled. This may allow man-in-the-middle attacks.',
line: lineNumber,
column: line.search(certVerificationPattern) + 1,
filePath: filePath,
type: 'cert_verification_disabled'
});
}
// Pattern 6: Axios/HTTP client insecure configuration
const httpClientInsecurePattern = /(?:axios|request|fetch|http)\.(?:create|defaults|get|post|put|delete)\s*\([^)]*rejectUnauthorized\s*:\s*false|(?:axios|request)\.defaults\.httpsAgent.*rejectUnauthorized\s*:\s*false/i;
if (httpClientInsecurePattern.test(line)) {
violations.push({
ruleId: this.ruleId,
severity: 'error',
message: 'HTTP client configured to ignore certificate errors. This is insecure.',
line: lineNumber,
column: line.search(httpClientInsecurePattern) + 1,
filePath: filePath,
type: 'http_client_insecure'
});
}
});
return violations;
}
}
module.exports = S015Analyzer;