/**
 * Healthcare Compliance Scanner
 *
 * Detects HIPAA and 42 CFR Part 2 violations including:
 * - PHI (Protected Health Information) exposure
 * - SUD (Substance Use Disorder) data disclosure
 * - Consent validation gaps
 * - Audit trail deficiencies
 * - Re-disclosure violations
 *
 * Uses pattern-based detection for healthcare-specific code issues.
 *
 * @module scanners/healthcare
 */
import { readFile, readdir } from "fs/promises";
import { join, relative, extname } from "path";
import { logger } from "../logger.js";
/**
 * PHI identifier patterns (HIPAA 18 identifiers + SUD-specific)
 */
const PHI_PATTERNS = [
    // Direct identifiers
    {
        name: "ssn-exposure",
        pattern: /\b(SSN|social[_\-\s]?security[_\-\s]?(?:number)?)\s*[:=]\s*["']?\d{3}[-\s]?\d{2}[-\s]?\d{4}/gi,
        description: "Social Security Number found in code",
        severity: "critical",
        category: "phi-exposure",
        cweIds: ["CWE-359", "CWE-532"],
    },
    {
        name: "ssn-variable",
        pattern: /\b(?:const|let|var)\s+(?:ssn|socialSecurity|social_security)\s*=/gi,
        description: "Variable likely storing SSN without encryption",
        severity: "high",
        category: "phi-exposure",
        cweIds: ["CWE-312", "CWE-359"],
    },
    {
        name: "mrn-exposure",
        pattern: /\b(?:MRN|medical[_\-\s]?record[_\-\s]?(?:number)?|patient[_\-\s]?id)\s*[:=]\s*["']?[A-Z0-9]{4,}/gi,
        description: "Medical Record Number or Patient ID in plaintext",
        severity: "high",
        category: "phi-exposure",
        cweIds: ["CWE-359", "CWE-200"],
    },
    {
        name: "dob-exposure",
        pattern: /\b(?:DOB|date[_\-\s]?of[_\-\s]?birth|birthdate|birth[_\-\s]?date)\s*[:=]/gi,
        description: "Date of birth identifier in code",
        severity: "medium",
        category: "phi-exposure",
        cweIds: ["CWE-359"],
    },
    // SUD-specific identifiers (42 CFR Part 2 - higher sensitivity)
    {
        name: "sud-record",
        pattern: /\b(?:substance[_\-\s]?(?:use|abuse)|sud[_\-\s]?(?:record|data|treatment)|addiction[_\-\s]?(?:treatment|record))\b/gi,
        description: "SUD (Substance Use Disorder) record reference - requires 42 CFR Part 2 protection",
        severity: "critical",
        category: "sud-disclosure",
        cweIds: ["CWE-359", "CWE-200"],
    },
    {
        name: "sud-medication",
        pattern: /\b(?:methadone|buprenorphine|suboxone|naltrexone|vivitrol)[_\-\s]?(?:dose|mg|prescription|rx)?\b/gi,
        description: "SUD medication reference - protected under 42 CFR Part 2",
        severity: "critical",
        category: "sud-disclosure",
        cweIds: ["CWE-359"],
    },
    {
        name: "sud-program",
        pattern: /\b(?:otp|opioid[_\-\s]?treatment|mat[_\-\s]?program|medication[_\-\s]?assisted)\b/gi,
        description: "Opioid Treatment Program reference - 42 CFR Part 2 protected",
        severity: "high",
        category: "sud-disclosure",
        cweIds: ["CWE-359"],
    },
    // Health information
    {
        name: "diagnosis-code",
        pattern: /\b(?:ICD[_\-]?10?|diagnosis[_\-]?code|dx[_\-]?code)\s*[:=]\s*["']?[A-Z]\d{2,}/gi,
        description: "Diagnosis code (ICD-10) in plaintext",
        severity: "high",
        category: "phi-exposure",
        cweIds: ["CWE-359"],
    },
    {
        name: "treatment-info",
        pattern: /\b(?:treatment[_\-\s]?plan|medication[_\-\s]?list|prescription[_\-\s]?history)\s*[:=]/gi,
        description: "Treatment information in code",
        severity: "medium",
        category: "phi-exposure",
        cweIds: ["CWE-359"],
    },
];
/**
 * PHI in logging patterns
 */
const PHI_LOGGING_PATTERNS = [
    {
        name: "phi-in-console",
        pattern: /console\.(log|info|warn|error|debug)\s*\([^)]*\b(ssn|mrn|patient[_\-]?id|social[_\-]?security|dob|date[_\-]?of[_\-]?birth)\b/gi,
        description: "PHI identifier logged to console",
        severity: "critical",
        cweIds: ["CWE-532", "CWE-359"],
    },
    {
        name: "phi-in-logger",
        pattern: /logger\.(log|info|warn|error|debug)\s*\([^)]*\b(ssn|mrn|patient[_\-]?id|social[_\-]?security|dob|date[_\-]?of[_\-]?birth)\b/gi,
        description: "PHI identifier logged via logger",
        severity: "critical",
        cweIds: ["CWE-532", "CWE-359"],
    },
    {
        name: "phi-in-log-object",
        pattern: /\b(log|logger|console)\.[a-z]+\s*\(\s*\{[^}]*\b(ssn|mrn|patientId|socialSecurity)\b[^}]*\}/gi,
        description: "PHI in log object payload",
        severity: "critical",
        cweIds: ["CWE-532", "CWE-359"],
    },
    {
        name: "sud-in-logs",
        pattern: /\b(log|logger|console)\.[a-z]+\s*\([^)]*\b(substance[_\-]?(?:use|abuse)|sud[_\-]?(?:record|data)|addiction)\b/gi,
        description: "SUD data logged - 42 CFR Part 2 violation",
        severity: "critical",
        cweIds: ["CWE-532", "CWE-359"],
    },
];
/**
 * PHI in URL patterns
 */
const PHI_URL_PATTERNS = [
    {
        name: "phi-in-query-param",
        pattern: /\?\s*[^"']*\b(ssn|mrn|patient[_\-]?id|social[_\-]?security|dob)\s*=/gi,
        description: "PHI in URL query parameter",
        severity: "critical",
        cweIds: ["CWE-598", "CWE-359"],
    },
    {
        name: "phi-in-fetch-url",
        pattern: /fetch\s*\([^)]*\b(ssn|mrn|patient[_\-]?id)\s*=/gi,
        description: "PHI in fetch URL",
        severity: "critical",
        cweIds: ["CWE-598", "CWE-359"],
    },
    {
        name: "phi-in-path",
        pattern: /\/patients?\/\$\{[^}]*\b(ssn|mrn)\b[^}]*\}/gi,
        description: "PHI identifier in URL path template",
        severity: "high",
        cweIds: ["CWE-598", "CWE-359"],
    },
];
/**
 * Consent validation patterns
 */
const CONSENT_PATTERNS = [
    {
        name: "consent-check-commented",
        pattern: /\/\/\s*(?:TODO|FIXME|XXX):?\s*(?:add|implement|check)\s*consent/gi,
        description: "Consent check marked as TODO - not implemented",
        severity: "high",
        category: "consent-missing",
        cweIds: ["CWE-862"],
    },
    {
        name: "consent-bypass-flag",
        pattern: /\b(?:skip[_\-]?consent|bypass[_\-]?consent|ignore[_\-]?consent|consent[_\-]?override)\s*[:=]\s*true/gi,
        description: "Consent check explicitly bypassed",
        severity: "critical",
        category: "consent-bypass",
        cweIds: ["CWE-862"],
    },
    {
        name: "consent-always-true",
        pattern: /(?:has[_\-]?consent|consent[_\-]?given|consent[_\-]?valid)\s*[:=]\s*true\s*[;,]/gi,
        description: "Consent hardcoded to true - bypasses validation",
        severity: "critical",
        category: "consent-bypass",
        cweIds: ["CWE-862"],
    },
];
/**
 * Audit logging gap patterns
 */
const AUDIT_GAP_PATTERNS = [
    {
        name: "phi-access-no-audit",
        pattern: /function\s+(?:get|read|fetch|access)[A-Z]?[a-zA-Z]*(?:Patient|PHI|Medical|Health|SUD)\s*\([^)]*\)\s*(?::\s*[^{]+)?\s*\{(?:(?!audit|log).)*\}/gis,
        description: "PHI access function without audit logging",
        severity: "high",
        cweIds: ["CWE-778"],
    },
    {
        name: "audit-log-deletable",
        pattern: /\b(?:delete|truncate|drop|remove)[A-Z]?[a-zA-Z]*(?:audit|log)/gi,
        description: "Audit logs can be deleted - violates retention requirements",
        severity: "critical",
        cweIds: ["CWE-778", "CWE-779"],
    },
    {
        name: "audit-missing-timestamp",
        pattern: /audit[_\-]?(?:log|entry|record)\s*\(\s*\{(?:(?!timestamp|created[_\-]?at|time).)*\}/gi,
        description: "Audit log entry missing timestamp",
        severity: "medium",
        cweIds: ["CWE-778"],
    },
    {
        name: "audit-missing-user",
        pattern: /audit[_\-]?(?:log|entry|record)\s*\(\s*\{(?:(?!user|actor|principal|subject).)*\}/gi,
        description: "Audit log entry missing user identification",
        severity: "medium",
        cweIds: ["CWE-778"],
    },
];
/**
 * Third-party/QSOA violation patterns
 */
const QSOA_PATTERNS = [
    {
        name: "third-party-phi-send",
        pattern: /\b(?:sendTo|postTo|shareTo|forward(?:To)?)[A-Z]?[a-zA-Z]*(?:Vendor|ThirdParty|External|Partner)\s*\([^)]*\b(?:patient|phi|medical|health|sud)\b/gi,
        description: "PHI sent to third party - requires QSOA/BAA",
        severity: "high",
        cweIds: ["CWE-359"],
    },
    {
        name: "external-api-phi",
        pattern: /axios|fetch|http\.(?:post|put|patch)\s*\([^)]*(?:external|vendor|partner|third[_\-]?party)[^)]*\b(?:patient|phi|ssn|mrn)\b/gi,
        description: "PHI transmitted to external API - verify QSOA exists",
        severity: "high",
        cweIds: ["CWE-359", "CWE-319"],
    },
];
/**
 * Files to skip when scanning
 */
const SKIP_PATTERNS = [
    /node_modules/,
    /\.git\//,
    /dist\//,
    /build\//,
    /coverage\//,
    /\.lock$/,
    /\.snap$/,
    /\.min\./,
    /vendor\//,
    /\.d\.ts$/,
    /test[s]?\//,
    /__tests__\//,
    /\.test\./,
    /\.spec\./,
];
/**
 * File extensions to scan
 */
const SCAN_EXTENSIONS = new Set([
    ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
    ".py", ".rb", ".java", ".cs", ".go",
    ".vue", ".svelte",
]);
/**
 * Run the healthcare compliance scanner
 */
export async function runHealthcareScanner(projectPath) {
    const startTime = Date.now();
    const findings = [];
    let filesScanned = 0;
    logger.info("healthcare.scan.start", { projectPath });
    try {
        const files = await collectFiles(projectPath);
        for (const file of files) {
            try {
                const content = await readFile(file, "utf-8");
                const relativePath = relative(projectPath, file);
                filesScanned++;
                // Scan for PHI exposure
                for (const pattern of PHI_PATTERNS) {
                    const matches = findMatches(content, pattern.pattern, relativePath);
                    for (const match of matches) {
                        findings.push({
                            scanner: "healthcare",
                            ruleId: `healthcare:${pattern.name}`,
                            file: relativePath,
                            line: match.line,
                            message: pattern.description,
                            severity: pattern.severity,
                            confidence: 90,
                            category: pattern.category,
                            cweIds: pattern.cweIds,
                            evidence: match.evidence,
                        });
                    }
                }
                // Scan for PHI in logs
                for (const pattern of PHI_LOGGING_PATTERNS) {
                    const matches = findMatches(content, pattern.pattern, relativePath);
                    for (const match of matches) {
                        findings.push({
                            scanner: "healthcare",
                            ruleId: `healthcare:${pattern.name}`,
                            file: relativePath,
                            line: match.line,
                            message: pattern.description,
                            severity: pattern.severity,
                            confidence: 95,
                            category: "phi-in-logs",
                            cweIds: pattern.cweIds,
                            evidence: match.evidence,
                        });
                    }
                }
                // Scan for PHI in URLs
                for (const pattern of PHI_URL_PATTERNS) {
                    const matches = findMatches(content, pattern.pattern, relativePath);
                    for (const match of matches) {
                        findings.push({
                            scanner: "healthcare",
                            ruleId: `healthcare:${pattern.name}`,
                            file: relativePath,
                            line: match.line,
                            message: pattern.description,
                            severity: pattern.severity,
                            confidence: 90,
                            category: "phi-in-url",
                            cweIds: pattern.cweIds,
                            evidence: match.evidence,
                        });
                    }
                }
                // Scan for consent issues
                for (const pattern of CONSENT_PATTERNS) {
                    const matches = findMatches(content, pattern.pattern, relativePath);
                    for (const match of matches) {
                        findings.push({
                            scanner: "healthcare",
                            ruleId: `healthcare:${pattern.name}`,
                            file: relativePath,
                            line: match.line,
                            message: pattern.description,
                            severity: pattern.severity,
                            confidence: 85,
                            category: pattern.category,
                            cweIds: pattern.cweIds,
                            evidence: match.evidence,
                        });
                    }
                }
                // Scan for audit gaps
                for (const pattern of AUDIT_GAP_PATTERNS) {
                    const matches = findMatches(content, pattern.pattern, relativePath);
                    for (const match of matches) {
                        findings.push({
                            scanner: "healthcare",
                            ruleId: `healthcare:${pattern.name}`,
                            file: relativePath,
                            line: match.line,
                            message: pattern.description,
                            severity: pattern.severity,
                            confidence: 75,
                            category: "audit-gap",
                            cweIds: pattern.cweIds,
                            evidence: match.evidence,
                        });
                    }
                }
                // Scan for QSOA/third-party violations
                for (const pattern of QSOA_PATTERNS) {
                    const matches = findMatches(content, pattern.pattern, relativePath);
                    for (const match of matches) {
                        findings.push({
                            scanner: "healthcare",
                            ruleId: `healthcare:${pattern.name}`,
                            file: relativePath,
                            line: match.line,
                            message: pattern.description,
                            severity: pattern.severity,
                            confidence: 80,
                            category: "qsoa-violation",
                            cweIds: pattern.cweIds,
                            evidence: match.evidence,
                        });
                    }
                }
            }
            catch (err) {
                logger.warn("healthcare.scan.file.error", {
                    file,
                    error: err instanceof Error ? err.message : String(err),
                });
            }
        }
        const duration = Date.now() - startTime;
        logger.info("healthcare.scan.complete", {
            filesScanned,
            findings: findings.length,
            duration,
        });
        return {
            scanner: "healthcare",
            findings,
            duration,
            success: true,
            filesScanned,
            rulesUsed: [
                ...PHI_PATTERNS.map((p) => p.name),
                ...PHI_LOGGING_PATTERNS.map((p) => p.name),
                ...PHI_URL_PATTERNS.map((p) => p.name),
                ...CONSENT_PATTERNS.map((p) => p.name),
                ...AUDIT_GAP_PATTERNS.map((p) => p.name),
                ...QSOA_PATTERNS.map((p) => p.name),
            ],
        };
    }
    catch (err) {
        const duration = Date.now() - startTime;
        const errorMessage = err instanceof Error ? err.message : String(err);
        logger.error("healthcare.scan.failed", { error: errorMessage, duration });
        return {
            scanner: "healthcare",
            findings: [],
            duration,
            success: false,
            error: errorMessage,
        };
    }
}
/**
 * Collect files to scan
 */
async function collectFiles(dirPath) {
    const files = [];
    async function walk(currentPath) {
        const entries = await readdir(currentPath, { withFileTypes: true });
        for (const entry of entries) {
            const fullPath = join(currentPath, entry.name);
            const relativePath = relative(dirPath, fullPath);
            // Skip excluded patterns
            if (SKIP_PATTERNS.some((p) => p.test(relativePath))) {
                continue;
            }
            if (entry.isDirectory()) {
                await walk(fullPath);
            }
            else if (entry.isFile()) {
                const ext = extname(entry.name);
                if (SCAN_EXTENSIONS.has(ext)) {
                    files.push(fullPath);
                }
            }
        }
    }
    await walk(dirPath);
    return files;
}
/**
 * Find pattern matches with line numbers
 */
function findMatches(content, pattern, _filePath) {
    const matches = [];
    const lines = content.split("\n");
    // Reset regex lastIndex for global patterns
    pattern.lastIndex = 0;
    let match;
    while ((match = pattern.exec(content)) !== null) {
        // Find line number
        const beforeMatch = content.slice(0, match.index);
        const lineNumber = beforeMatch.split("\n").length;
        // Get context (the line containing the match)
        const lineContent = lines[lineNumber - 1] || "";
        const evidence = lineContent.trim().slice(0, 200);
        matches.push({
            line: lineNumber,
            evidence,
        });
        // Prevent infinite loop for zero-width matches
        if (match[0].length === 0) {
            pattern.lastIndex++;
        }
    }
    return matches;
}
/**
 * Check if healthcare scanner is relevant for a project
 * (looks for healthcare-related code patterns)
 */
export async function isHealthcareProject(projectPath) {
    try {
        const files = await collectFiles(projectPath);
        // Sample up to 50 files
        const sampleFiles = files.slice(0, 50);
        for (const file of sampleFiles) {
            try {
                const content = await readFile(file, "utf-8");
                // Check for healthcare-related keywords
                const healthcareKeywords = [
                    /\bpatient\b/i,
                    /\bHIPAA\b/i,
                    /\bPHI\b/,
                    /\bePHI\b/i,
                    /\bhealthcare\b/i,
                    /\bmedical\b/i,
                    /\bEHR\b/,
                    /\bEMR\b/,
                    /\b42\s*CFR\b/i,
                    /\bsubstance\s*use\b/i,
                    /\bSUD\b/,
                ];
                if (healthcareKeywords.some((kw) => kw.test(content))) {
                    return true;
                }
            }
            catch {
                // Skip unreadable files
            }
        }
        return false;
    }
    catch {
        return false;
    }
}
//# sourceMappingURL=healthcare.js.map