UNPKG

nehonix-uri-processor

Version:

A powerful URI processor for encoding, decoding, and analyzing URI data securely.

697 lines 28.8 kB
import { jsx as _jsx } from "react/jsx-runtime"; import React, { useEffect, useState, useRef, useCallback, useContext, } from "react"; import { NSB } from "../../../services/NehonixSecurityBooster.service"; import NDS from "../../../services/NehonixDec.service"; import { NehonixShieldContext } from "../context/REACT.ShieldContext"; import { MaliciousPatternType, } from "../../../services/MaliciousPatterns.service"; // Create context for the DOM processor export const NehonixDomProcessorContext = React.createContext(null); // Hook to use the DOM processor context export const useNehonixDomProcessor = () => { const context = useContext(NehonixDomProcessorContext); if (!context) { throw new Error("useNehonixDomProcessor must be used within a NehonixDomProcessorProvider"); } return context; }; // Utility to map string confidence to numeric value const mapConfidenceToNumber = (confidence) => { switch (confidence) { case "low": return 0.3; case "medium": return 0.6; case "high": return 0.9; default: return 0; } }; // Utility to generate a CSS selector path for an element const getDomPath = (element) => { if (!element.parentElement) return element.tagName.toLowerCase(); const pathParts = []; let current = element; while (current && current.tagName) { let selector = current.tagName.toLowerCase(); if (current.id) { selector = `#${current.id}`; pathParts.unshift(selector); break; } if (current.className && typeof current.className === "string") { const classes = current.className.trim().split(/\s+/).join("."); if (classes) selector += `.${classes}`; } pathParts.unshift(selector); current = current.parentElement; } return pathParts.join(" > "); }; // Default configuration for the DOM processor const defaultConfig = { enabled: true, processingMode: "idle-callback", chunkSize: 50, idleTimeout: 2000, throttleInterval: 1000, targetElements: { a: true, script: true, iframe: true, img: true, form: true, input: true, button: true, source: true, embed: true, object: true, }, attributesToScan: { href: true, src: true, action: true, formaction: true, data: true, onclick: true, onload: true, onerror: true, style: true, }, scanDepth: "medium", ignoreInlineContent: false, scanShadowDOM: true, ignoreHiddenElements: true, parseCss: true, detectObfuscation: true, iframeSandboxPolicy: "strict", scanXSS: true, scanCSRF: true, scanClickjacking: true, whitelistedDomains: [], blacklistedPatterns: [], onDetectionCallbacks: {}, analyzeOptions: { debug: false, checkEncoding: true, ignoreCase: true, maxEncodingLayers: 3, }, enableThreatIntelligence: false, adaptiveChunkSize: true, threatIntelligenceApiKey: // process.env.REACT_NEHONIX_DOM_PROCESSOR_PUBLIC_API_KEY || "AIzaSyAj46be9NZBVkFxD2FujgaoBD2GyrLA5z4", //public api 'REACT_NEHONIX_DOM_PROCESSOR_PUBLIC_API_KEY': AIzaSyAj46be9NZBVkFxD2FujgaoBD2GyrLA5z4 detailedLogging: true, confidenceThreshold: 0.7, urlUtils: { dynamicWhitelist: [] }, }; // Initial stats state const initialStats = { elementsScanned: 0, threatsDetected: 0, lastScanTimestamp: 0, scanDuration: 0, scanningActive: false, elementTypeStats: {}, threatsByType: {}, blockedElements: [], pendingElements: 0, avgProcessingTimePerElement: 0, totalProcessingTime: 0, }; /** * Nehonix DOM Processor Provider * Enhances NehonixShield with advanced DOM scanning capabilities */ export const NehonixDomProcessorProvider = ({ children, initialConfig = {} }) => { const shieldContext = useContext(NehonixShieldContext); const [config, setConfig] = useState({ ...defaultConfig, ...initialConfig, }); const [stats, setStats] = useState(initialStats); const processingQueue = useRef([]); const isProcessing = useRef(false); const worker = useRef(null); const throttleTimer = useRef(null); const configRef = useRef(config); useEffect(() => { configRef.current = config; }, [config]); const checkWithThreatIntelligence = async (url) => { if (!configRef.current.enableThreatIntelligence || !configRef.current.threatIntelligenceApiKey) { return false; } try { const response = await fetch(`https://safebrowsing.googleapis.com/v4/threatMatches:find?key=${configRef.current.threatIntelligenceApiKey}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ client: { clientId: "nehonix", clientVersion: "2.1.2" }, threatInfo: { threatTypes: [ "MALWARE", "SOCIAL_ENGINEERING", "UNWANTED_SOFTWARE", ], platformTypes: ["ANY_PLATFORM"], threatEntryTypes: ["URL"], threatEntries: [{ url }], }, }), }); const data = await response.json(); return !!data.matches?.length; } catch (error) { console.error("Threat intelligence check failed:", error); return false; } }; const updateConfig = (newConfig) => { setConfig((prevConfig) => { const updatedConfig = { ...prevConfig, ...newConfig, targetElements: { ...prevConfig.targetElements, ...(newConfig.targetElements || {}), }, attributesToScan: { ...prevConfig.attributesToScan, ...(newConfig.attributesToScan || {}), }, onDetectionCallbacks: { ...prevConfig.onDetectionCallbacks, ...(newConfig.onDetectionCallbacks || {}), }, urlUtils: { ...prevConfig.urlUtils, ...(newConfig.urlUtils || {}), }, }; return updatedConfig; }); }; const addToDynamicWhitelist = (url) => { setConfig((prev) => ({ ...prev, urlUtils: { ...prev.urlUtils, dynamicWhitelist: [...(prev.urlUtils?.dynamicWhitelist || []), url], }, })); }; const scanElement = async (element) => { const startTime = performance.now(); const results = []; const elementType = element.tagName.toLowerCase(); const scannedAttributes = {}; // Collect metadata for debugging const metadata = { domLocation: element.outerHTML.length > 500 ? element.outerHTML.substring(0, 500) + "..." : element.outerHTML, elementId: element.id || undefined, className: element.className || undefined, parentElement: element.parentElement ? { tagName: element.parentElement.tagName.toLowerCase(), id: element.parentElement.id || undefined, className: element.parentElement.className || undefined, } : undefined, domPath: getDomPath(element), innerTextSnippet: element.textContent && element.textContent.length > 100 ? element.textContent.substring(0, 100) + "..." : element.textContent || undefined, attributes: Array.from(element.attributes).reduce((acc, attr) => ({ ...acc, [attr.name]: attr.value, }), {}), }; try { if (!configRef.current.targetElements[elementType]) { return { element, elementType, results: [], scannedAttributes: {}, duration: performance.now() - startTime, timestamp: Date.now(), hasMaliciousContent: false, metadata, }; } if (configRef.current.ignoreHiddenElements) { const computedStyle = window.getComputedStyle(element); if (computedStyle.display === "none" || computedStyle.visibility === "hidden") { return { element, elementType, results: [], scannedAttributes: {}, duration: performance.now() - startTime, timestamp: Date.now(), hasMaliciousContent: false, metadata, }; } } for (const attrName in configRef.current.attributesToScan) { if (configRef.current.attributesToScan[attrName]) { const attrValue = element.getAttribute(attrName); if (attrValue) { if (attrName === "href" || attrName === "src") { const isWhitelisted = configRef.current.whitelistedDomains.some((domain) => attrValue.includes(domain)) || configRef.current.urlUtils?.dynamicWhitelist?.includes(attrValue); if (isWhitelisted) continue; } if ((attrName === "href" || attrName === "src") && (await checkWithThreatIntelligence(attrValue))) { const threatResult = { isMalicious: true, score: 0.9, recommendation: "Block or sanitize the malicious URL", detectedPatterns: [ { type: MaliciousPatternType.KNOWN_MALICIOUS_URL, pattern: attrValue, location: `${elementType}[${attrName}]`, severity: "high", confidence: "high", description: "URL identified as malicious by threat intelligence", matchedValue: attrValue, contextScore: 0.9, }, ], confidence: "high", }; results.push(threatResult); scannedAttributes[attrName] = threatResult; continue; } try { const norm = await NDS.asyncDecodeAnyToPlainText(attrValue); const decodedValue = norm.val(); const normalizedValue = decodedValue.normalize("NFC"); const analysisResult = await NSB.analyzeUrl(normalizedValue, configRef.current.analyzeOptions); if (configRef.current.detailedLogging && analysisResult.isMalicious) { console.log(`Threat detected in ${elementType}.${attrName}:`, { value: attrValue, decoded: normalizedValue, patterns: analysisResult.detectedPatterns, confidence: analysisResult.confidence, metadata, }); } scannedAttributes[attrName] = analysisResult; if (analysisResult.isMalicious && mapConfidenceToNumber(analysisResult.confidence) >= configRef.current.confidenceThreshold) { results.push(analysisResult); } else if (analysisResult.isMalicious) { console.warn("Low-confidence threat detected:", analysisResult); } } catch (error) { console.error(`Error scanning attribute ${attrName}:`, error); } } } } if (!configRef.current.ignoreInlineContent && elementType === "script" && !element.getAttribute("src")) { try { const scriptContent = element.textContent || ""; if (scriptContent.trim()) { const mockUrl = `http://mock.nehonix.space?script=${encodeURIComponent(scriptContent.substring(0, 2000))}`; const norm = await NDS.asyncDecodeAnyToPlainText(mockUrl); const mockUrlDecoded = norm.val(); const mockUrlNormalized = mockUrlDecoded.normalize("NFC"); const scriptAnalysis = await NSB.analyzeUrl(mockUrlNormalized, configRef.current.analyzeOptions); if (configRef.current.detailedLogging && scriptAnalysis.isMalicious) { console.log(`Threat detected in ${elementType}.inline-script:`, { content: scriptContent.substring(0, 200), patterns: scriptAnalysis.detectedPatterns, confidence: scriptAnalysis.confidence, metadata, }); } scannedAttributes["inline-script"] = scriptAnalysis; if (scriptAnalysis.isMalicious && mapConfidenceToNumber(scriptAnalysis.confidence) >= configRef.current.confidenceThreshold) { results.push(scriptAnalysis); } } } catch (error) { console.error("Error scanning inline script:", error); } } if (configRef.current.parseCss && elementType === "style") { try { const cssContent = element.textContent || ""; if (cssContent.trim()) { const mockUrl = `http://mock.nehonix.space?css=${encodeURIComponent(cssContent.substring(0, 2000))}`; const norm = await NDS.asyncDecodeAnyToPlainText(mockUrl); const mockUrlDecoded = norm.val(); const mockUrlNormalized = mockUrlDecoded.normalize("NFC"); const cssAnalysis = await NSB.analyzeUrl(mockUrlNormalized, configRef.current.analyzeOptions); if (configRef.current.detailedLogging && cssAnalysis.isMalicious) { console.log(`Threat detected in ${elementType}.inline-css:`, { content: cssContent.substring(0, 200), patterns: cssAnalysis.detectedPatterns, confidence: cssAnalysis.confidence, metadata, }); } scannedAttributes["inline-css"] = cssAnalysis; if (cssAnalysis.isMalicious && mapConfidenceToNumber(cssAnalysis.confidence) >= configRef.current.confidenceThreshold) { results.push(cssAnalysis); } } } catch (error) { console.error("Error scanning inline CSS:", error); } } if (configRef.current.scanShadowDOM && element.shadowRoot) { try { const shadowElements = Array.from(element.shadowRoot.querySelectorAll("*")); for (const shadowEl of shadowElements) { processingQueue.current.push(shadowEl); } } catch (error) { console.error("Error processing shadow DOM:", error); } } const hasMaliciousContent = results.length > 0; if (hasMaliciousContent && configRef.current.onDetectionCallbacks[elementType]) { configRef.current.onDetectionCallbacks[elementType]({ element, results, elementType, }); } return { element, elementType, results, scannedAttributes, duration: performance.now() - startTime, timestamp: Date.now(), hasMaliciousContent, metadata, }; } catch (error) { console.error(`Error scanning element ${elementType}:`, error); return { element, elementType, results: [], scannedAttributes: {}, duration: performance.now() - startTime, timestamp: Date.now(), hasMaliciousContent: false, error: error instanceof Error ? error.message : String(error), metadata, }; } }; const processElementChunk = async () => { if (!isProcessing.current || processingQueue.current.length === 0 || !configRef.current.enabled) { isProcessing.current = false; return; } const startTime = performance.now(); let chunkSize = configRef.current.chunkSize; if (configRef.current.adaptiveChunkSize) { chunkSize = Math.min(processingQueue.current.length, Math.max(10, Math.floor(100 / (document.readyState === "complete" ? 1 : 2)))); } const elementsToProcess = processingQueue.current.splice(0, chunkSize); setStats((prevStats) => ({ ...prevStats, pendingElements: processingQueue.current.length, })); try { const scanPromises = elementsToProcess.map(scanElement); const results = await Promise.all(scanPromises); setStats((prevStats) => { const newElementTypeStats = { ...prevStats.elementTypeStats }; const newThreatsByType = { ...prevStats.threatsByType }; const newBlockedElements = [...prevStats.blockedElements]; let newThreatsDetected = prevStats.threatsDetected; results.forEach((result) => { newElementTypeStats[result.elementType] = (newElementTypeStats[result.elementType] || 0) + 1; if (result.hasMaliciousContent) { newThreatsDetected++; newBlockedElements.push({ elementType: result.elementType, timestamp: result.timestamp, threatTypes: result.results .map((r) => r.detectedPatterns.map((p) => p.type)) .flatMap((x) => x), metadata: result.metadata, // Include metadata in blockedElements }); result.results.forEach((threat) => { threat.detectedPatterns.forEach((pattern) => { newThreatsByType[pattern.type] = (newThreatsByType[pattern.type] || 0) + 1; }); }); } }); const processingTime = performance.now() - startTime; const totalProcessingTime = prevStats.totalProcessingTime + processingTime; const elementsScanned = prevStats.elementsScanned + elementsToProcess.length; return { ...prevStats, elementsScanned, threatsDetected: newThreatsDetected, lastScanTimestamp: Date.now(), scanDuration: processingTime, elementTypeStats: newElementTypeStats, threatsByType: newThreatsByType, blockedElements: newBlockedElements, totalProcessingTime, avgProcessingTimePerElement: totalProcessingTime / elementsScanned, }; }); if (processingQueue.current.length > 0) { scheduleNextChunk(); } else { isProcessing.current = false; setStats((prev) => ({ ...prev, scanningActive: false })); } } catch (error) { console.error("Error processing element chunk:", error); isProcessing.current = false; setStats((prev) => ({ ...prev, scanningActive: false })); } }; const scheduleNextChunk = useCallback(() => { switch (configRef.current.processingMode) { case "idle-callback": if ("requestIdleCallback" in window) { window.requestIdleCallback(() => { processElementChunk(); }, { timeout: configRef.current.idleTimeout }); } else { setTimeout(processElementChunk, 1); } break; case "chunk": setTimeout(processElementChunk, 1); break; case "worker": if (!worker.current) { setTimeout(processElementChunk, 1); } break; } }, []); const scanDOM = useCallback(() => { if (isProcessing.current || !configRef.current.enabled) { return; } if (throttleTimer.current) { return; } throttleTimer.current = setTimeout(() => { throttleTimer.current = null; }, configRef.current.throttleInterval); processingQueue.current = []; isProcessing.current = true; setStats((prev) => ({ ...prev, scanningActive: true, pendingElements: 0, })); let selector = ""; Object.keys(configRef.current.targetElements).forEach((tag, index) => { if (configRef.current.targetElements[tag]) { selector += index > 0 ? `, ${tag}` : tag; } }); if (selector) { const elements = Array.from(document.querySelectorAll(selector)); processingQueue.current = elements; setStats((prev) => ({ ...prev, pendingElements: elements.length, })); scheduleNextChunk(); } else { isProcessing.current = false; setStats((prev) => ({ ...prev, scanningActive: false })); } }, [scheduleNextChunk]); const initializeWorker = useCallback(() => { if (configRef.current.processingMode === "worker" && typeof Worker !== "undefined") { const workerCode = ` self.onmessage = function(e) { self.postMessage({ type: 'scan-results', results: e.data.elements.map(el => ({ elementId: el.id, processed: true })) }); } `; try { const blob = new Blob([workerCode], { type: "application/javascript" }); const workerUrl = URL.createObjectURL(blob); worker.current = new Worker(workerUrl); worker.current.onmessage = (e) => { if (e.data.type === "scan-results") { console.log("Received worker results:", e.data.results); } }; worker.current.onerror = (error) => { console.error("Worker error:", error); configRef.current.processingMode = "chunk"; }; } catch (error) { console.error("Failed to initialize worker:", error); configRef.current.processingMode = "chunk"; } } }, []); const cleanupWorker = useCallback(() => { if (worker.current) { worker.current.terminate(); worker.current = null; } }, []); const resetStats = useCallback(() => { setStats(initialStats); }, []); const stopScanning = useCallback(() => { isProcessing.current = false; processingQueue.current = []; setStats((prev) => ({ ...prev, scanningActive: false, pendingElements: 0, })); }, []); const getSecurityReport = useCallback(() => { return { ...stats, config: configRef.current, timestamp: Date.now(), topThreats: Object.entries(stats.threatsByType) .sort((a, b) => b[1] - a[1]) .slice(0, 5), mostScannedElements: Object.entries(stats.elementTypeStats) .sort((a, b) => b[1] - a[1]) .slice(0, 5), scanEfficiency: { elementsPerSecond: stats.avgProcessingTimePerElement ? 1000 / stats.avgProcessingTimePerElement : 0, threatsPerElement: stats.elementsScanned ? stats.threatsDetected / stats.elementsScanned : 0, }, }; }, [stats]); useEffect(() => { if (!configRef.current.enabled) return; if (configRef.current.processingMode === "worker") { initializeWorker(); } const observer = new MutationObserver((mutations) => { let needsScan = false; for (const mutation of mutations) { if (mutation.type === "childList" && mutation.addedNodes.length > 0) { needsScan = true; break; } } if (needsScan && configRef.current.enabled) { if (!throttleTimer.current) { throttleTimer.current = setTimeout(() => { throttleTimer.current = null; scanDOM(); }, configRef.current.throttleInterval); } } }); observer.observe(document.body, { childList: true, subtree: true, }); scanDOM(); return () => { observer.disconnect(); cleanupWorker(); stopScanning(); if (throttleTimer.current) { clearTimeout(throttleTimer.current); throttleTimer.current = null; } }; }, [scanDOM, initializeWorker, cleanupWorker, stopScanning]); const contextValue = { config, updateConfig, stats, scanDOM, stopScanning, resetStats, getSecurityReport, isScanning: stats.scanningActive, addToDynamicWhitelist, }; return (_jsx(NehonixDomProcessorContext.Provider, { value: contextValue, children: children })); }; //# sourceMappingURL=REACT.NehonixDomProcessor.js.map