nehonix-uri-processor
Version:
A powerful URI processor for encoding, decoding, and analyzing URI data securely.
1,067 lines • 60.9 kB
JavaScript
import { toAscii } from "idna-uts46-hx";
import { AppLogger } from "../common/AppLogger";
import { PATTERNS } from "../utils/attacks_parttens";
import { MaliciousPatternType, } from "./MaliciousPatterns.service";
import NDS from "./NehonixDec.service";
/**
* Enhanced service for detecting various malicious patterns in URLs and general input
* Nehonix Malicious Parttens Service => NMPS
* Nehonix Security Service => NSS
*/
export class NSS {
static isSafeHighEntropy(input) {
// JWT pattern
if (/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(input)) {
return true;
}
// Data URI for images
if (/^data:image\/(png|jpeg|gif);base64,[A-Za-z0-9+/=]+$/.test(input)) {
return true;
}
// API key pattern (example: 32+ alphanumeric characters)
if (/^[A-Za-z0-9]{32,}$/.test(input)) {
return true;
}
return false;
}
/**
* Analyzes input for malicious patterns and returns detailed detection results
*
* @param input - The string to analyze
* @param options - Configuration options for detection
* @returns Detailed analysis result
*/
static detectMaliciousPatterns(receivedInput, options = {}) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
try {
// Set default options
const opts = {
minScore: (_a = options.minScore) !== null && _a !== void 0 ? _a : 50,
debug: (_b = options.debug) !== null && _b !== void 0 ? _b : false,
ignorePatterns: (_c = options.ignorePatterns) !== null && _c !== void 0 ? _c : [],
sensitivity: (_d = options.sensitivity) !== null && _d !== void 0 ? _d : 1.0,
customPatterns: (_e = options.customPatterns) !== null && _e !== void 0 ? _e : [],
enableContextualAnalysis: (_f = options.enableContextualAnalysis) !== null && _f !== void 0 ? _f : true,
enableEntropyAnalysis: (_g = options.enableEntropyAnalysis) !== null && _g !== void 0 ? _g : true,
enableStatisticalAnalysis: (_h = options.enableStatisticalAnalysis) !== null && _h !== void 0 ? _h : true,
componentSensitivity: (_j = options.componentSensitivity) !== null && _j !== void 0 ? _j : {
protocol: 1.0,
hostname: 1.2,
path: 1.0,
query: 1.5,
fragment: 1.3,
},
characterSet: (_k = options.characterSet) !== null && _k !== void 0 ? _k : "all",
};
const parsedInput = NDS.decodeAnyToPlaintext(receivedInput).val();
if (opts.debug) {
AppLogger.debug("NMPS: Analyzing input with options:", opts);
}
// Normalize Unicode input
const normalizedInput = parsedInput.normalize("NFC");
// Use normalized input for checks
const inputsToCheck = [normalizedInput, parsedInput].filter((i, idx, arr) => i !== arr[idx - 1]);
let decodedInput = "";
let totalScore = 0;
let contextAnalysis;
const detectedPatterns = [];
inputsToCheck.forEach((ipt) => {
const input = NDS.decodeAnyToPlaintext(ipt).val();
// Store all detected patterns
decodedInput = input;
const encodingLayers = this.detectEncodingLayers(input);
if (encodingLayers > 0) {
let tempInput = input;
for (let i = 0; i < encodingLayers; i++) {
try {
tempInput = decodeURIComponent(tempInput.replace(/%25/g, "%"));
tempInput = tempInput
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/&/g, "&")
.replace(/"/g, '"')
.replace(/&#x[0-9a-fA-F]+;/g, (match) => {
const hex = match.substring(3, match.length - 1);
return String.fromCodePoint(parseInt(hex, 16));
})
.replace(/&#\d+;/g, (match) => {
const decimal = match.substring(2, match.length - 1);
return String.fromCodePoint(parseInt(decimal, 10));
});
tempInput = tempInput.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => {
return String.fromCodePoint(parseInt(hex, 16));
});
}
catch (_a) { }
decodedInput = tempInput;
}
}
if (opts.enableEntropyAnalysis && !this.isSafeHighEntropy(input)) {
const entropyScore = this.calculateEntropy(input);
if (entropyScore > 4.5) {
detectedPatterns.push({
type: MaliciousPatternType.ENCODED_PAYLOAD,
pattern: "high_entropy",
location: "full_input",
severity: "medium",
confidence: "medium",
description: "High entropy content may indicate obfuscated payload",
contextScore: entropyScore,
});
if (contextAnalysis) {
contextAnalysis.entropyScore = entropyScore;
}
}
}
// Check for SQL injection patterns
if (!opts.ignorePatterns.includes(MaliciousPatternType.SQL_INJECTION)) {
this.checkPatterns(input, this.SQL_INJECTION_PATTERNS, MaliciousPatternType.SQL_INJECTION, "SQL injection attempt", "high", detectedPatterns, opts);
}
// Check for XSS patterns
if (!opts.ignorePatterns.includes(MaliciousPatternType.XSS)) {
this.checkPatterns(input, this.XSS_PATTERNS, MaliciousPatternType.XSS, "Cross-site scripting attempt", "high", detectedPatterns, opts);
}
// Check for RFI patterns
if (!opts.ignorePatterns.includes(MaliciousPatternType.RFI)) {
this.checkPatterns(input, this.RFI_PATTERNS, MaliciousPatternType.RFI, "Remote file inclusion attempt", "high", detectedPatterns, opts);
}
// Check for command injection patterns
if (!opts.ignorePatterns.includes(MaliciousPatternType.COMMAND_INJECTION)) {
this.checkPatterns(input, this.COMMAND_INJECTION_PATTERNS, MaliciousPatternType.COMMAND_INJECTION, "Command injection attempt", "high", detectedPatterns, opts);
}
// Check for path traversal patterns
if (!opts.ignorePatterns.includes(MaliciousPatternType.PATH_TRAVERSAL)) {
this.checkPatterns(input, this.PATH_TRAVERSAL_PATTERNS, MaliciousPatternType.PATH_TRAVERSAL, "Path traversal attempt", "high", detectedPatterns, opts);
}
// Check for open redirect patterns
if (!opts.ignorePatterns.includes(MaliciousPatternType.OPEN_REDIRECT)) {
this.checkPatterns(input, this.OPEN_REDIRECT_PATTERNS, MaliciousPatternType.OPEN_REDIRECT, "Open redirect attempt", "medium", detectedPatterns, opts);
}
// Check for SSRF patterns
if (!opts.ignorePatterns.includes(MaliciousPatternType.SSRF)) {
this.checkPatterns(input, this.SSRF_PATTERNS, MaliciousPatternType.SSRF, "Server-side request forgery attempt", "high", detectedPatterns, opts);
}
// Check for CRLF injection patterns
if (!opts.ignorePatterns.includes(MaliciousPatternType.CRLF_INJECTION)) {
this.checkPatterns(input, this.CRLF_INJECTION_PATTERNS, MaliciousPatternType.CRLF_INJECTION, "CRLF injection attempt", "medium", detectedPatterns, opts);
}
// Check for template injection patterns
if (!opts.ignorePatterns.includes(MaliciousPatternType.TEMPLATE_INJECTION)) {
this.checkPatterns(input, this.TEMPLATE_INJECTION_PATTERNS, MaliciousPatternType.TEMPLATE_INJECTION, "Template injection attempt", "high", detectedPatterns, opts);
}
// Check for NoSQL injection patterns
if (!opts.ignorePatterns.includes(MaliciousPatternType.NOSQL_INJECTION)) {
this.checkPatterns(input, this.NOSQL_INJECTION_PATTERNS, MaliciousPatternType.NOSQL_INJECTION, "NoSQL injection attempt", "high", detectedPatterns, opts);
}
// Check for GraphQL injection patterns
if (!opts.ignorePatterns.includes(MaliciousPatternType.GRAPHQL_INJECTION)) {
this.checkPatterns(input, this.GRAPHQL_INJECTION_PATTERNS, MaliciousPatternType.GRAPHQL_INJECTION, "GraphQL injection attempt", "high", detectedPatterns, opts);
}
// Check for encoded payload patterns
if (!opts.ignorePatterns.includes(MaliciousPatternType.ENCODED_PAYLOAD)) {
this.checkPatterns(input, this.ENCODED_PAYLOAD_PATTERNS, MaliciousPatternType.ENCODED_PAYLOAD, "Suspicious encoded payload", "medium", detectedPatterns, opts);
}
// Check for suspicious TLD patterns
if (!opts.ignorePatterns.includes(MaliciousPatternType.SUSPICIOUS_TLD)) {
this.checkPatterns(input, this.SUSPICIOUS_TLD_PATTERNS, MaliciousPatternType.SUSPICIOUS_TLD, "Suspicious TLD detected", "low", detectedPatterns, opts);
}
// Check for homograph attack patterns
if (!opts.ignorePatterns.includes(MaliciousPatternType.HOMOGRAPH_ATTACK)) {
this.checkPatterns(input, this.HOMOGRAPH_ATTACK_PATTERNS, MaliciousPatternType.HOMOGRAPH_ATTACK, "Potential homograph attack", "medium", detectedPatterns, opts);
}
// Check for multi-encoding patterns
if (!opts.ignorePatterns.includes(MaliciousPatternType.MULTI_ENCODING)) {
this.checkPatterns(input, this.MULTI_ENCODING_PATTERNS, MaliciousPatternType.MULTI_ENCODING, "Multi-layer encoding detected", "medium", detectedPatterns, opts);
}
// Add encoding layer detection
if (encodingLayers > 1) {
detectedPatterns.push({
type: MaliciousPatternType.MULTI_ENCODING,
pattern: "multi_layer_encoding",
location: "full_input",
severity: "medium",
confidence: "high",
description: `Multiple encoding layers detected (${encodingLayers})`,
matchedValue: input,
contextScore: encodingLayers * 1.5,
});
}
// Check for suspicious parameter names
if (!opts.ignorePatterns.includes(MaliciousPatternType.SUSPICIOUS_PARAMETER)) {
this.checkSuspiciousParameters(input, detectedPatterns, opts);
}
// Check custom patterns if provided
if (opts.customPatterns && opts.customPatterns.length > 0) {
for (const customPattern of opts.customPatterns) {
const match = customPattern.pattern.exec(input);
if (match) {
const matchedValue = match[0];
const confidence = this.calculateConfidence(matchedValue, input);
const contextScore = opts.enableContextualAnalysis
? this.calculateContextScore(match, input)
: undefined;
detectedPatterns.push({
type: customPattern.type,
pattern: customPattern.pattern.toString(),
location: `index: ${match.index}`,
severity: customPattern.severity,
confidence,
description: customPattern.description,
matchedValue,
contextScore,
});
}
}
}
// Perform contextual analysis if enabled
if (opts.enableContextualAnalysis && detectedPatterns.length > 0) {
contextAnalysis = this.performContextualAnalysis(detectedPatterns, input, opts);
}
// Calculate entropy if enabled
if (opts.enableEntropyAnalysis) {
const entropyScore = this.calculateEntropy(input);
// High entropy can indicate obfuscation
if (entropyScore > 4.5) {
detectedPatterns.push({
type: MaliciousPatternType.ENCODED_PAYLOAD,
pattern: "high_entropy",
location: "full_input",
severity: "medium",
confidence: "medium",
description: "High entropy content may indicate obfuscated payload",
contextScore: entropyScore,
});
if (contextAnalysis) {
contextAnalysis.entropyScore = entropyScore;
}
}
}
// Calculate total score based on detected patterns
totalScore = this.calculateTotalScore(detectedPatterns, opts.sensitivity);
});
// Determine overall confidence level
const confidence = this.determineConfidence(totalScore, detectedPatterns.length);
// Generate appropriate recommendation
const recommendation = this.generateRecommendation(detectedPatterns, totalScore);
return {
isMalicious: totalScore >= opts.minScore,
detectedPatterns,
score: totalScore,
confidence,
recommendation,
contextAnalysis,
};
}
catch (error) {
AppLogger.error("Error in NMPS.detectMaliciousPatterns:", error);
return {
isMalicious: false,
detectedPatterns: [],
score: 0,
confidence: "low",
recommendation: "Error analyzing input. Please try again with simplified input.",
};
}
}
/**
* Analyzes a URL for malicious patterns with specific sensitivity per URL component
*
* @param url - The URL to analyze
* @param options - Configuration options for detection
* @returns Detailed analysis result
*/
static async analyzeUrl(url, options = {}) {
var _a, _b, _c;
try {
const parsedUrl = new URL(url);
const detectedPatterns = [];
const componentResults = {};
const opts = { ...options };
// Normalize hostname for homograph detection
const normalizedHostname = toAscii(parsedUrl.hostname);
if (normalizedHostname !== parsedUrl.hostname) {
detectedPatterns.push({
type: MaliciousPatternType.HOMOGRAPH_ATTACK,
pattern: "punycode_conversion",
location: `hostname:${parsedUrl.hostname}`,
severity: "medium",
confidence: "high",
description: `Hostname converted from punycode: ${parsedUrl.hostname} -> ${normalizedHostname}`,
matchedValue: parsedUrl.hostname,
});
}
// Analyze each component separately
const components = [
{ type: "protocol", value: parsedUrl.protocol },
{ type: "hostname", value: parsedUrl.hostname },
{ type: "path", value: parsedUrl.pathname },
{ type: "query", value: parsedUrl.search },
{ type: "fragment", value: parsedUrl.hash },
];
// Analyze each component with its custom sensitivity
for (const component of components) {
if (component.value) {
let valueToCheck = await NDS.asyncDecodeAnyToPlainText(component.value, {
maxIterations: 20,
}).then((res) => res.val());
// Decode fragment specifically
if (component.type === "fragment" && valueToCheck.startsWith("#")) {
valueToCheck = valueToCheck.substring(1);
try {
valueToCheck = decodeURIComponent(valueToCheck);
}
catch (_d) { }
}
if (opts.componentSensitivity && component.type) {
opts.sensitivity =
(options.sensitivity || 1.0) *
(((_a = options === null || options === void 0 ? void 0 : options.componentSensitivity) === null || _a === void 0 ? void 0 : _a[component.type]) || 1.0);
}
const result = this.detectMaliciousPatterns(valueToCheck, opts);
componentResults[component.type] = result;
if (result.detectedPatterns.length > 0) {
for (const pattern of result.detectedPatterns) {
detectedPatterns.push({
...pattern,
location: `${component.type}:${pattern.location}`,
});
}
}
}
}
// Check for mixed scripts in hostname
const mixedScriptPattern = /([a-zA-Z])([\u0400-\u04FF])/i;
if (mixedScriptPattern.test(parsedUrl.hostname)) {
detectedPatterns.push({
type: MaliciousPatternType.HOMOGRAPH_ATTACK,
pattern: "mixed_script",
location: `hostname:${parsedUrl.hostname}`,
severity: "medium",
confidence: "high",
description: "Hostname contains mixed Latin and non-Latin scripts, potential homograph attack",
matchedValue: parsedUrl.hostname,
});
}
// Special check for URL parameters
if (parsedUrl.searchParams) {
for (const [key, value] of parsedUrl.searchParams.entries()) {
// Check for suspicious parameter names
if (this.SUSPICIOUS_PARAMETER_NAMES.includes(key.toLowerCase())) {
detectedPatterns.push({
type: MaliciousPatternType.SUSPICIOUS_PARAMETER,
pattern: "suspicious_param_name",
location: `query:parameter_name:${key}`,
severity: "low",
confidence: "medium",
description: `Suspicious parameter name "${key}" detected`,
matchedValue: key,
});
}
// Check for malicious parameter values
const valueResult = this.detectMaliciousPatterns(value, {
...opts,
sensitivity: (options.sensitivity || 1.0) *
((((_b = options.componentSensitivity) === null || _b === void 0 ? void 0 : _b.query) || 1.0) * 1.2), // Extra sensitivity for parameter values
});
if (valueResult.detectedPatterns.length > 0) {
for (const pattern of valueResult.detectedPatterns) {
AppLogger.log("pattern", pattern);
detectedPatterns.push({
...pattern,
location: `query:parameter_value:${key}:${pattern.location}`,
});
}
}
}
}
// Calculate combined score with weighted components
let totalScore = this.calculateTotalScore(detectedPatterns, options.sensitivity || 1.0);
// Determine overall confidence level
const confidence = this.determineConfidence(totalScore, detectedPatterns.length);
// Generate contextualAnalysis if multiple components have issues
let contextAnalysis;
if (((_c = options.enableContextualAnalysis) !== null && _c !== void 0 ? _c : true) &&
detectedPatterns.length > 0) {
contextAnalysis = this.performContextualAnalysis(detectedPatterns, url, options);
// Add cross-component analysis
if (detectedPatterns.length > 0) {
// Find related patterns across components
const relatedGroups = this.findRelatedPatternGroups(detectedPatterns);
if (contextAnalysis && relatedGroups.length > 0) {
contextAnalysis.relatedPatterns = relatedGroups;
// Add additional score for sophisticated multi-component attacks
if (relatedGroups.some((g) => g.riskMultiplier > 1.5)) {
totalScore *= 1.25;
}
}
}
}
// Generate URL-specific recommendation
const recommendation = this.generateUrlRecommendation(detectedPatterns, componentResults);
return {
isMalicious: totalScore >= (options.minScore || 50),
detectedPatterns,
score: totalScore,
confidence,
recommendation,
contextAnalysis,
};
}
catch (error) {
AppLogger.error("Error in NMPS.analyzeUrl:", error);
// Fall back to basic analysis if URL parsing fails
if (error instanceof TypeError && error.message.includes("Invalid URL")) {
AppLogger.warn("Invalid URL format, falling back to basic analysis");
return this.detectMaliciousPatterns(url, options);
}
return {
isMalicious: false,
detectedPatterns: [],
score: 0,
confidence: "low",
recommendation: "Error analyzing URL. Please verify the URL format is correct.",
};
}
}
/**
* Generates a recommendation specifically for URLs based on detected patterns
*/
static generateUrlRecommendation(detectedPatterns, componentResults) {
if (detectedPatterns.length === 0) {
return "No suspicious patterns detected in the URL.";
}
const componentIssues = [];
let hasCriticalIssue = false;
// Check severity of issues by component
Object.entries(componentResults).forEach(([component, result]) => {
if (result.detectedPatterns.length > 0) {
const highSeverity = result.detectedPatterns.some((p) => p.severity === "high");
if (highSeverity) {
hasCriticalIssue = true;
componentIssues.push(`Critical issues found in the ${component} component`);
}
else {
componentIssues.push(`Suspicious patterns found in the ${component} component`);
}
}
});
// Generate overall recommendation
if (hasCriticalIssue) {
return `This URL contains potentially malicious patterns. ${componentIssues.join(". ")}. Consider blocking this URL and scanning related systems for compromise.`;
}
else if (componentIssues.length > 1) {
return `This URL has multiple suspicious components: ${componentIssues.join("; ")}. Recommend further review before processing this URL.`;
}
else {
return `This URL contains suspicious patterns. ${componentIssues[0]}. Proceed with caution and validate the URL source.`;
}
}
/**
* Finds related patterns across different components that might indicate a sophisticated attack
*/
static findRelatedPatternGroups(patterns) {
const groups = [];
// Find cross-site scripting patterns across multiple components
const xssPatterns = patterns.filter((p) => p.type === MaliciousPatternType.XSS);
if (xssPatterns.length > 1) {
groups.push({
patterns: [MaliciousPatternType.XSS],
description: "Multiple XSS vectors detected across different URL components",
riskMultiplier: 1.5,
});
}
// Find SQL injection patterns across multiple components
const sqlInjectionPatterns = patterns.filter((p) => p.type === MaliciousPatternType.SQL_INJECTION);
if (sqlInjectionPatterns.length > 1) {
groups.push({
patterns: [MaliciousPatternType.SQL_INJECTION],
description: "Multiple SQL injection vectors detected across different URL components",
riskMultiplier: 1.6,
});
}
// Find encoding obfuscation techniques
const encodingPatterns = patterns.filter((p) => p.type === MaliciousPatternType.ENCODED_PAYLOAD ||
p.type === MaliciousPatternType.MULTI_ENCODING);
// Check for encoding + injection combination (sophisticated attack)
if (encodingPatterns.length > 0) {
const injectionPatterns = patterns.filter((p) => p.type === MaliciousPatternType.SQL_INJECTION ||
p.type === MaliciousPatternType.XSS ||
p.type === MaliciousPatternType.COMMAND_INJECTION ||
p.type === MaliciousPatternType.TEMPLATE_INJECTION ||
p.type === MaliciousPatternType.NOSQL_INJECTION);
if (injectionPatterns.length > 0) {
groups.push({
patterns: [
MaliciousPatternType.ENCODED_PAYLOAD,
...injectionPatterns.map((p) => p.type),
],
description: "Encoded payload combined with injection attempt - sophisticated evasion technique",
riskMultiplier: 2.0,
});
}
}
// Detect potential combination attacks
const hasRedirect = patterns.some((p) => p.type === MaliciousPatternType.OPEN_REDIRECT);
const hasXss = patterns.some((p) => p.type === MaliciousPatternType.XSS);
if (hasRedirect && hasXss) {
groups.push({
patterns: [
MaliciousPatternType.OPEN_REDIRECT,
MaliciousPatternType.XSS,
],
description: "Combined redirect and XSS attack vector detected",
riskMultiplier: 1.8,
});
}
// Detect protocol confusion attacks
const hasProtocolConfusion = patterns.some((p) => p.type === MaliciousPatternType.PROTOCOL_CONFUSION);
const hasSsrf = patterns.some((p) => p.type === MaliciousPatternType.SSRF);
if (hasProtocolConfusion && hasSsrf) {
groups.push({
patterns: [
MaliciousPatternType.PROTOCOL_CONFUSION,
MaliciousPatternType.SSRF,
],
description: "Protocol confusion combined with SSRF attempt",
riskMultiplier: 1.9,
});
}
return groups;
}
/**
* Checks a string against a set of regex patterns for a specific attack type
*/
static checkPatterns(input, patterns, type, description, severity, results, options) {
for (const pattern of patterns) {
const match = pattern.exec(input);
if (match) {
const matchedValue = match[0];
const confidence = this.calculateConfidence(matchedValue, input);
const contextScore = options.enableContextualAnalysis
? this.calculateContextScore(match, input)
: undefined;
results.push({
type,
pattern: pattern.toString(),
location: `index: ${match.index}`,
severity,
confidence,
description,
matchedValue,
contextScore,
});
if (options.debug) {
AppLogger.debug(`NMPS: Detected ${type} pattern: '${matchedValue}' at index ${match.index}`);
}
// Only report the first match for each pattern to avoid redundancy
break;
}
}
}
/**
* Checks for suspicious parameter names in a URL
*/
static checkSuspiciousParameters(input, results, options) {
try {
// Try to extract parameter names from URL query string
let params = [];
// Check if input contains URL parameters
const queryStart = input.indexOf("?");
if (queryStart !== -1) {
const queryString = input.substring(queryStart + 1);
const pairs = queryString.split("&");
for (const pair of pairs) {
const eqIndex = pair.indexOf("=");
if (eqIndex !== -1) {
const paramName = pair.substring(0, eqIndex).toLowerCase();
params.push(paramName);
}
}
}
// Check parameter names against suspicious list
for (const param of params) {
if (this.SUSPICIOUS_PARAMETER_NAMES.includes(param)) {
results.push({
type: MaliciousPatternType.SUSPICIOUS_PARAMETER,
pattern: "suspicious_param_name",
location: `parameter: ${param}`,
severity: "low",
confidence: "medium",
description: `Suspicious parameter name "${param}" detected`,
matchedValue: param,
});
if (options.debug) {
AppLogger.debug(`NMPS: Detected suspicious parameter: '${param}'`);
}
}
}
}
catch (error) {
AppLogger.error("Error in checkSuspiciousParameters:", error);
}
}
/**
* Calculates confidence level for a pattern match based on match characteristics
*/
// In NehonixSecurity.service.txt
static calculateConfidence(matchedValue, fullInput) {
const matchRatio = matchedValue.length / fullInput.length;
// High-confidence patterns
const highConfidencePatterns = [
/<script/i,
/javascript:/i,
/onload=/i,
/alert\(/i,
/select.*from/i,
/union.*select/i,
/whoami/i,
/ping/i,
/etc\/passwd/i,
/localhost/i,
];
if (highConfidencePatterns.some((pattern) => pattern.test(matchedValue))) {
return "high";
}
if (matchRatio < 0.1 && matchedValue.length < 5) {
return "low";
}
if (this.isLikelyFalsePositive(matchedValue, fullInput)) {
return "low";
}
if (matchedValue.length >= 10 || matchRatio > 0.5) {
return "high";
}
return "medium";
}
/**
* Checks if a match is likely a false positive based on context
*/
// In NehonixSecurity.service.txt
static isLikelyFalsePositive(match, fullInput) {
const falsePositiveContexts = [
/https?:\/\/[^\/]+\/documentation\/examples?\/.*sql/i,
/https?:\/\/[^\/]+\/blog\/.*security/i,
/code example/i,
/security training/i,
/\/\/\s*Example/i,
// Ignore common URL parameters
/\?(sort|filter|category|page|limit|offset)=/i,
];
for (const context of falsePositiveContexts) {
if (context.test(fullInput)) {
return true;
}
}
// Ignore matches in legitimate query parameters
if (/[&?]sort=/.test(fullInput) && match.includes("sort")) {
return true;
}
/**
* TODO: Add a feed back for false positives
* */
return false;
}
/**
* Calculates additional context score for pattern matches
*/
static calculateContextScore(match, fullInput) {
let score = 1.0;
const start = Math.max(0, match.index - 10);
const end = Math.min(fullInput.length, match.index + match[0].length + 10);
const context = fullInput.substring(start, end);
if (/\\x|\\u|%u|%[0-9a-f]{2}|&#\d+;|&#x[0-9a-f]+;/i.test(context)) {
score *= 1.5;
}
if (/\/\*|\*\/|--|#|\/{2}/i.test(context)) {
score *= 1.3;
}
const criticalPatterns = [
/etc\/passwd/i,
/localhost/i,
/192\.168\.\d{1,3}\.\d{1,3}/i,
/file=https?:\/\//i,
];
for (const pattern of criticalPatterns) {
if (pattern.test(context)) {
score *= 1.5;
break;
}
}
return score;
}
/**
* Performs contextual analysis on detected patterns to improve detection accuracy
*/
static performContextualAnalysis(patterns, fullInput, options) {
// Find relationships between detected patterns
const relatedGroups = this.findRelatedPatternGroups(patterns);
// Calculate entropy score
const entropyScore = this.calculateEntropy(fullInput);
// Detect potential encoding layers
const encodingLayers = this.detectEncodingLayers(fullInput);
// Calculate anomaly score based on character distribution
const anomalyScore = this.calculateAnomalyScore(fullInput);
return {
relatedPatterns: relatedGroups,
entropyScore,
anomalyScore,
encodingLayers,
};
}
/**
* Calculates Shannon entropy of a string to detect random or encoded content
* Higher entropy often indicates encryption or encoding
*/
static calculateEntropy(input) {
const len = input.length;
const charCounts = {};
// Count character occurrences
for (let i = 0; i < len; i++) {
const char = input[i];
charCounts[char] = (charCounts[char] || 0) + 1;
}
// Calculate entropy
let entropy = 0;
for (const char in charCounts) {
const probability = charCounts[char] / len;
entropy -= probability * Math.log2(probability);
}
return entropy;
}
/**
* Detects number of potential encoding layers in a string
*/
static detectEncodingLayers(input) {
let layers = 0;
let currentInput = input;
const maxLayers = 5; // Prevent infinite loops
let attempts = 0;
while (attempts < maxLayers) {
let decoded = currentInput;
// URL decoding
if (/%[0-9A-Fa-f]{2}/.test(decoded)) {
try {
decoded = decodeURIComponent(decoded);
if (decoded !== currentInput) {
layers++;
currentInput = decoded;
}
}
catch (_a) { }
}
// HTML entities
if (/&[#a-zA-Z0-9]+;/.test(decoded)) {
const temp = decoded
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/&/g, "&")
.replace(/"/g, '"')
.replace(/&#x[0-9a-fA-F]+;/g, (match) => {
try {
const hex = match.substring(3, match.length - 1);
return String.fromCodePoint(parseInt(hex, 16));
}
catch (_a) {
return match;
}
})
.replace(/&#\d+;/g, (match) => {
try {
const decimal = match.substring(2, match.length - 1);
return String.fromCodePoint(parseInt(decimal, 10));
}
catch (_a) {
return match;
}
});
if (temp !== decoded) {
layers++;
currentInput = temp;
}
}
// Unicode escapes
if (/\\u[0-9a-fA-F]{4}/.test(decoded)) {
try {
const temp = decoded.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => {
return String.fromCodePoint(parseInt(hex, 16));
});
if (temp !== decoded) {
layers++;
currentInput = temp;
}
}
catch (_b) { }
}
// Base64
if (/^[A-Za-z0-9+/=]+$/.test(decoded)) {
try {
const temp = Buffer.from(decoded, "base64").toString();
if (temp !== decoded && temp.length > 0) {
layers++;
currentInput = temp;
}
}
catch (_c) { }
}
// If no further decoding occurred, break
if (decoded === currentInput) {
break;
}
attempts++;
}
return layers;
}
/**
* Calculates statistical anomaly score based on character distribution
*/
static calculateAnomalyScore(input) {
const len = input.length;
if (len === 0)
return 0;
// Character type counts
let lowerCount = 0;
let upperCount = 0;
let digitCount = 0;
let specialCount = 0;
let controlCount = 0;
let nonAsciiCount = 0;
// Count character types
for (let i = 0; i < len; i++) {
const code = input.charCodeAt(i);
if (code >= 97 && code <= 122) {
// a-z
lowerCount++;
}
else if (code >= 65 && code <= 90) {
// A-Z
upperCount++;
}
else if (code >= 48 && code <= 57) {
// 0-9
digitCount++;
}
else if (code < 32 || code === 127) {
// Control characters
controlCount++;
}
else if (code > 127) {
// Non-ASCII
nonAsciiCount++;
}
else {
specialCount++; // Other special characters
}
}
// Calculate percentages
const lowerPercent = lowerCount / len;
const upperPercent = upperCount / len;
const digitPercent = digitCount / len;
const specialPercent = specialCount / len;
const controlPercent = controlCount / len;
const nonAsciiPercent = nonAsciiCount / len;
// Calculate anomaly score - higher means more unusual distribution
let anomalyScore = 0;
// Unusual amount of special characters
if (specialPercent > 0.3) {
anomalyScore += specialPercent * 2;
}
// Unusual amount of non-ASCII characters
if (nonAsciiPercent > 0.1) {
anomalyScore += nonAsciiPercent * 3;
}
// Presence of control characters is very suspicious
if (controlPercent > 0) {
anomalyScore += controlPercent * 5;
}
// Unusual character type distribution
const alphaPercent = lowerPercent + upperPercent;
if (alphaPercent < 0.2 && len > 10) {
anomalyScore += 1;
}
// Very high percentage of digits can be suspicious
if (digitPercent > 0.7 && len > 10) {
anomalyScore += 0.5;
}
return Math.min(anomalyScore, 5); // Cap at 5
}
/**
* Calculates total risk score based on detected patterns
*/
// In NehonixSecurity.service.txt
static calculateTotalScore(patterns, sensitivityMultiplier) {
if (patterns.length === 0)
return 0;
let score = 0;
const severityScores = {
high: 40,
medium: 20,
low: 10,
};
const criticalPatternMultipliers = {
[MaliciousPatternType.PATH_TRAVERSAL]: 1.5,
[MaliciousPatternType.SSRF]: 1.5,
[MaliciousPatternType.RFI]: 1.5,
};
const confidenceMultipliers = {
high: 1.5,
medium: 1.0,
low: 0.5,
};
for (const pattern of patterns) {
let patternScore = severityScores[pattern.severity] *
confidenceMultipliers[pattern.confidence];
// Apply critical pattern multiplier
if (criticalPatternMultipliers[pattern.type]) {
patternScore *=
criticalPatternMultipliers[pattern.type];
}
if (pattern.contextScore) {
patternScore *= pattern.contextScore;
}
score += patternScore;
}
score *= sensitivityMultiplier;
const patternTypeCounts = {};
for (const pattern of patterns) {
patternTypeCounts[pattern.type] =
(patternTypeCounts[pattern.type] || 0) + 1;
}
for (const type in patternTypeCounts) {
if (patternTypeCounts[type] > 1) {
score *= 1 + (patternTypeCounts[type] - 1) * 0.2;
}
}
return Math.min(Math.round(score), 100);
}
/**
* Determines overall confidence level based on score and pattern count
*/
static determineConfidence(score, patternCount) {
if (score >= 75 || (score >= 50 && patternCount >= 3)) {
return "high";
}
else if (score >= 40 || (score >= 25 && patternCount >= 2)) {
return "medium";
}
else {
return "low";
}
}
/**
* Generates appropriate recommendation based on detected patterns
*/
static generateRecommendation(patterns, score) {
if (patterns.length === 0) {
return "No malicious patterns detected. Input appears safe.";
}
const patternTypes = new Set(patterns.map((p) => p.type));
const recommendations = [];
// Critical recommendations first
if (score >= 75) {
recommendations.push("HIGH RISK: Input contains malicious patterns. Block and investigate immediately.");
}
else if (score >= 50) {
recommendations.push("MEDIUM RISK: Input contains suspicious patterns. Validate before processing.");
}
else {
recommendations.push("LOW RISK: Input contains potentially suspicious patterns. Use caution.");
}
// Specific recommendations based on pattern types
if (patternTypes.has(MaliciousPatternType.SQL_INJECTION)) {
recommendations.push("Implement prepared statements or parameterized queries for database operations.");
}
if (patternTypes.has(MaliciousPatternType.XSS)) {
recommendations.push("Implement output encoding and content security policy (CSP) headers.");
}
if (patternTypes.has(MaliciousPatternType.COMMAND_INJECTION)) {
recommendations.push("Avoid direct command execution. Use restricted APIs and allowlists.");
}
if (patternTypes.has(MaliciousPatternType.PATH_TRAVERSAL)) {
recommendations.push("Validate file paths and use path canonicalization before file operations.");
}
if (patternTypes.has(MaliciousPatternType.SSRF)) {
recommendations.push("Implement allowlists for external resource access and validate URLs.");
}
if (patternTypes.has(MaliciousPatternType.RFI)) {
recommendations.push("Validate file inclusions against a whitelist of allowed sources and disable remote file access.");
}
if (patternTypes.has(MaliciousPatternType.ENCODED_PAYLOAD) ||
patternTypes.has(MaliciousPatternType.MULTI_ENCODING)) {
recommendations.push("Decode and normalize input before validation to prevent evasion techniques.");
}
return recommendations.join(" ");
}
/**
* Analyzes input for a specific malicious pattern type
*
* @param input - The string to analyze
* @param patternType - The specific pattern type to check for
* @param options - Configuration options for detection
* @returns Boolean indicating if pattern was detected
*/
static detectSpecificPatternType(input, patternType, options = {}) {
// Use full detection but filter for specific pattern type
const result = this.detectMaliciousPatterns(input, {
...options,
minScore: 1, // Set minimum score low to catch any matches
});
return result.detectedPatterns.some((p) => p.type === patternType);
}
/**
* Sanitizes input by removing potentially malicious patterns
*
* @param input - The string to sanitize
* @param options - Additional sanitization options
* @returns Sanitized string
*/
/**
* Sanitizes input by removing potentially malicious patterns
*
* @param input - The string to sanitize
* @param options - Additional sanitization options
* @returns Sanitized string
*/
static sanitizeInput(input, options = {}) {
var _a, _b, _c, _d, _e;
try {
if (!input)
return "";
let sanitized = input;
// Default options
const opts = {
allowHtml: (_a = options.allowHtml) !== null && _a !== void 0 ? _a : false,
allowMarkdown: (_b = options.allowMarkdown) !== null && _b !== void 0 ? _b : true,
strictMode: (_c = options.strictMode) !== null && _c !== void 0 ? _c : false,
preserveLength: (_d = options.preserveLength) !== null && _d !== void 0 ? _d : false,
customPatterns: (_e = options.customPatterns) !== null && _e !== void 0 ? _e : [],
};
// HTML sanitization if HTML not allowed
if (!opts.allowHtml) {
// More comprehensive script tag handling including variants
sanitized = sanitized
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, opts.preserveLength ? this.createPlaceholder("script-block", 0) : "")
.replace(/<\s*script[^>]*>(.*?)<\s*\/\s*script\s*>/gi, opts.preserveLength
? this.createPlaceholder("script-inline", 0)
: "");
// More comprehensive HTML tag handling
sanitized = sanitized.replace(/<(\/?\w+)((\s+\w+(\s*=\s*(?:".*?"|'.*?'|[^'">\s]+))?)+\s*|\s*)\/?>/gi, (match, tag, attrs) => {
// Check against allowed tags if not in strict mode
const safeTag = this.isSafeTag(tag) && !opts.strictMode;
return safeTag
? match
: opts.preserveLength
? this.createPlaceholder("tag", match.length)
: `<${tag}${attrs}>`;
});
// Remove event handlers with more extensive coverage
const eventHandlerPattern = /\s+(on\w+)\s*=\s*["']?[^"']*["']?/gi;
sanitized = sanitized.replace(eventHandlerPattern, (_, handler) => {
return opts.preserveLength ? ` data-blocked-${handler}=""` : "";
});
// More comprehensive JavaScript URL blocking
sanitized = sanitized.replace(/\b(href|src|data|action|formaction)\s*=\s*["']?\s*(javascript|data|vbscript):/gi, (_, attr, protocol) => `${attr}="${protocol}_blocked:"`);
}
// SQL injection protection with more patterns
const sqlPatterns = [
{
pattern: /(\b)(select|insert|update|delete|drop|a