/**
 * Agent Privacy Analyzer
 *
 * Analyzes tool traces and agent interactions for privacy issues.
 * Detects PII egress, cross-tenant data bleed, memory/caching of
 * sensitive data, and consent compliance issues.
 *
 * @module agents/agent-privacy
 */
import { readFile, readdir } from "fs/promises";
import { join } from "path";
const PII_PATTERNS = [
    // Email
    {
        type: "email",
        pattern: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
        severity: "medium",
        gdprRelevant: true,
        ccpaRelevant: true,
        validate: (match) => !match.includes("example.com") && !match.includes("test.com"),
    },
    // Phone (various formats)
    {
        type: "phone",
        pattern: /(?:\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g,
        severity: "medium",
        gdprRelevant: true,
        ccpaRelevant: true,
    },
    // SSN
    {
        type: "ssn",
        pattern: /\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/g,
        severity: "critical",
        gdprRelevant: true,
        ccpaRelevant: true,
        validate: (match) => {
            // Validate it's not a date or other number
            const clean = match.replace(/[-\s]/g, "");
            const first3 = parseInt(clean.slice(0, 3));
            return first3 > 0 && first3 < 900 && first3 !== 666;
        },
    },
    // Credit Card
    {
        type: "credit-card",
        pattern: /\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|6(?:011|5[0-9]{2})[0-9]{12})\b/g,
        severity: "critical",
        gdprRelevant: true,
        ccpaRelevant: true,
        validate: (match) => {
            // Luhn algorithm validation
            const digits = match.replace(/\D/g, "");
            let sum = 0;
            let isEven = false;
            for (let i = digits.length - 1; i >= 0; i--) {
                let d = parseInt(digits[i]);
                if (isEven) {
                    d *= 2;
                    if (d > 9)
                        d -= 9;
                }
                sum += d;
                isEven = !isEven;
            }
            return sum % 10 === 0;
        },
    },
    // IP Address
    {
        type: "ip-address",
        pattern: /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g,
        severity: "low",
        gdprRelevant: true,
        ccpaRelevant: false,
        validate: (match) => {
            // Exclude localhost, private ranges for lower severity
            return !match.startsWith("127.") &&
                !match.startsWith("192.168.") &&
                !match.startsWith("10.") &&
                !match.startsWith("172.");
        },
    },
    // Date of Birth patterns
    {
        type: "dob",
        pattern: /\b(?:dob|date\s*of\s*birth|birthday|born\s*on)[:\s]+(\d{1,2}[-/]\d{1,2}[-/]\d{2,4})\b/gi,
        severity: "medium",
        gdprRelevant: true,
        ccpaRelevant: true,
    },
    // Medical terms (simple detection)
    {
        type: "medical",
        pattern: /\b(?:diagnosis|prescription|medication|treatment|patient|symptoms|medical\s*record)\b.*?(?:\.|$)/gi,
        severity: "high",
        gdprRelevant: true,
        ccpaRelevant: true,
    },
    // Financial (account numbers, routing)
    {
        type: "financial",
        pattern: /\b(?:account\s*(?:number|#)|routing\s*(?:number|#))[:\s]*\d{6,17}\b/gi,
        severity: "high",
        gdprRelevant: true,
        ccpaRelevant: true,
    },
];
// ============================================================================
// Analysis Functions
// ============================================================================
/**
 * Detect PII in a text string
 */
function detectPII(text, location, tool) {
    const findings = [];
    for (const piiPattern of PII_PATTERNS) {
        piiPattern.pattern.lastIndex = 0;
        let match;
        while ((match = piiPattern.pattern.exec(text)) !== null) {
            const matchText = match[0];
            // Run validator if present
            if (piiPattern.validate && !piiPattern.validate(matchText)) {
                continue;
            }
            // Redact the match for safe logging
            const redacted = redactPII(matchText, piiPattern.type);
            findings.push({
                type: piiPattern.type,
                location,
                tool,
                severity: piiPattern.severity,
                description: `${piiPattern.type.toUpperCase()} detected in ${location}`,
                evidence: `Found in tool "${tool}" ${location}`,
                redactedSample: redacted,
                gdprRelevant: piiPattern.gdprRelevant,
                ccpaRelevant: piiPattern.ccpaRelevant,
            });
        }
    }
    return findings;
}
/**
 * Redact PII for safe logging
 */
function redactPII(text, type) {
    const len = text.length;
    if (len <= 4)
        return "****";
    switch (type) {
        case "email": {
            const [local, domain] = text.split("@");
            return `${local[0]}***@${domain}`;
        }
        case "phone":
            return `***-***-${text.slice(-4)}`;
        case "ssn":
            return `***-**-${text.slice(-4)}`;
        case "credit-card":
            return `****-****-****-${text.slice(-4)}`;
        case "ip-address":
            return `***.***.***.${text.split(".").pop()}`;
        default:
            return text.slice(0, 2) + "***" + text.slice(-2);
    }
}
/**
 * Analyze a single trace entry
 */
function analyzeTrace(trace) {
    const findings = [];
    // Analyze input
    if (trace.input) {
        findings.push(...detectPII(trace.input, "input", trace.tool));
    }
    // Analyze output
    if (trace.output) {
        findings.push(...detectPII(trace.output, "output", trace.tool));
    }
    return findings;
}
/**
 * Load traces from a directory
 */
async function loadTraces(tracesDir) {
    const traces = [];
    try {
        const files = await readdir(tracesDir);
        const jsonlFiles = files.filter((f) => f.endsWith(".jsonl") || f.endsWith(".json"));
        for (const file of jsonlFiles) {
            const content = await readFile(join(tracesDir, file), "utf-8");
            const lines = content.split("\n").filter((l) => l.trim());
            for (const line of lines) {
                try {
                    const entry = JSON.parse(line);
                    traces.push({
                        timestamp: entry.timestamp || new Date().toISOString(),
                        tool: entry.tool || entry.name || "unknown",
                        input: typeof entry.input === "string" ? entry.input : JSON.stringify(entry.input || {}),
                        output: typeof entry.output === "string" ? entry.output : JSON.stringify(entry.output || {}),
                        sessionId: entry.sessionId,
                        userId: entry.userId,
                        metadata: entry.metadata,
                    });
                }
                catch {
                    // Skip invalid JSON lines
                }
            }
        }
    }
    catch {
        // Directory doesn't exist or can't be read
    }
    return traces;
}
// ============================================================================
// Cross-Tenant Detection
// ============================================================================
/**
 * Detect potential cross-tenant data bleed
 */
function detectCrossTenantIssues(traces) {
    const findings = [];
    // Group traces by session/user
    const bySession = {};
    for (const trace of traces) {
        const key = trace.sessionId || trace.userId || "unknown";
        if (!bySession[key])
            bySession[key] = [];
        bySession[key].push(trace);
    }
    // Look for data appearing across sessions
    const dataBySession = {};
    for (const [session, sessionTraces] of Object.entries(bySession)) {
        dataBySession[session] = new Set();
        for (const trace of sessionTraces) {
            // Extract potential identifiers from outputs
            const matches = (trace.output || "").match(/[a-zA-Z0-9._-]{10,}/g) || [];
            for (const m of matches) {
                dataBySession[session].add(m);
            }
        }
    }
    // Check for overlaps
    const sessions = Object.keys(dataBySession);
    for (let i = 0; i < sessions.length; i++) {
        for (let j = i + 1; j < sessions.length; j++) {
            const overlap = [...dataBySession[sessions[i]]].filter((d) => dataBySession[sessions[j]].has(d));
            if (overlap.length > 10) {
                findings.push({
                    type: "custom",
                    location: "memory",
                    tool: "cross-session",
                    severity: "high",
                    description: "Potential cross-tenant data bleed detected",
                    evidence: `Sessions ${sessions[i]} and ${sessions[j]} share ${overlap.length} data patterns`,
                    gdprRelevant: true,
                    ccpaRelevant: true,
                });
            }
        }
    }
    return findings;
}
// ============================================================================
// Main Analysis Function
// ============================================================================
/**
 * Run privacy analysis on tool traces
 */
export async function runPrivacyAnalysis(tracesDir, options) {
    // Load traces
    const traces = await loadTraces(tracesDir);
    if (traces.length === 0) {
        return {
            totalTraces: 0,
            tracesAnalyzed: 0,
            piiDetected: 0,
            findings: [],
            byType: {},
            byTool: {},
            recommendations: ["No traces found. Ensure tool usage logs are being captured."],
        };
    }
    // Analyze each trace
    const allFindings = [];
    for (const trace of traces) {
        allFindings.push(...analyzeTrace(trace));
    }
    // Detect cross-tenant issues if requested
    if (options?.detectCrossTenant) {
        allFindings.push(...detectCrossTenantIssues(traces));
    }
    // Aggregate by type
    const byType = {};
    for (const finding of allFindings) {
        byType[finding.type] = (byType[finding.type] || 0) + 1;
    }
    // Aggregate by tool
    const byTool = {};
    for (const finding of allFindings) {
        byTool[finding.tool] = (byTool[finding.tool] || 0) + 1;
    }
    // Generate recommendations
    const recommendations = [];
    if (byType["ssn"] || byType["credit-card"]) {
        recommendations.push("CRITICAL: Sensitive financial/identity data detected. Implement PII masking immediately.");
    }
    if (byType["email"] || byType["phone"]) {
        recommendations.push("Implement input/output sanitization for contact information.");
    }
    if (allFindings.some((f) => f.location === "output")) {
        recommendations.push("Review tools that expose PII in outputs. Consider response filtering.");
    }
    if (allFindings.some((f) => f.gdprRelevant)) {
        recommendations.push("GDPR compliance: Document lawful basis for PII processing.");
    }
    return {
        totalTraces: traces.length,
        tracesAnalyzed: traces.length,
        piiDetected: allFindings.length,
        findings: allFindings,
        byType,
        byTool,
        recommendations,
    };
}
/**
 * Convert privacy findings to certification findings
 */
export function privacyToFindings(result) {
    return result.findings.map((f, i) => ({
        id: `prv-${String(i + 1).padStart(3, "0")}`,
        severity: f.severity,
        category: "pii-exposure",
        description: f.description,
        evidence: `${f.evidence}. Sample: ${f.redactedSample || "N/A"}`,
        confidence: 90,
        verifications: [],
        created_at: new Date().toISOString(),
        scanner_source: undefined,
    }));
}
//# sourceMappingURL=agent-privacy.js.map