UNPKG

@iota-big3/sdk-security

Version:

Advanced security features including zero trust, quantum-safe crypto, and ML threat detection

843 lines 36.4 kB
"use strict"; /** * Web Application Scanner * OWASP Top 10 vulnerability detection and web security testing */ Object.defineProperty(exports, "__esModule", { value: true }); exports.WebScanner = void 0; const tslib_1 = require("tslib"); const crypto = tslib_1.__importStar(require("crypto")); const events_1 = require("events"); const types_1 = require("../types"); class WebScanner extends events_1.EventEmitter { constructor() { super(); this.userAgent = 'IOTA-Security-Scanner/1.0'; this.xssPayloads = [ '<script>alert(1)</script>', '"><script>alert(1)</script>', '<img src=x onerror=alert(1)>', 'javascript:alert(1)', '<svg onload=alert(1)>' ]; this.sqlPayloads = [ "' OR '1'='1", "1' OR '1'='1'--", "' UNION SELECT NULL--", "1; DROP TABLE users--", "' OR 1=1#" ]; this.pathTraversalPayloads = [ '../../../etc/passwd', '..\\..\\..\\windows\\win.ini', '....//....//....//etc/passwd', '%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd' ]; } /** * Scan web application */ async scanWebApp(url, options = {}) { const startTime = new Date(); const target = { id: crypto.randomUUID(), type: 'APPLICATION', address: url }; this.emit('web-scan:started', { url }); try { // Crawl the application const crawlResult = await this.crawlApplication(url, options); // Check for vulnerabilities const vulnerabilities = []; // Check security headers if (options.checkHeaders !== false) { const headerVulns = this.checkSecurityHeaders(url, crawlResult.headers); vulnerabilities.push(...headerVulns); } // Check cookies if (options.checkCookies !== false) { const cookieVulns = this.checkCookieSecurity(crawlResult.cookies); vulnerabilities.push(...cookieVulns); } // Test for XSS const xssVulns = await this.testXSS(crawlResult, target); vulnerabilities.push(...xssVulns); // Test for SQL injection const sqlVulns = await this.testSQLInjection(crawlResult, target); vulnerabilities.push(...sqlVulns); // Test for path traversal const pathVulns = await this.testPathTraversal(crawlResult.urls, target); vulnerabilities.push(...pathVulns); // Test for insecure direct object references const idorVulns = await this.testIDOR(crawlResult.urls, target); vulnerabilities.push(...idorVulns); // Test for CSRF const csrfVulns = this.testCSRF(crawlResult.forms, target); vulnerabilities.push(...csrfVulns); // Test for XXE const xxeVulns = await this.testXXE(crawlResult.forms, target); vulnerabilities.push(...xxeVulns); // Test authentication const authVulns = await this.testAuthentication(url, target); vulnerabilities.push(...authVulns); const result = { target, startTime, endTime: new Date(), vulnerabilities, crawledUrls: Array.from(crawlResult.urls), forms: crawlResult.forms, cookies: crawlResult.cookies, headers: Object.fromEntries(crawlResult.headers) }; this.emit('web-scan:completed', { url, vulnerabilities: vulnerabilities.length }); return result; } catch (error) { this.emit('web-scan:failed', { url, error }); throw error; } } /** * Private methods */ async crawlApplication(baseUrl, options) { const visited = new Set(); const toVisit = [baseUrl]; const forms = []; const cookies = []; const headers = new Map(); const maxUrls = options.maxUrls || 100; const depth = options.depth || 3; while (toVisit.length > 0 && visited.size < maxUrls) { const url = toVisit.shift(); if (visited.has(url)) continue; visited.add(url); this.emit('crawl:url', { url }); // Mock HTTP request const response = await this.mockHttpRequest(url, 'GET', undefined, options.authCookie); // Extract headers if (visited.size === 1) { Object.entries(response.headers).forEach(([k, v]) => headers.set(k, v)); } // Extract cookies response.cookies.forEach(c => { if (!cookies.find(existing => existing.name === c.name)) { cookies.push(c); } }); // Extract forms forms.push(...response.forms); // Extract links const links = this.extractLinks(response.body, url); const currentDepth = url.split('/').length - 3; links.forEach(link => { const linkDepth = link.split('/').length - 3; if (!visited.has(link) && linkDepth <= currentDepth + depth) { toVisit.push(link); } }); } return { urls: visited, forms, cookies, headers }; } checkSecurityHeaders(url, headers) { const vulnerabilities = []; const baseTarget = { id: '', type: 'APPLICATION', address: url }; // Check for missing security headers const requiredHeaders = [ { name: 'X-Frame-Options', recommended: 'DENY', vulnerability: 'Clickjacking' }, { name: 'X-Content-Type-Options', recommended: 'nosniff', vulnerability: 'MIME Sniffing' }, { name: 'X-XSS-Protection', recommended: '1; mode=block', vulnerability: 'XSS' }, { name: 'Strict-Transport-Security', recommended: 'max-age=31536000', vulnerability: 'HTTPS Downgrade' }, { name: 'Content-Security-Policy', recommended: 'present', vulnerability: 'XSS/Data Injection' } ]; requiredHeaders.forEach(({ name, recommended, vulnerability }) => { const value = headers.get(name.toLowerCase()); if (!value) { vulnerabilities.push({ id: crypto.randomUUID(), title: `Missing Security Header: ${name}`, description: `The ${name} header is not set, which may allow ${vulnerability} attacks`, severity: types_1.VulnerabilitySeverity.MEDIUM, category: types_1.VulnerabilityCategory.SECURITY_MISCONFIGURATION, discoveredAt: new Date(), discoveredBy: 'WebScanner', testType: 'WEB_APPLICATION', affectedTarget: baseTarget, affectedComponent: 'HTTP Headers', attackVector: types_1.AttackVector.NETWORK, exploitComplexity: 'LOW', privilegesRequired: 'NONE', userInteraction: vulnerability === 'Clickjacking' ? 'REQUIRED' : 'NONE', evidence: [{ type: types_1.EvidenceType.RESPONSE, data: 'Header not present in response', timestamp: new Date() }], exploitStatus: types_1.ExploitStatus.NOT_ATTEMPTED, impact: { confidentiality: types_1.ImpactLevel.LOW, integrity: types_1.ImpactLevel.LOW, availability: types_1.ImpactLevel.NONE }, remediation: { summary: `Add ${name} header`, steps: [`Set ${name}: ${recommended}`], effort: 'LOW', priority: 'MEDIUM', retestRequired: true }, riskScore: 5, likelihood: 'MEDIUM' }); } }); return vulnerabilities; } checkCookieSecurity(cookies) { const vulnerabilities = []; cookies.forEach(cookie => { const issues = []; if (!cookie.secure && cookie.name.toLowerCase().includes('session')) { issues.push('Missing Secure flag on session cookie'); } if (!cookie.httpOnly && cookie.name.toLowerCase().includes('session')) { issues.push('Missing HttpOnly flag on session cookie'); } if (!cookie.sameSite) { issues.push('Missing SameSite attribute'); } if (issues.length > 0) { vulnerabilities.push({ id: crypto.randomUUID(), title: `Insecure Cookie: ${cookie.name}`, description: issues.join(', '), severity: types_1.VulnerabilitySeverity.MEDIUM, category: types_1.VulnerabilityCategory.SECURITY_MISCONFIGURATION, discoveredAt: new Date(), discoveredBy: 'WebScanner', testType: 'WEB_APPLICATION', affectedTarget: { id: '', type: 'APPLICATION', address: cookie.domain }, affectedComponent: `Cookie: ${cookie.name}`, attackVector: types_1.AttackVector.NETWORK, exploitComplexity: 'LOW', privilegesRequired: 'NONE', userInteraction: 'NONE', evidence: [{ type: types_1.EvidenceType.RESPONSE, data: `Cookie attributes: Secure=${cookie.secure}, HttpOnly=${cookie.httpOnly}, SameSite=${cookie.sameSite}`, timestamp: new Date() }], exploitStatus: types_1.ExploitStatus.NOT_ATTEMPTED, impact: { confidentiality: types_1.ImpactLevel.HIGH, integrity: types_1.ImpactLevel.LOW, availability: types_1.ImpactLevel.NONE }, remediation: { summary: 'Secure cookie attributes', steps: [ 'Set Secure flag for HTTPS transmission only', 'Set HttpOnly flag to prevent JavaScript access', 'Set SameSite attribute to prevent CSRF' ], effort: 'LOW', priority: 'HIGH', retestRequired: true }, riskScore: 6, likelihood: 'MEDIUM' }); } }); return vulnerabilities; } async testXSS(crawlResult, target) { const vulnerabilities = []; // Test forms for XSS for (const form of crawlResult.forms) { for (const input of form.inputs) { if (input.type === 'text' || input.type === 'textarea') { for (const payload of this.xssPayloads) { const response = await this.submitForm(form, { [input.name]: payload }); if (response.body.includes(payload)) { vulnerabilities.push(this.createXSSVulnerability(form.action, input.name, payload, target)); break; // One vulnerability per input is enough } } } } } // Test URL parameters for (const url of crawlResult.urls) { if (url.includes('?')) { const [base, query] = url.split('?'); const params = new URLSearchParams(query); for (const [param] of params) { for (const payload of this.xssPayloads) { params.set(param, payload); const testUrl = `${base}?${params.toString()}`; const response = await this.mockHttpRequest(testUrl, 'GET'); if (response.body.includes(payload)) { vulnerabilities.push(this.createXSSVulnerability(url, param, payload, target)); break; } } } } } return vulnerabilities; } createXSSVulnerability(url, parameter, payload, target) { return { id: crypto.randomUUID(), title: `Cross-Site Scripting (XSS) in ${parameter}`, description: `The parameter '${parameter}' is vulnerable to XSS attacks. User input is not properly sanitized.`, severity: types_1.VulnerabilitySeverity.HIGH, category: types_1.VulnerabilityCategory.XSS, cwe: 'CWE-79', discoveredAt: new Date(), discoveredBy: 'WebScanner', testType: 'WEB_APPLICATION', affectedTarget: target, affectedComponent: url, attackVector: types_1.AttackVector.NETWORK, exploitComplexity: 'LOW', privilegesRequired: 'NONE', userInteraction: 'REQUIRED', evidence: [ { type: types_1.EvidenceType.REQUEST, data: `Parameter: ${parameter}\nPayload: ${payload}`, timestamp: new Date() }, { type: types_1.EvidenceType.RESPONSE, data: 'Payload reflected in response without encoding', timestamp: new Date() } ], exploitStatus: types_1.ExploitStatus.SUCCESSFUL, exploitDetails: { exploitCode: payload, exploitSteps: [ `Send payload: ${payload}`, 'Payload executed in victim\'s browser' ], toolsUsed: ['Manual injection'], timeToExploit: 5, reliability: 'HIGH' }, impact: { confidentiality: types_1.ImpactLevel.HIGH, integrity: types_1.ImpactLevel.HIGH, availability: types_1.ImpactLevel.NONE, businessImpact: { reputation: 'User trust compromised', regulatory: 'Potential data breach' } }, remediation: { summary: 'Implement proper input validation and output encoding', steps: [ 'Validate all user input on the server side', 'Encode output based on context (HTML, JavaScript, CSS)', 'Use Content Security Policy headers', 'Consider using a Web Application Firewall' ], effort: 'MEDIUM', priority: 'IMMEDIATE', references: [ 'https://owasp.org/www-community/attacks/xss/', 'https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html' ], retestRequired: true }, riskScore: 8, likelihood: 'HIGH' }; } async testSQLInjection(crawlResult, target) { const vulnerabilities = []; // Test forms for (const form of crawlResult.forms) { for (const input of form.inputs) { for (const payload of this.sqlPayloads) { const response = await this.submitForm(form, { [input.name]: payload }); if (this.detectSQLError(response.body)) { vulnerabilities.push(this.createSQLInjectionVulnerability(form.action, input.name, payload, response.body, target)); break; } } } } return vulnerabilities; } detectSQLError(body) { const errorPatterns = [ /SQL syntax.*MySQL/i, /Warning.*mysql_/i, /MySqlException/i, /valid PostgreSQL result/i, /PostgreSQL.*ERROR/i, /ORA-\d{5}/, /Microsoft.*ODBC.*SQL/i, /SQLException/i, /SQL Server.*Error/i ]; return errorPatterns.some(pattern => pattern.test(body)); } createSQLInjectionVulnerability(url, parameter, payload, errorMessage, target) { return { id: crypto.randomUUID(), title: `SQL Injection in ${parameter}`, description: `The parameter '${parameter}' is vulnerable to SQL injection. Database errors are exposed.`, severity: types_1.VulnerabilitySeverity.CRITICAL, category: types_1.VulnerabilityCategory.INJECTION, cwe: 'CWE-89', discoveredAt: new Date(), discoveredBy: 'WebScanner', testType: 'WEB_APPLICATION', affectedTarget: target, affectedComponent: url, attackVector: types_1.AttackVector.NETWORK, exploitComplexity: 'LOW', privilegesRequired: 'NONE', userInteraction: 'NONE', evidence: [ { type: types_1.EvidenceType.REQUEST, data: `Parameter: ${parameter}\nPayload: ${payload}`, timestamp: new Date() }, { type: types_1.EvidenceType.RESPONSE, data: errorMessage.substring(0, 200) + '...', timestamp: new Date() } ], exploitStatus: types_1.ExploitStatus.SUCCESSFUL, impact: { confidentiality: types_1.ImpactLevel.CRITICAL, integrity: types_1.ImpactLevel.CRITICAL, availability: types_1.ImpactLevel.HIGH, businessImpact: { financial: 'Complete database compromise possible', regulatory: 'Major data breach risk' } }, remediation: { summary: 'Use parameterized queries', steps: [ 'Use prepared statements with parameterized queries', 'Validate and sanitize all user input', 'Apply least privilege to database accounts', 'Disable error reporting in production' ], effort: 'HIGH', priority: 'IMMEDIATE', references: [ 'https://owasp.org/www-community/attacks/SQL_Injection' ], retestRequired: true }, riskScore: 10, likelihood: 'CRITICAL' }; } async testPathTraversal(urls, target) { const vulnerabilities = []; for (const url of urls) { // Look for file parameters if (url.includes('file=') || url.includes('path=') || url.includes('doc=')) { for (const payload of this.pathTraversalPayloads) { const testUrl = url.replace(/([?&](file|path|doc)=)[^&]*/, `$1${payload}`); const response = await this.mockHttpRequest(testUrl, 'GET'); if (this.detectPathTraversalSuccess(response.body)) { vulnerabilities.push(this.createPathTraversalVulnerability(url, payload, target)); break; } } } } return vulnerabilities; } detectPathTraversalSuccess(body) { const successPatterns = [ /root:.*:0:0/, // Unix passwd file /\[boot loader\]/i, // Windows ini file /\[fonts\]/i // Windows ini file ]; return successPatterns.some(pattern => pattern.test(body)); } createPathTraversalVulnerability(url, payload, target) { return { id: crypto.randomUUID(), title: 'Path Traversal Vulnerability', description: 'The application is vulnerable to path traversal attacks, allowing access to files outside the web root.', severity: types_1.VulnerabilitySeverity.HIGH, category: types_1.VulnerabilityCategory.BROKEN_ACCESS_CONTROL, cwe: 'CWE-22', discoveredAt: new Date(), discoveredBy: 'WebScanner', testType: 'WEB_APPLICATION', affectedTarget: target, affectedComponent: url, attackVector: types_1.AttackVector.NETWORK, exploitComplexity: 'LOW', privilegesRequired: 'NONE', userInteraction: 'NONE', evidence: [ { type: types_1.EvidenceType.REQUEST, data: `Payload: ${payload}`, timestamp: new Date() }, { type: types_1.EvidenceType.RESPONSE, data: 'System file contents exposed', timestamp: new Date() } ], exploitStatus: types_1.ExploitStatus.SUCCESSFUL, impact: { confidentiality: types_1.ImpactLevel.HIGH, integrity: types_1.ImpactLevel.NONE, availability: types_1.ImpactLevel.NONE }, remediation: { summary: 'Implement proper file access controls', steps: [ 'Use a whitelist of allowed files', 'Validate file paths and reject traversal sequences', 'Use chroot jail or similar sandboxing', 'Run web server with minimal privileges' ], effort: 'MEDIUM', priority: 'IMMEDIATE', retestRequired: true }, riskScore: 8, likelihood: 'HIGH' }; } async testIDOR(urls, target) { const vulnerabilities = []; // Look for numeric IDs in URLs const idPattern = /[?&](id|user|account|order)=(\d+)/; for (const url of urls) { const match = url.match(idPattern); if (match) { const param = match[1]; const originalId = match[2]; const testId = String(parseInt(originalId) + 1); const testUrl = url.replace(`${param}=${originalId}`, `${param}=${testId}`); const response = await this.mockHttpRequest(testUrl, 'GET'); // Simple check - if we get data back, might be IDOR if (response.statusCode === 200 && response.body.length > 100) { vulnerabilities.push({ id: crypto.randomUUID(), title: `Insecure Direct Object Reference in ${param}`, description: 'The application exposes internal object references without proper authorization checks.', severity: types_1.VulnerabilitySeverity.HIGH, category: types_1.VulnerabilityCategory.BROKEN_ACCESS_CONTROL, cwe: 'CWE-639', discoveredAt: new Date(), discoveredBy: 'WebScanner', testType: 'WEB_APPLICATION', affectedTarget: target, affectedComponent: url, attackVector: types_1.AttackVector.NETWORK, exploitComplexity: 'LOW', privilegesRequired: 'LOW', userInteraction: 'NONE', evidence: [{ type: types_1.EvidenceType.REQUEST, data: `Changed ${param} from ${originalId} to ${testId}`, timestamp: new Date() }], exploitStatus: types_1.ExploitStatus.SUCCESSFUL, impact: { confidentiality: types_1.ImpactLevel.HIGH, integrity: types_1.ImpactLevel.LOW, availability: types_1.ImpactLevel.NONE }, remediation: { summary: 'Implement proper access controls', steps: [ 'Check user authorization for each object access', 'Use indirect object references (UUIDs)', 'Implement session-based access controls' ], effort: 'MEDIUM', priority: 'HIGH', retestRequired: true }, riskScore: 7, likelihood: 'HIGH' }); } } } return vulnerabilities; } testCSRF(forms, target) { const vulnerabilities = []; forms.forEach(form => { const hasToken = form.inputs.some(input => input.name.toLowerCase().includes('csrf') || input.name.toLowerCase().includes('token')); if (!hasToken && form.method.toUpperCase() === 'POST') { vulnerabilities.push({ id: crypto.randomUUID(), title: `Missing CSRF Protection on ${form.action}`, description: 'The form does not include CSRF tokens, making it vulnerable to cross-site request forgery.', severity: types_1.VulnerabilitySeverity.MEDIUM, category: types_1.VulnerabilityCategory.CSRF, cwe: 'CWE-352', discoveredAt: new Date(), discoveredBy: 'WebScanner', testType: 'WEB_APPLICATION', affectedTarget: target, affectedComponent: form.action, attackVector: types_1.AttackVector.NETWORK, exploitComplexity: 'LOW', privilegesRequired: 'NONE', userInteraction: 'REQUIRED', evidence: [{ type: types_1.EvidenceType.CODE, data: 'No CSRF token found in form', timestamp: new Date() }], exploitStatus: types_1.ExploitStatus.NOT_ATTEMPTED, impact: { confidentiality: types_1.ImpactLevel.NONE, integrity: types_1.ImpactLevel.HIGH, availability: types_1.ImpactLevel.NONE }, remediation: { summary: 'Implement CSRF tokens', steps: [ 'Generate unique CSRF tokens per session', 'Include token in all state-changing forms', 'Validate token on server side', 'Consider using SameSite cookies' ], effort: 'LOW', priority: 'MEDIUM', retestRequired: true }, riskScore: 5, likelihood: 'MEDIUM' }); } }); return vulnerabilities; } async testXXE(forms, target) { const vulnerabilities = []; // XXE payload const xxePayload = `<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> <data>&xxe;</data>`; for (const form of forms) { // Look for file upload or XML input const hasFileInput = form.inputs.some(i => i.type === 'file'); const hasXMLEndpoint = form.action.includes('xml') || form.action.includes('upload'); if (hasFileInput || hasXMLEndpoint) { const response = await this.submitForm(form, {}, xxePayload); if (this.detectPathTraversalSuccess(response.body)) { vulnerabilities.push({ id: crypto.randomUUID(), title: 'XML External Entity (XXE) Injection', description: 'The application processes XML input without disabling external entity resolution.', severity: types_1.VulnerabilitySeverity.HIGH, category: types_1.VulnerabilityCategory.XXE, cwe: 'CWE-611', discoveredAt: new Date(), discoveredBy: 'WebScanner', testType: 'WEB_APPLICATION', affectedTarget: target, affectedComponent: form.action, attackVector: types_1.AttackVector.NETWORK, exploitComplexity: 'LOW', privilegesRequired: 'NONE', userInteraction: 'NONE', evidence: [{ type: types_1.EvidenceType.REQUEST, data: xxePayload, timestamp: new Date() }], exploitStatus: types_1.ExploitStatus.SUCCESSFUL, impact: { confidentiality: types_1.ImpactLevel.HIGH, integrity: types_1.ImpactLevel.NONE, availability: types_1.ImpactLevel.LOW }, remediation: { summary: 'Disable XML external entity processing', steps: [ 'Disable DTD processing entirely', 'Disable external entity resolution', 'Use less complex data formats (JSON)', 'Validate and sanitize XML input' ], effort: 'LOW', priority: 'HIGH', retestRequired: true }, riskScore: 7, likelihood: 'MEDIUM' }); break; } } } return vulnerabilities; } async testAuthentication(url, target) { const vulnerabilities = []; // Test for common weak credentials const weakCredentials = [ { username: 'admin', password: 'admin' }, { username: 'admin', password: 'password' }, { username: 'admin', password: '123456' }, { username: 'test', password: 'test' } ]; // Mock authentication test for (const creds of weakCredentials) { const response = await this.mockHttpRequest(`${url}/login`, 'POST', creds); if (response.statusCode === 200 || response.statusCode === 302) { vulnerabilities.push({ id: crypto.randomUUID(), title: 'Weak Default Credentials', description: `The application accepts weak credentials: ${creds.username}/${creds.password}`, severity: types_1.VulnerabilitySeverity.CRITICAL, category: types_1.VulnerabilityCategory.BROKEN_AUTHENTICATION, cwe: 'CWE-798', discoveredAt: new Date(), discoveredBy: 'WebScanner', testType: 'WEB_APPLICATION', affectedTarget: target, affectedComponent: `${url}/login`, attackVector: types_1.AttackVector.NETWORK, exploitComplexity: 'LOW', privilegesRequired: 'NONE', userInteraction: 'NONE', evidence: [{ type: types_1.EvidenceType.REQUEST, data: `Credentials: ${creds.username}/${creds.password}`, timestamp: new Date() }], exploitStatus: types_1.ExploitStatus.SUCCESSFUL, impact: { confidentiality: types_1.ImpactLevel.CRITICAL, integrity: types_1.ImpactLevel.CRITICAL, availability: types_1.ImpactLevel.HIGH }, remediation: { summary: 'Enforce strong password policy', steps: [ 'Remove all default credentials', 'Enforce strong password requirements', 'Implement account lockout policies', 'Use multi-factor authentication' ], effort: 'MEDIUM', priority: 'IMMEDIATE', retestRequired: true }, riskScore: 10, likelihood: 'CRITICAL' }); break; } } return vulnerabilities; } // Helper methods extractLinks(html, baseUrl) { const links = []; const linkRegex = /href="([^"]+)"/g; let match; while ((match = linkRegex.exec(html)) !== null) { const link = match[1]; if (!link.startsWith('http')) { // Relative link const url = new URL(baseUrl); links.push(url.origin + link); } else if (link.startsWith(new URL(baseUrl).origin)) { // Same origin links.push(link); } } return links; } async submitForm(form, data, rawBody) { return this.mockHttpRequest(form.action, form.method, data, undefined, rawBody); } async mockHttpRequest(url, method, data, authCookie, rawBody) { // Mock implementation const mockForms = []; // Add some forms for certain URLs if (url.includes('contact') || url.includes('login')) { mockForms.push({ action: url, method: 'POST', inputs: [ { name: 'username', type: 'text' }, { name: 'password', type: 'password' }, { name: 'submit', type: 'submit' } ] }); } return { statusCode: 200, headers: { 'content-type': 'text/html', 'server': 'Apache/2.4.41' }, body: rawBody || '<html><body>Mock response</body></html>', cookies: [ { name: 'sessionid', value: 'abc123', domain: new URL(url).hostname, path: '/', secure: false, httpOnly: false } ], forms: mockForms }; } } exports.WebScanner = WebScanner; //# sourceMappingURL=web-scanner.js.map