- Updated all component headers and documentation
- Changed navbar and footer branding
- Updated homepage hero badge
- Modified page title in index.html
- Simplified footer text to 'Built with ❤️'
- Consistent V2 capitalization across all references
313 lines
13 KiB
JavaScript
313 lines
13 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.SecurityAnalyzerService = void 0;
|
|
const axios_1 = __importDefault(require("axios"));
|
|
const url_1 = require("url");
|
|
const logger_1 = require("../lib/logger");
|
|
class SecurityAnalyzerService {
|
|
async analyzeSecurity(url, redirectChain) {
|
|
logger_1.logger.info(`Starting security analysis for: ${url}`);
|
|
const result = {
|
|
url,
|
|
flags: {
|
|
safeBrowsingStatus: 'unknown',
|
|
mixedContent: 'NONE',
|
|
httpsToHttp: false,
|
|
securityHeaders: { score: 0 },
|
|
weakCiphers: false,
|
|
openRedirects: false,
|
|
},
|
|
mixedContentAnalysis: {
|
|
status: 'none',
|
|
insecureResources: [],
|
|
httpsToHttpRedirect: false,
|
|
},
|
|
safeBrowsing: { status: 'unknown' },
|
|
vulnerabilities: [],
|
|
recommendations: [],
|
|
securityScore: 0,
|
|
};
|
|
try {
|
|
if (redirectChain && redirectChain.length > 0) {
|
|
this.analyzeRedirectChainSecurity(redirectChain, result);
|
|
}
|
|
const headersAnalysis = await this.analyzeSecurityHeaders(url);
|
|
result.flags.securityHeaders = headersAnalysis;
|
|
const mixedContentAnalysis = await this.analyzeMixedContent(url);
|
|
result.mixedContentAnalysis = mixedContentAnalysis;
|
|
result.flags.mixedContent = mixedContentAnalysis.status === 'none' ? 'NONE' :
|
|
mixedContentAnalysis.status === 'final_to_http' ? 'FINAL_TO_HTTP' : 'PRESENT';
|
|
result.flags.httpsToHttp = mixedContentAnalysis.httpsToHttpRedirect;
|
|
const safeBrowsingResult = await this.checkSafeBrowsing(url);
|
|
result.safeBrowsing = safeBrowsingResult;
|
|
result.flags.safeBrowsingStatus = safeBrowsingResult.status;
|
|
this.generateSecurityRecommendations(result);
|
|
logger_1.logger.info(`Security analysis completed for: ${url}`, {
|
|
score: result.securityScore,
|
|
safeBrowsing: result.safeBrowsing.status,
|
|
mixedContent: result.flags.mixedContent,
|
|
headersScore: result.flags.securityHeaders.score
|
|
});
|
|
}
|
|
catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : 'Unknown security analysis error';
|
|
result.vulnerabilities.push(`Security analysis failed: ${errorMessage}`);
|
|
logger_1.logger.error(`Security analysis failed for ${url}:`, error);
|
|
}
|
|
return result;
|
|
}
|
|
analyzeRedirectChainSecurity(redirectChain, result) {
|
|
for (let i = 0; i < redirectChain.length - 1; i++) {
|
|
const currentUrl = redirectChain[i];
|
|
const nextUrl = redirectChain[i + 1];
|
|
try {
|
|
const current = new url_1.URL(currentUrl);
|
|
const next = new url_1.URL(nextUrl);
|
|
if (current.protocol === 'https:' && next.protocol === 'http:') {
|
|
result.flags.httpsToHttp = true;
|
|
result.vulnerabilities.push(`Insecure redirect: HTTPS to HTTP (${currentUrl} → ${nextUrl})`);
|
|
}
|
|
if (current.hostname !== next.hostname) {
|
|
if (this.isOpenRedirectVulnerable(currentUrl, nextUrl)) {
|
|
result.flags.openRedirects = true;
|
|
result.vulnerabilities.push(`Potential open redirect vulnerability: ${currentUrl} → ${nextUrl}`);
|
|
}
|
|
}
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.warn(`Failed to parse URLs in redirect chain: ${currentUrl} → ${nextUrl}`);
|
|
}
|
|
}
|
|
}
|
|
isOpenRedirectVulnerable(fromUrl, toUrl) {
|
|
try {
|
|
const from = new url_1.URL(fromUrl);
|
|
const to = new url_1.URL(toUrl);
|
|
if (from.hostname !== to.hostname) {
|
|
const urlParams = new URLSearchParams(from.search);
|
|
for (const [key, value] of urlParams.entries()) {
|
|
if (key.toLowerCase().includes('url') ||
|
|
key.toLowerCase().includes('redirect') ||
|
|
key.toLowerCase().includes('return')) {
|
|
if (value.includes(to.hostname)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
catch {
|
|
return false;
|
|
}
|
|
}
|
|
async analyzeSecurityHeaders(url) {
|
|
try {
|
|
const response = await axios_1.default.head(url, {
|
|
timeout: 5000,
|
|
headers: {
|
|
'User-Agent': 'RedirectIntelligence-Bot/2.0 (Security Analysis)',
|
|
},
|
|
});
|
|
const headers = response.headers;
|
|
let score = 0;
|
|
const analysis = {
|
|
score: 0,
|
|
};
|
|
if (headers['strict-transport-security']) {
|
|
analysis.strictTransportSecurity = headers['strict-transport-security'];
|
|
score += 20;
|
|
}
|
|
if (headers['content-security-policy']) {
|
|
analysis.contentSecurityPolicy = headers['content-security-policy'];
|
|
score += 25;
|
|
}
|
|
if (headers['x-frame-options']) {
|
|
analysis.xFrameOptions = headers['x-frame-options'];
|
|
score += 15;
|
|
}
|
|
if (headers['x-content-type-options']) {
|
|
analysis.xContentTypeOptions = headers['x-content-type-options'];
|
|
score += 10;
|
|
}
|
|
if (headers['referrer-policy']) {
|
|
analysis.referrerPolicy = headers['referrer-policy'];
|
|
score += 10;
|
|
}
|
|
if (headers['permissions-policy'] || headers['feature-policy']) {
|
|
analysis.permissionsPolicy = headers['permissions-policy'] || headers['feature-policy'];
|
|
score += 10;
|
|
}
|
|
analysis.score = score;
|
|
return analysis;
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.warn(`Failed to analyze security headers for ${url}:`, error);
|
|
return { score: 0 };
|
|
}
|
|
}
|
|
async analyzeMixedContent(url) {
|
|
const result = {
|
|
status: 'none',
|
|
insecureResources: [],
|
|
httpsToHttpRedirect: false,
|
|
};
|
|
try {
|
|
const parsedUrl = new url_1.URL(url);
|
|
if (parsedUrl.protocol === 'http:') {
|
|
result.status = 'final_to_http';
|
|
result.httpsToHttpRedirect = true;
|
|
return result;
|
|
}
|
|
if (parsedUrl.protocol === 'https:') {
|
|
const mixedContentCheck = await this.checkPageMixedContent(url);
|
|
if (mixedContentCheck.length > 0) {
|
|
result.status = 'present';
|
|
result.insecureResources = mixedContentCheck;
|
|
}
|
|
}
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.warn(`Mixed content analysis failed for ${url}:`, error);
|
|
}
|
|
return result;
|
|
}
|
|
async checkPageMixedContent(url) {
|
|
try {
|
|
const response = await axios_1.default.get(url, {
|
|
timeout: 10000,
|
|
headers: {
|
|
'User-Agent': 'RedirectIntelligence-Bot/2.0 (Mixed Content Analysis)',
|
|
},
|
|
maxContentLength: 512 * 1024,
|
|
});
|
|
const html = response.data;
|
|
const insecureResources = [];
|
|
const httpResourceRegex = /(?:src|href|action)=['"]http:\/\/[^'"]+['"]|url\(http:\/\/[^)]+\)/gi;
|
|
let match;
|
|
while ((match = httpResourceRegex.exec(html)) !== null) {
|
|
const resource = match[0];
|
|
const urlMatch = resource.match(/http:\/\/[^'")\s]+/);
|
|
if (urlMatch) {
|
|
insecureResources.push(urlMatch[0]);
|
|
}
|
|
}
|
|
return [...new Set(insecureResources)];
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.warn(`Failed to check mixed content for ${url}:`, error);
|
|
return [];
|
|
}
|
|
}
|
|
async checkSafeBrowsing(url) {
|
|
try {
|
|
const parsedUrl = new url_1.URL(url);
|
|
const hostname = parsedUrl.hostname.toLowerCase();
|
|
const suspiciousPatterns = [
|
|
/phishing/i,
|
|
/malware/i,
|
|
/virus/i,
|
|
/hack/i,
|
|
/suspicious/i,
|
|
];
|
|
for (const pattern of suspiciousPatterns) {
|
|
if (pattern.test(hostname)) {
|
|
return {
|
|
status: 'phishing',
|
|
details: 'Suspicious hostname pattern detected',
|
|
};
|
|
}
|
|
}
|
|
const suspiciousTlds = ['.tk', '.ml', '.ga', '.cf'];
|
|
const tld = hostname.substring(hostname.lastIndexOf('.'));
|
|
if (suspiciousTlds.includes(tld)) {
|
|
return {
|
|
status: 'unwanted_software',
|
|
details: 'Suspicious top-level domain',
|
|
};
|
|
}
|
|
return { status: 'safe' };
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.warn(`Safe browsing check failed for ${url}:`, error);
|
|
return {
|
|
status: 'error',
|
|
details: 'Safe browsing check failed',
|
|
};
|
|
}
|
|
}
|
|
generateSecurityRecommendations(result) {
|
|
let score = 100;
|
|
if (result.safeBrowsing.status === 'malware' || result.safeBrowsing.status === 'phishing') {
|
|
result.vulnerabilities.push(`Site flagged as ${result.safeBrowsing.status}`);
|
|
result.recommendations.push('Immediately investigate and resolve security issues');
|
|
score -= 50;
|
|
}
|
|
else if (result.safeBrowsing.status === 'unwanted_software') {
|
|
result.vulnerabilities.push('Site may contain unwanted software');
|
|
result.recommendations.push('Review site content and remove unwanted software');
|
|
score -= 30;
|
|
}
|
|
if (result.flags.httpsToHttp) {
|
|
result.vulnerabilities.push('HTTPS to HTTP downgrade detected');
|
|
result.recommendations.push('Ensure all redirects maintain HTTPS encryption');
|
|
score -= 25;
|
|
}
|
|
if (result.mixedContentAnalysis.insecureResources.length > 0) {
|
|
result.vulnerabilities.push(`${result.mixedContentAnalysis.insecureResources.length} insecure resources found`);
|
|
result.recommendations.push('Update all HTTP resources to use HTTPS');
|
|
score -= 15;
|
|
}
|
|
const headersScore = result.flags.securityHeaders.score;
|
|
if (headersScore < 50) {
|
|
result.recommendations.push('Implement security headers (CSP, HSTS, X-Frame-Options)');
|
|
score -= 20;
|
|
}
|
|
else if (headersScore < 80) {
|
|
result.recommendations.push('Add additional security headers for better protection');
|
|
score -= 10;
|
|
}
|
|
if (result.flags.openRedirects) {
|
|
result.vulnerabilities.push('Potential open redirect vulnerability detected');
|
|
result.recommendations.push('Validate and restrict redirect destinations');
|
|
score -= 20;
|
|
}
|
|
score = Math.min(score, score + (headersScore * 0.2));
|
|
result.securityScore = Math.max(0, score);
|
|
if (result.securityScore >= 90) {
|
|
result.recommendations.push('Excellent security posture!');
|
|
}
|
|
else if (result.securityScore >= 70) {
|
|
result.recommendations.push('Good security with minor improvements possible');
|
|
}
|
|
else if (result.securityScore >= 50) {
|
|
result.recommendations.push('Security needs improvement for better protection');
|
|
}
|
|
else {
|
|
result.recommendations.push('Security requires immediate attention');
|
|
}
|
|
}
|
|
async quickSecurityCheck(url) {
|
|
try {
|
|
const analysis = await this.analyzeSecurity(url);
|
|
return {
|
|
httpsToHttp: analysis.flags.httpsToHttp,
|
|
safeBrowsingStatus: analysis.flags.safeBrowsingStatus,
|
|
hasSecurityHeaders: analysis.flags.securityHeaders.score > 0,
|
|
};
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.warn(`Quick security check failed for ${url}:`, error);
|
|
return {
|
|
httpsToHttp: false,
|
|
safeBrowsingStatus: 'unknown',
|
|
hasSecurityHeaders: false,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
exports.SecurityAnalyzerService = SecurityAnalyzerService;
|
|
//# sourceMappingURL=security-analyzer.service.js.map
|