"use strict";
/**
 * OWASP Agentic Top 10 Scanner — W1.7 of F-NEW-PRODUCT-AGENT-VISIBILITY-CLUSTER-2026-05-12.
 *
 * Analyses an AgentManifest against the OWASP Agentic Top 10 (A01-A10), emits
 * structured findings with severity, remediation, and suggested company-pack
 * gates, and produces a build-structure score (0-100) that feeds the third
 * trust-score dimension (W1.8).
 *
 * Customer flow:
 *   1. Customer-side scan produces an AgentManifest per agent.
 *   2. scanAgent(manifest) -> ScanReport with findings + suggested rules.
 *   3. Each suggested rule maps to a CapabilityRule consumable by buildCompanyPack.
 *   4. Customer accepts/rejects rules; accepted ones become the auto-generated
 *      company pack for that agent.
 *   5. buildStructureScore feeds the agent_build_structure trust-score dimension.
 *
 * Coverage reference: docs/cisos/07-owasp-agentic-top10.md (A01-A10).
 *
 * @connexum/ai-governance  F-NEW-PRODUCT-OWASP-AGENTIC-SCANNER
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.OWASP_AGENTIC_RISK = void 0;
exports.scanAgent = scanAgent;
const capability_classes_js_1 = require("./capability-classes.js");
/** OWASP Agentic Top 10 risk classes (LLM/agent variant). */
exports.OWASP_AGENTIC_RISK = {
    A01_PROMPT_INJECTION: 'A01-prompt-injection',
    A02_INSECURE_OUTPUT: 'A02-insecure-output',
    A03_RAG_POISONING: 'A03-rag-poisoning',
    A04_DOS: 'A04-denial-of-service',
    A05_SUPPLY_CHAIN: 'A05-supply-chain',
    A06_SENSITIVE_DISCLOSURE: 'A06-sensitive-disclosure',
    A07_INSECURE_PLUGIN: 'A07-insecure-plugin',
    A08_EXCESSIVE_AGENCY: 'A08-excessive-agency',
    A09_OVERRELIANCE: 'A09-overreliance',
    A10_MODEL_THEFT: 'A10-model-theft',
};
// ---------------------------------------------------------------------------
// Tool classifiers (reuse capability-class detector semantics directly,
// against the structured manifest rather than serialised input)
// ---------------------------------------------------------------------------
const NETWORK_EGRESS_TOOLS = new Set(['webfetch', 'fetch', 'curl', 'http', 'https', 'request']);
const SHELL_TOOLS = new Set(['bash', 'shell', 'exec', 'execute', 'execute_command', 'run_command']);
const FILESYSTEM_WRITE_TOOLS = new Set(['write', 'edit', 'multiedit', 'write_file', 'fs_write']);
const DB_WRITE_HINTS = ['sql', 'postgres', 'mysql', 'mongo', 'database', 'db_'];
function hasTool(tools, predicate) {
    return tools.some((tool) => predicate(tool.toLowerCase()));
}
function hasNetworkEgress(tools) {
    return hasTool(tools, (t) => NETWORK_EGRESS_TOOLS.has(t) || t.startsWith('mcp__'));
}
function hasShellExec(tools) {
    return hasTool(tools, (t) => SHELL_TOOLS.has(t));
}
function hasFilesystemWrite(tools) {
    return hasTool(tools, (t) => FILESYSTEM_WRITE_TOOLS.has(t));
}
function hasDbWrite(tools) {
    return hasTool(tools, (t) => DB_WRITE_HINTS.some((h) => t.includes(h)));
}
function hasMcp(tools, mcpServers) {
    return hasTool(tools, (t) => t.startsWith('mcp__') || t.startsWith('mcp_')) ||
        (mcpServers !== undefined && mcpServers.length > 0);
}
// ---------------------------------------------------------------------------
// Risk analyzers (A01-A10)
// ---------------------------------------------------------------------------
function analyzeA01PromptInjection(m) {
    const findings = [];
    const fetchesUntrusted = hasNetworkEgress(m.tools);
    const canAct = hasShellExec(m.tools) || hasDbWrite(m.tools) || hasFilesystemWrite(m.tools);
    if (fetchesUntrusted && canAct) {
        findings.push({
            risk: exports.OWASP_AGENTIC_RISK.A01_PROMPT_INJECTION,
            severity: 'CRIT',
            title: 'Lethal Trifecta: fetches untrusted content AND has state-changing tools',
            detail: 'This agent can retrieve untrusted external content (web/MCP/network) and ' +
                'also execute state-changing actions (shell / DB write / filesystem write) in the same ' +
                'cycle. A hostile retrieval can therefore trigger consequential action via indirect ' +
                'prompt injection.',
            remediation: 'Enable info/action separation in the company pack (isolation: "sourced-only" or ' +
                '"strict") OR remove one side of the trifecta.',
        });
    }
    if (m.hooks?.preToolUse !== true) {
        findings.push({
            risk: exports.OWASP_AGENTIC_RISK.A01_PROMPT_INJECTION,
            severity: 'HIGH',
            title: 'PreToolUse hook not wired',
            detail: 'Without PreToolUse hook, the PromptInjectionDetector cannot run before tool dispatch.',
            remediation: 'Install the @connexum/ai-governance PreToolUse hook on this agent.',
        });
    }
    return findings;
}
function analyzeA02InsecureOutput(m) {
    if (m.hooks?.postResponse !== true) {
        return [
            {
                risk: exports.OWASP_AGENTIC_RISK.A02_INSECURE_OUTPUT,
                severity: 'HIGH',
                title: 'PostResponse hook not wired',
                detail: 'Without PostResponse, the G-series output classifiers cannot scan LLM responses for ' +
                    'secrets, PHI, or executable content before downstream consumption.',
                remediation: 'Install the @connexum/ai-governance PostResponse hook on this agent.',
            },
        ];
    }
    return [];
}
function analyzeA03RagPoisoning(m) {
    if (hasNetworkEgress(m.tools) && m.hooks?.postToolUse !== true) {
        return [
            {
                risk: exports.OWASP_AGENTIC_RISK.A03_RAG_POISONING,
                severity: 'MED',
                title: 'Retrieval without provenance capture',
                detail: 'Agent retrieves external content but PostToolUse hook is not wired, so retrieval ' +
                    'provenance is not bound into the audit chain (A03 mitigation requires per-retrieval ' +
                    'pack_binding capture).',
                remediation: 'Install PostToolUse hook to bind retrievals to active packs.',
            },
        ];
    }
    return [];
}
function analyzeA04Dos(m) {
    if (m.rateLimitsConfigured !== true) {
        return [
            {
                risk: exports.OWASP_AGENTIC_RISK.A04_DOS,
                severity: 'MED',
                title: 'No tool rate limits configured',
                detail: 'Agent has no per-tool / per-cycle rate caps. Adversarial inputs can exhaust the API ' +
                    'budget or trigger an expensive cascade.',
                remediation: 'Configure ToolRateLimiter caps in the company pack (per-tool blast-radius scaling).',
            },
        ];
    }
    return [];
}
function analyzeA05SupplyChain(m) {
    if (m.pluginIntegrityVerified !== true) {
        return [
            {
                risk: exports.OWASP_AGENTIC_RISK.A05_SUPPLY_CHAIN,
                severity: 'HIGH',
                title: 'Plugin / adapter integrity not verified',
                detail: 'Loaded framework adapters / MCP plugins have not been SHA-256 attestation-verified. ' +
                    'A tampered plugin could execute attacker code in the agent context.',
                remediation: 'Run `npx @connexum/ai-governance verify-plugins` to attest plugin integrity ' +
                    'and commit the attestation manifest.',
            },
        ];
    }
    return [];
}
function analyzeA06SensitiveDisclosure(m) {
    const findings = [];
    if (m.llmProvider && !m.llmProvider.startsWith('connexum-proxy/')) {
        findings.push({
            risk: exports.OWASP_AGENTIC_RISK.A06_SENSITIVE_DISCLOSURE,
            severity: 'MED',
            title: 'LLM keys not routed through governed proxy',
            detail: 'Direct LLM provider calls expose the API key to the agent process. The Mode A proxy ' +
                '(governed.my-cc.io) keeps keys in customer infrastructure and zero-knowledge upstream.',
            remediation: 'Swap LLM base URL to https://proxy.my-cc.io to route through the Mode A governed proxy.',
        });
    }
    return findings;
}
function analyzeA07InsecurePlugin(m) {
    if (hasMcp(m.tools, m.mcpServers) && m.pluginIntegrityVerified !== true) {
        return [
            {
                risk: exports.OWASP_AGENTIC_RISK.A07_INSECURE_PLUGIN,
                severity: 'HIGH',
                title: 'MCP plugin in use without integrity attestation',
                detail: 'Agent uses MCP plugins but plugin integrity is not attested. MCP plugin design is ' +
                    'high-trust (A07 specifically calls out plugin-design risks).',
                remediation: 'Attest plugin SHA-256s + cycle-lock the active plugin set, OR forbid MCP via the ' +
                    'capability:mcp rule.',
                suggestedRule: {
                    capability: capability_classes_js_1.CAPABILITY_CLASS.MCP,
                    action: 'REQUIRE_APPROVAL',
                    note: 'A07 suggested-rule: route MCP plugin invocations through human approval',
                },
            },
        ];
    }
    return [];
}
function analyzeA08ExcessiveAgency(m) {
    const findings = [];
    const riskyCapabilities = [
        { check: hasShellExec(m.tools), cap: capability_classes_js_1.CAPABILITY_CLASS.SHELL_EXEC, label: 'shell exec' },
        { check: hasDbWrite(m.tools), cap: capability_classes_js_1.CAPABILITY_CLASS.EXTERNAL_DB_WRITE, label: 'destructive DB writes' },
        { check: hasFilesystemWrite(m.tools), cap: capability_classes_js_1.CAPABILITY_CLASS.FILESYSTEM_WRITE, label: 'filesystem writes' },
    ];
    const declaredApprovals = new Set((m.declaredApprovalGates ?? []).map((g) => g.capability));
    const declaredForbidden = new Set(m.declaredForbiddenCapabilities ?? []);
    for (const { check, cap, label } of riskyCapabilities) {
        if (!check)
            continue;
        if (declaredApprovals.has(cap) || declaredForbidden.has(cap))
            continue;
        findings.push({
            risk: exports.OWASP_AGENTIC_RISK.A08_EXCESSIVE_AGENCY,
            severity: 'HIGH',
            title: `${label} capability ungated`,
            detail: `Agent has ${label} capability but no approval-required gate or capability-forbid ` +
                'rule is declared. A08 (excessive agency) requires explicit constraints on high-blast-radius actions.',
            remediation: `Add { capability: '${cap}', action: 'REQUIRE_APPROVAL' } to the company pack, or ` +
                `forbid the capability outright if not needed.`,
            suggestedRule: {
                capability: cap,
                action: 'REQUIRE_APPROVAL',
                note: `A08 suggested-rule: gate ${label} with human approval`,
            },
        });
    }
    return findings;
}
function analyzeA09Overreliance(m) {
    const approvals = m.declaredApprovalGates ?? [];
    const forbids = m.declaredForbiddenCapabilities ?? [];
    if (approvals.length === 0 && forbids.length === 0) {
        return [
            {
                risk: exports.OWASP_AGENTIC_RISK.A09_OVERRELIANCE,
                severity: 'MED',
                title: 'No human-in-loop gates configured',
                detail: 'Agent declares zero approval-required gates and zero forbidden capabilities. ' +
                    'A09 (overreliance) requires at least one human-in-loop touchpoint for high-blast-radius ' +
                    'classes.',
                remediation: 'Author at least one capability rule with action: REQUIRE_APPROVAL covering the agent\'s ' +
                    'state-changing surface.',
            },
        ];
    }
    return [];
}
function analyzeA10ModelTheft(_m) {
    // A10 is primarily an infrastructure concern (audit chain hash + cross-tenant
    // RLS) -- both already enforced platform-side per Locked Invariant 7. No
    // per-agent finding to emit at scan time. Stub retained so the analyzer set
    // is exhaustive across A01..A10.
    return [];
}
// ---------------------------------------------------------------------------
// Public surface
// ---------------------------------------------------------------------------
const SEVERITY_PENALTY = {
    LOW: 8,
    MED: 16,
    HIGH: 32,
    CRIT: 50,
};
function dedupSuggestedRules(rules) {
    const seen = new Set();
    const out = [];
    for (const r of rules) {
        const key = `${r.capability}::${r.action}`;
        if (seen.has(key))
            continue;
        seen.add(key);
        out.push(r);
    }
    return out;
}
function verdictFromScore(score, crit) {
    if (crit > 0)
        return 'CRITICAL';
    if (score >= 90)
        return 'EXCELLENT';
    if (score >= 75)
        return 'GOOD';
    if (score >= 50)
        return 'NEEDS_REVIEW';
    if (score >= 25)
        return 'POOR';
    return 'CRITICAL';
}
/**
 * Run all A01..A10 analyzers against a manifest and produce a structured report.
 */
function scanAgent(manifest) {
    const findings = [
        ...analyzeA01PromptInjection(manifest),
        ...analyzeA02InsecureOutput(manifest),
        ...analyzeA03RagPoisoning(manifest),
        ...analyzeA04Dos(manifest),
        ...analyzeA05SupplyChain(manifest),
        ...analyzeA06SensitiveDisclosure(manifest),
        ...analyzeA07InsecurePlugin(manifest),
        ...analyzeA08ExcessiveAgency(manifest),
        ...analyzeA09Overreliance(manifest),
        ...analyzeA10ModelTheft(manifest),
    ];
    const counts = { LOW: 0, MED: 0, HIGH: 0, CRIT: 0 };
    let penalty = 0;
    for (const f of findings) {
        counts[f.severity] += 1;
        penalty += SEVERITY_PENALTY[f.severity];
    }
    const score = Math.max(0, 100 - penalty);
    const suggestedRules = dedupSuggestedRules(findings.flatMap((f) => (f.suggestedRule ? [f.suggestedRule] : [])));
    return {
        agentId: manifest.agentId,
        findings,
        countsBySeverity: counts,
        suggestedRules,
        buildStructureScore: score,
        verdict: verdictFromScore(score, counts.CRIT),
    };
}
//# sourceMappingURL=owasp-agentic-scanner.js.map