/**
 * Agent Chain Analysis
 *
 * Extends exfil-path-graph with multi-agent attack path analysis.
 * Models trust boundaries between agents and MCP servers to identify
 * attack paths that span multiple agent hops.
 *
 * @module scanners/agent/agent-chain-analysis
 */
// ============================================================================
// Sensitive Data Patterns
// ============================================================================
const SENSITIVE_DATA_PATTERNS = [
    { pattern: /password/i, type: "credentials" },
    { pattern: /secret/i, type: "secrets" },
    { pattern: /api[_\s-]?key/i, type: "api-keys" },
    { pattern: /token/i, type: "tokens" },
    { pattern: /credential/i, type: "credentials" },
    { pattern: /auth/i, type: "auth-data" },
    { pattern: /private[_\s-]?key/i, type: "private-keys" },
    { pattern: /ssh/i, type: "ssh-keys" },
    { pattern: /certificate/i, type: "certificates" },
    { pattern: /pii/i, type: "pii" },
    { pattern: /personal/i, type: "pii" },
    { pattern: /credit[_\s-]?card/i, type: "pci-data" },
    { pattern: /ssn|social[_\s-]?security/i, type: "pii" },
    { pattern: /health|medical|hipaa/i, type: "phi" },
    { pattern: /financial/i, type: "financial-data" },
    { pattern: /database/i, type: "database-access" },
    { pattern: /admin/i, type: "admin-access" },
];
// ============================================================================
// Graph Building
// ============================================================================
/**
 * Classify trust level for a tool
 */
function classifyTrustLevel(tool) {
    // Network-facing tools are less trusted
    if (tool.networkAccess)
        return "low";
    // Code execution is low trust
    if (tool.codeExecution)
        return "low";
    // Destructive tools are medium trust
    if (tool.destructiveHint)
        return "medium";
    // Read-only tools are higher trust
    if (tool.readOnlyHint)
        return "high";
    // Default to medium
    return "medium";
}
/**
 * Extract data types from tool description
 */
function extractDataTypes(tool) {
    const text = `${tool.name} ${tool.description || ""}`;
    const types = new Set();
    for (const { pattern, type } of SENSITIVE_DATA_PATTERNS) {
        if (pattern.test(text)) {
            types.add(type);
        }
    }
    return Array.from(types);
}
/**
 * Check if a tool accesses sensitive data
 */
function accessesSensitiveData(dataTypes) {
    const sensitiveTypes = [
        "credentials",
        "secrets",
        "api-keys",
        "tokens",
        "private-keys",
        "ssh-keys",
        "pii",
        "pci-data",
        "phi",
    ];
    return dataTypes.some((t) => sensitiveTypes.includes(t));
}
/**
 * Calculate risk score for an agent node
 */
function calculateAgentNodeRisk(node) {
    let score = 0;
    // Trust level impact
    if (node.trustLevel === "untrusted")
        score += 40;
    else if (node.trustLevel === "low")
        score += 30;
    else if (node.trustLevel === "medium")
        score += 15;
    // Entry point risk
    if (node.isEntryPoint)
        score += 25;
    // Sensitive data access
    if (node.accessesSensitiveData)
        score += 35;
    // Capabilities
    if (node.capabilities?.includes("network_access"))
        score += 20;
    if (node.capabilities?.includes("code_execution"))
        score += 30;
    if (node.capabilities?.includes("file_write"))
        score += 15;
    return Math.min(100, score);
}
/**
 * Build agent graph from MCP manifests
 */
export function buildAgentGraph(manifests, agentConfigs) {
    const nodes = [];
    const edges = [];
    // Add user as entry point
    nodes.push({
        id: "user",
        type: "user",
        name: "User Input",
        trustLevel: "untrusted",
        capabilities: ["input"],
        dataAccess: [],
        isEntryPoint: true,
        accessesSensitiveData: false,
        riskScore: 40,
    });
    // Add each MCP server and its tools
    for (const { name: serverName, manifest } of manifests) {
        // Add MCP server node
        const serverId = `mcp-${serverName}`;
        nodes.push({
            id: serverId,
            type: "mcp-server",
            name: serverName,
            trustLevel: "medium",
            capabilities: ["tool-provider"],
            dataAccess: [],
            isEntryPoint: false,
            accessesSensitiveData: false,
            riskScore: 20,
        });
        // Add edge from user to MCP server
        edges.push({
            from: "user",
            to: serverId,
            type: "calls",
            dataTypes: ["user-input"],
            requiresAuth: false,
            crossesTrustBoundary: true,
        });
        // Add tool nodes
        for (const tool of manifest.tools) {
            const toolId = `${serverId}-${tool.name}`;
            const dataTypes = extractDataTypes(tool);
            const trustLevel = classifyTrustLevel(tool);
            const toolNode = {
                id: toolId,
                type: "tool",
                name: tool.name,
                trustLevel,
                capabilities: [],
                dataAccess: dataTypes,
                isEntryPoint: false,
                accessesSensitiveData: accessesSensitiveData(dataTypes),
                riskScore: 0,
            };
            // Add capabilities
            if (tool.networkAccess)
                toolNode.capabilities.push("network_access");
            if (tool.codeExecution)
                toolNode.capabilities.push("code_execution");
            if (tool.destructiveHint)
                toolNode.capabilities.push("write");
            if (tool.readOnlyHint)
                toolNode.capabilities.push("read");
            toolNode.riskScore = calculateAgentNodeRisk(toolNode);
            nodes.push(toolNode);
            // Add edge from server to tool
            edges.push({
                from: serverId,
                to: toolId,
                type: "calls",
                dataTypes,
                requiresAuth: false,
                crossesTrustBoundary: trustLevel !== "medium",
            });
        }
    }
    // Add agent nodes if configured
    if (agentConfigs) {
        for (const agent of agentConfigs) {
            const agentId = `agent-${agent.name}`;
            nodes.push({
                id: agentId,
                type: "agent",
                name: agent.name,
                trustLevel: agent.trustLevel,
                capabilities: ["orchestration"],
                dataAccess: [],
                isEntryPoint: agent.trustLevel === "untrusted",
                accessesSensitiveData: false,
                riskScore: calculateAgentNodeRisk({
                    trustLevel: agent.trustLevel,
                    isEntryPoint: agent.trustLevel === "untrusted",
                }),
            });
        }
    }
    return { nodes, edges };
}
// ============================================================================
// Attack Path Analysis
// ============================================================================
/**
 * Find attack paths from entry points to sensitive data
 */
export function analyzeAgentChains(graph) {
    const paths = [];
    // Find entry points
    const entryPoints = graph.nodes.filter((n) => n.isEntryPoint);
    // Find sensitive data nodes
    const sensitiveNodes = graph.nodes.filter((n) => n.accessesSensitiveData);
    // Build adjacency list
    const adjacency = new Map();
    for (const edge of graph.edges) {
        if (!adjacency.has(edge.from)) {
            adjacency.set(edge.from, []);
        }
        adjacency.get(edge.from).push({ node: edge.to, edge });
    }
    // BFS from each entry point to each sensitive node
    for (const entry of entryPoints) {
        for (const target of sensitiveNodes) {
            const result = findPathBFS(entry.id, target.id, graph.nodes, adjacency);
            if (result) {
                const trustCrossings = result.edges.filter((e) => e.crossesTrustBoundary).length;
                const severity = calculatePathSeverity(trustCrossings, target.dataAccess);
                paths.push({
                    id: `path-${paths.length + 1}`,
                    title: `${entry.name} → ${target.name}`,
                    steps: result.nodes,
                    edgesTraversed: result.edges,
                    trustBoundariesCrossed: trustCrossings,
                    dataExposed: target.dataAccess,
                    severity,
                    confidence: Math.max(50, 100 - trustCrossings * 10),
                    attackNarrative: generateAttackNarrative(entry, target, result.nodes),
                    mitigation: generateMitigations(result.nodes, result.edges),
                });
            }
        }
    }
    return paths;
}
/**
 * BFS to find path between nodes
 */
function findPathBFS(start, end, nodes, adjacency) {
    const nodeMap = new Map(nodes.map((n) => [n.id, n]));
    const visited = new Set();
    const queue = [
        { nodeId: start, path: [start], edges: [] },
    ];
    while (queue.length > 0) {
        const { nodeId, path, edges } = queue.shift();
        if (nodeId === end) {
            return {
                nodes: path.map((id) => nodeMap.get(id)).filter(Boolean),
                edges,
            };
        }
        if (visited.has(nodeId))
            continue;
        visited.add(nodeId);
        const neighbors = adjacency.get(nodeId) || [];
        for (const { node: neighbor, edge } of neighbors) {
            if (!visited.has(neighbor)) {
                queue.push({
                    nodeId: neighbor,
                    path: [...path, neighbor],
                    edges: [...edges, edge],
                });
            }
        }
    }
    return null;
}
/**
 * Calculate severity based on trust crossings and data types
 */
function calculatePathSeverity(trustCrossings, dataTypes) {
    const criticalData = ["credentials", "private-keys", "ssh-keys", "api-keys"];
    const highData = ["tokens", "pii", "pci-data", "phi"];
    const hasCriticalData = dataTypes.some((t) => criticalData.includes(t));
    const hasHighData = dataTypes.some((t) => highData.includes(t));
    if (hasCriticalData && trustCrossings >= 1)
        return "critical";
    if (hasCriticalData || (hasHighData && trustCrossings >= 2))
        return "high";
    if (hasHighData || trustCrossings >= 2)
        return "medium";
    return "low";
}
/**
 * Generate attack narrative
 */
function generateAttackNarrative(entry, target, path) {
    const steps = path.map((n) => n.name).join(" → ");
    return `An attacker could inject malicious input through ${entry.name}, ` +
        `which flows through ${path.length - 2} intermediate step(s) ` +
        `(${steps}) to access ${target.dataAccess.join(", ")} ` +
        `via the ${target.name} tool.`;
}
/**
 * Generate mitigations for a path
 */
function generateMitigations(nodes, edges) {
    const mitigations = new Set();
    // Trust boundary mitigations
    const trustCrossings = edges.filter((e) => e.crossesTrustBoundary);
    if (trustCrossings.length > 0) {
        mitigations.add("Add authentication at trust boundary crossings");
        mitigations.add("Implement input validation at each trust boundary");
    }
    // Sensitive data mitigations
    const sensitiveNodes = nodes.filter((n) => n.accessesSensitiveData);
    for (const node of sensitiveNodes) {
        mitigations.add(`Apply principle of least privilege to ${node.name}`);
        mitigations.add(`Add audit logging for ${node.name} access`);
    }
    // Entry point mitigations
    const entryPoints = nodes.filter((n) => n.isEntryPoint);
    for (const entry of entryPoints) {
        mitigations.add(`Sanitize and validate all input from ${entry.name}`);
    }
    // Code execution mitigations
    const codeExecNodes = nodes.filter((n) => n.capabilities.includes("code_execution"));
    for (const node of codeExecNodes) {
        mitigations.add(`Sandbox code execution in ${node.name}`);
        mitigations.add(`Restrict network access from ${node.name}`);
    }
    // General mitigations
    mitigations.add("Enable comprehensive audit logging across all agents");
    mitigations.add("Implement rate limiting on sensitive operations");
    mitigations.add("Deploy anomaly detection for unusual data access patterns");
    return Array.from(mitigations);
}
// ============================================================================
// Visualization
// ============================================================================
/**
 * Generate Mermaid diagram for agent graph
 */
export function generateAgentChainDiagram(graph, attackPaths) {
    const lines = ["graph TD"];
    // Styling
    lines.push("  classDef entryPoint fill:#ff6b6b,stroke:#333,color:#fff");
    lines.push("  classDef sensitive fill:#ffe66d,stroke:#333,color:#000");
    lines.push("  classDef mcpServer fill:#4ecdc4,stroke:#333,color:#fff");
    lines.push("  classDef tool fill:#95e1d3,stroke:#333");
    lines.push("  classDef agent fill:#a8d8ea,stroke:#333");
    lines.push("  classDef attackPath stroke:#f00,stroke-width:3px");
    // Add nodes
    for (const node of graph.nodes) {
        const sanitized = node.id.replace(/[^a-zA-Z0-9]/g, "_");
        const label = node.name.length > 25 ? node.name.slice(0, 22) + "..." : node.name;
        let className = "tool";
        if (node.isEntryPoint)
            className = "entryPoint";
        else if (node.accessesSensitiveData)
            className = "sensitive";
        else if (node.type === "mcp-server")
            className = "mcpServer";
        else if (node.type === "agent")
            className = "agent";
        lines.push(`  ${sanitized}["${label}"]:::${className}`);
    }
    // Add edges
    const attackEdges = new Set();
    for (const path of attackPaths) {
        for (const edge of path.edgesTraversed) {
            attackEdges.add(`${edge.from}-${edge.to}`);
        }
    }
    for (const edge of graph.edges) {
        const from = edge.from.replace(/[^a-zA-Z0-9]/g, "_");
        const to = edge.to.replace(/[^a-zA-Z0-9]/g, "_");
        const edgeKey = `${edge.from}-${edge.to}`;
        const arrow = edge.crossesTrustBoundary ? "==>" : "-->";
        const style = attackEdges.has(edgeKey) ? "|ATTACK|" : "";
        lines.push(`  ${from} ${arrow}${style} ${to}`);
    }
    // Legend
    lines.push("");
    lines.push("  subgraph Legend");
    lines.push("    entry_leg[Entry Point]:::entryPoint");
    lines.push("    sensitive_leg[Sensitive Data]:::sensitive");
    lines.push("    mcp_leg[MCP Server]:::mcpServer");
    lines.push("    tool_leg[Tool]:::tool");
    lines.push("  end");
    return lines.join("\n");
}
// ============================================================================
// Main Analysis Function
// ============================================================================
/**
 * Run full agent chain analysis
 */
export function runAgentChainAnalysis(manifests, agentConfigs) {
    // Build the graph
    const graph = buildAgentGraph(manifests, agentConfigs);
    // Find attack paths
    const attackPaths = analyzeAgentChains(graph);
    // Generate diagram
    const mermaidDiagram = generateAgentChainDiagram(graph, attackPaths);
    // Calculate stats
    const criticalPaths = attackPaths.filter((p) => p.severity === "critical").length;
    const highPaths = attackPaths.filter((p) => p.severity === "high").length;
    const trustBoundaries = graph.edges.filter((e) => e.crossesTrustBoundary).length;
    return {
        graph,
        attackPaths,
        stats: {
            totalNodes: graph.nodes.length,
            totalEdges: graph.edges.length,
            entryPoints: graph.nodes.filter((n) => n.isEntryPoint).length,
            sensitiveNodes: graph.nodes.filter((n) => n.accessesSensitiveData).length,
            trustBoundaries,
            attackPaths: attackPaths.length,
            criticalPaths,
            highPaths,
        },
        mermaidDiagram,
    };
}
//# sourceMappingURL=agent-chain-analysis.js.map