/**
 * 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
 */
import { CAPABILITY_CLASS, } from './capability-classes.js';
/** OWASP Agentic Top 10 risk classes (LLM/agent variant). */
export const 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',
    /**
     * ASI-08 Cascading Failures (Agentic Security Initiative extension).
     * Tracks whether the CircuitBreaker is wired into the agent's PreToolUse
     * enforcement path. Without it, a failure spiral in one agent can cascade
     * to every downstream tool call in the same session.
     */
    ASI08_CASCADING_FAILURES: 'ASI-08-cascading-failures',
    /**
     * ASI-10 Rogue Agents (Agentic Security Initiative extension).
     * Detects agents that lack operator-initiated kill-switch controls:
     *   - No circuit breaker configured (no forceOpen capability)
     *   - No revocation capability declared
     *   - Autonomy level > 3 for strict-pack deployments (HIPAA / PCI DSS)
     * These gaps mean a rogue agent cannot be hard-stopped without a full
     * process restart — an unacceptable blast radius in production.
     */
    ASI10_ROGUE_AGENT: 'ASI-10-rogue-agent',
    /**
     * ASI-07 Insecure Inter-Agent Communication (Agentic Security Initiative).
     * Detects agents that participate in agent-to-agent messaging (delegation /
     * handoff / multi-agent orchestration) WITHOUT per-message authenticity:
     * identity bound only at handshake, no per-message Ed25519 signature. A
     * compromised relay or man-in-the-middle can then forge/inject messages
     * mid-session. The fix is to enable AgentMessageEnvelope signing on the
     * adapter (src/security/agent-message-envelope.ts).
     */
    ASI07_INSECURE_INTER_AGENT_COMMS: 'ASI-07-insecure-inter-agent-comms',
    /**
     * ASI-06 Memory & Context Poisoning (Agentic Security Initiative).
     * Detects agents whose persistent memory or multi-turn context window can be
     * poisoned undetected:
     *   - no per-cycle context-window integrity hash (in-session prompt/turn
     *     rewrite, reorder, or truncation goes unnoticed), and/or
     *   - persistent memory protected only by a forgeable plain SHA-256 footer
     *     instead of a keyed HMAC.
     * Fixes: ContextWindowHasher (src/security/context-window-hasher.ts) +
     * MemoryIntegrity keyed-HMAC mode (src/governance/memory-integrity.ts).
     */
    ASI06_MEMORY_CONTEXT_POISONING: 'ASI-06-memory-context-poisoning',
    /**
     * ASI-05 Unsafe Code Execution (Agentic Security Initiative).
     * A permitted shell command runs with full process authority unless confined.
     * Fires when the agent can execute shell but no sandbox policy (allowlist /
     * no-network / no-priv-esc / path-confinement / injection-deny) is enforced.
     * Fix: ShellSandbox policy (src/security/shell-sandbox.ts) + an OS-primitive jail.
     */
    ASI05_UNSAFE_CODE_EXECUTION: 'ASI-05-unsafe-code-execution',
};
// ---------------------------------------------------------------------------
// 10-ASI coverage model (Trust Score, component #1)
//
// The scanner historically mixed the OLD OWASP-LLM A0x numbering with a few new
// ASI-0x analyzers. The Trust Score's build axis must be calculated HONESTLY
// across the canonical OWASP Agentic (ASI / AGT) Top 10 — and must not imply
// coverage it does not have. This registry declares the 10 items and which
// finding-risks assess each; scanAgent() reports per-item coverage so the band
// can be coverage-gated (no "passed the 10 AGTs" unless all 10 were evaluated).
// ---------------------------------------------------------------------------
/** Canonical OWASP Agentic (ASI / AGT) Top 10 — 2026. */
export const ASI_TOP_10 = {
    'ASI-01': 'Prompt Injection / Goal Hijack',
    'ASI-02': 'Tool Misuse',
    'ASI-03': 'Identity & Privilege Abuse',
    'ASI-04': 'Agentic Supply Chain',
    'ASI-05': 'Unsafe Code Execution',
    'ASI-06': 'Memory & Context Poisoning',
    'ASI-07': 'Insecure Inter-Agent Communication',
    'ASI-08': 'Cascading Failures',
    'ASI-09': 'Human-Agent Trust / Oversight',
    'ASI-10': 'Rogue Agents',
};
/** Canonical ASI ids in order. */
export const ASI_IDS = Object.keys(ASI_TOP_10);
/**
 * CANONICAL taxonomy bridge (consolidation 2026-05-30).
 *
 * The codebase historically carried TWO OWASP representations: the runtime
 * posture surface (audit `owasp_category` tagger + `/api/v1/owasp/posture` +
 * dashboard tile) used the OLD OWASP-LLM `A01..A10` ids, while the build scanner
 * + coverage model use the OWASP Agentic `ASI-01..ASI-10` ids. This map folds
 * the legacy A0x ids onto the canonical ASI items so EVERY surface — scanner,
 * runtime tagger, posture endpoint, dashboard tile, CLI, packs/middleware —
 * speaks ONE taxonomy. Persisted A0x-tagged audit events keep their history:
 * the posture endpoint normalizes them through this map at query time.
 *
 * Mapping per docs/decisions/owasp-agentic-AGT-parity-matrix-2026-05-29.md.
 * `ASI-10-rogue-agent` (already emitted by the tagger for suspend/resume) and
 * the other ASI-0x risk ids pass through unchanged.
 */
export const A0X_TO_ASI = {
    'A01-prompt-injection': 'ASI-01',
    'A02-insecure-output': 'ASI-01', // output-side goal hijack
    'A03-rag-poisoning': 'ASI-06', // retrieval/memory poisoning
    'A04-denial-of-service': 'ASI-08', // cascading / resource exhaustion
    'A05-supply-chain': 'ASI-04',
    'A06-sensitive-disclosure': 'ASI-03', // credential/identity exposure
    'A07-insecure-plugin': 'ASI-02', // tool misuse
    'A08-excessive-agency': 'ASI-08', // autonomy bounds → cascading
    'A09-overreliance': 'ASI-09',
    'A10-model-theft': 'ASI-10',
};
/**
 * Normalize ANY OWASP category id (legacy `A0x-*` or canonical `ASI-0x[-*]`) to
 * its canonical `ASI-0x` item id. Unknown ids return undefined (caller decides).
 */
export function toCanonicalAsi(category) {
    if (!category)
        return undefined;
    if (category in A0X_TO_ASI)
        return A0X_TO_ASI[category];
    // ASI-0x or ASI-0x-something → take the ASI-0x prefix.
    const m = /^(ASI-\d{2})/.exec(category);
    if (m && ASI_TOP_10[m[1]])
        return m[1];
    return undefined;
}
/**
 * Which finding-risks (and therefore which analyzers) assess each ASI item.
 * An ASI item is "assessed" iff at least one of its risks is produced by an
 * analyzer that actually ran. Maintained alongside scanAgent()'s analyzer list:
 * adding an analyzer for a previously-unassessed ASI item is what flips its
 * coverage to true.
 */
export const ASI_RISK_COVERAGE = {
    'ASI-01': [OWASP_AGENTIC_RISK.A01_PROMPT_INJECTION],
    'ASI-02': [OWASP_AGENTIC_RISK.A07_INSECURE_PLUGIN],
    'ASI-03': [OWASP_AGENTIC_RISK.A06_SENSITIVE_DISCLOSURE],
    'ASI-04': [OWASP_AGENTIC_RISK.A05_SUPPLY_CHAIN],
    'ASI-05': [OWASP_AGENTIC_RISK.ASI05_UNSAFE_CODE_EXECUTION],
    'ASI-06': [OWASP_AGENTIC_RISK.ASI06_MEMORY_CONTEXT_POISONING, OWASP_AGENTIC_RISK.A03_RAG_POISONING],
    'ASI-07': [OWASP_AGENTIC_RISK.ASI07_INSECURE_INTER_AGENT_COMMS],
    'ASI-08': [OWASP_AGENTIC_RISK.ASI08_CASCADING_FAILURES],
    'ASI-09': [OWASP_AGENTIC_RISK.A09_OVERRELIANCE],
    'ASI-10': [OWASP_AGENTIC_RISK.ASI10_ROGUE_AGENT],
};
/**
 * The set of ASI items that scanAgent() currently has a real analyzer for.
 * Items NOT in this set are reported as `assessed:false` (not evaluated) so the
 * Trust Score never treats "no findings" as "passed" for an unrun check.
 * Update this as parity analyzers land (ASI-05 is the remaining gap).
 */
export const ASI_ASSESSED_ITEMS = new Set([
    'ASI-01', 'ASI-02', 'ASI-03', 'ASI-04', 'ASI-05', 'ASI-06', 'ASI-07', 'ASI-08', 'ASI-09', 'ASI-10',
]);
// ---------------------------------------------------------------------------
// Tool classifiers (reuse capability-class detector semantics directly,
// against the structured manifest rather than serialised input)
// ---------------------------------------------------------------------------
// Network egress / data-exfiltration channels. Includes HTTP-style fetch verbs
// AND messaging/upload channels (Thomas 2026-06-05): `send_email`, `upload_file`,
// webhook/slack/sms posts are real exfiltration vectors — a customer-support
// agent that can email data out must NOT score like a pure read-only analyzer.
// Tokens only (matched whole-word via matchesCapabilitySet) — kept unambiguous to
// avoid false positives (e.g. no bare 'send'/'post' which appear in benign names).
const NETWORK_EGRESS_TOOLS = new Set([
    'webfetch', 'fetch', 'curl', 'http', 'https', 'request',
    'email', 'smtp', 'sendmail', 'mailgun', 'sendgrid',
    'webhook', 'slack', 'sms', 'twilio', 'telegram', 'discord',
    'upload', 'publish', 'pushnotification',
]);
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()));
}
/**
 * Split a tool name into its component tokens so capability matching works on
 * real-world snake/kebab/dotted names, not just exact Claude-native tool ids.
 * e.g. 'shell_exec' → ['shell','exec'], 'network_request' → ['network','request'],
 * 'file_write' → ['file','write']. Without this, customer manifests that declare
 * tools as `{name:'shell_exec'}` were invisible to the scanner → every agent
 * scored identically (Thomas 2026-06-05: scores didn't differ by build).
 */
function toolTokens(t) {
    return t.split(/[^a-z0-9]+/i).filter(Boolean);
}
/** Exact id match (back-compat) OR any whole token in the set (custom names). */
function matchesCapabilitySet(tool, set) {
    if (set.has(tool))
        return true;
    return toolTokens(tool).some((tok) => set.has(tok));
}
function hasNetworkEgress(tools) {
    return hasTool(tools, (t) => matchesCapabilitySet(t, NETWORK_EGRESS_TOOLS) || t.startsWith('mcp__'));
}
function hasShellExec(tools) {
    return hasTool(tools, (t) => matchesCapabilitySet(t, SHELL_TOOLS));
}
function hasFilesystemWrite(tools) {
    return hasTool(tools, (t) => matchesCapabilitySet(t, FILESYSTEM_WRITE_TOOLS));
}
function hasDbWrite(tools) {
    return hasTool(tools, (t) => DB_WRITE_HINTS.some((h) => t.includes(h)));
}
function hasMcp(tools, mcpServers) {
    // MCP tool naming convention is the double-underscore form `mcp__<server>__<tool>`.
    // Earlier this also matched single-underscore `mcp_` which caught non-MCP tools
    // like `mcp_helper` or `mcp_internal` (Stern audit 2026-05-26). Narrowed to
    // the double-underscore canonical pattern only.
    return hasTool(tools, (t) => 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: 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.',
        });
    }
    // Capability-scoped (Thomas 2026-06-05): PreToolUse enforcement is only REQUIRED
    // for an agent that actually dispatches tools. A no-tool (conversational) agent
    // isn't penalized for a control it doesn't need.
    if ((m.tools?.length ?? 0) > 0 && m.hooks?.preToolUse !== true) {
        findings.push({
            risk: 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.',
        });
    }
    // ASI-01 semantic stage: an agent that ingests untrusted external content is
    // exposed to PARAPHRASED indirect injection that the rule/regex detector
    // misses (no trigger keywords). Recommend the semantic classifier when the
    // agent fetches untrusted content and the semantic stage isn't enabled.
    if (fetchesUntrusted && m.semanticInjectionEnabled !== true) {
        findings.push({
            risk: OWASP_AGENTIC_RISK.A01_PROMPT_INJECTION,
            severity: 'MED',
            title: 'Pattern-only injection detection: paraphrased indirect injection can evade it',
            detail: 'This agent ingests untrusted external content but relies on rule/regex prompt-injection ' +
                'detection only. A paraphrased, keyword-free indirect injection (e.g. "kindly set aside the ' +
                'directions you were given earlier") evades the pattern stage. Enable the semantic classifier ' +
                '(SemanticInjectionClassifier) so paraphrased injection intent is caught by embedding similarity.',
            remediation: 'Wire SemanticInjectionClassifier (src/security/semantic-injection-classifier.ts) as a second ' +
                'stage after the PromptInjectionDetector, providing your LLM adapter\'s embedding function. It is ' +
                'advisory (never throws / never hard-blocks) and feature-flagged.',
            suggestedRule: {
                capability: CAPABILITY_CLASS.NETWORK_EGRESS,
                action: 'REQUIRE_APPROVAL',
                note: 'ASI-01: require approval for untrusted-content egress until semantic injection detection is enabled',
            },
        });
    }
    return findings;
}
function analyzeA02InsecureOutput(m) {
    if (m.hooks?.postResponse !== true) {
        return [
            {
                risk: 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: 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) {
    // Capability-scoped (Thomas 2026-06-05): per-tool rate caps are only REQUIRED
    // for an agent that actually calls tools. A no-tool agent has no tool budget to
    // exhaust, so it isn't penalized for this control.
    if ((m.tools?.length ?? 0) > 0 && m.rateLimitsConfigured !== true) {
        return [
            {
                risk: 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) {
    const findings = [];
    // Capability-scoped (Thomas 2026-06-05): plugin/adapter integrity is only
    // REQUIRED for an agent that actually loads plugins/MCP. An agent with no
    // plugins isn't penalized for a control that doesn't apply to it.
    if (hasMcp(m.tools, m.mcpServers) && m.pluginIntegrityVerified !== true) {
        findings.push({
            risk: 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.',
        });
    }
    // ASI-04: no AI Bill of Materials = no verifiable supply-chain provenance.
    if (m.aiBomGenerated !== true) {
        findings.push({
            risk: OWASP_AGENTIC_RISK.A05_SUPPLY_CHAIN,
            severity: 'MED',
            title: 'No AI Bill of Materials (AI-BOM) — supply chain is not provenance-verifiable',
            detail: 'This agent has no AI-BOM: a tamper-evident inventory of its models, tools, MCP servers, ' +
                'compliance packs, and dependencies anchored by a bomHash. Without it a relying org cannot ' +
                'verify what composes the agent or detect a swapped model / injected dependency (ASI-04 ' +
                'agentic supply-chain vector).',
            remediation: 'Generate an AI-BOM at registration (generateAiBom in src/security/ai-bom.ts) and a SLSA ' +
                'provenance statement binding its bomHash; store bomHash on the agent so drift is detectable.',
            suggestedRule: {
                capability: CAPABILITY_CLASS.NETWORK_EGRESS,
                action: 'REQUIRE_APPROVAL',
                note: 'ASI-04: require approval for external egress until an AI-BOM + provenance exist',
            },
        });
    }
    return findings;
}
function analyzeA06SensitiveDisclosure(m) {
    const findings = [];
    if (m.llmProvider && !m.llmProvider.startsWith('connexum-proxy/')) {
        findings.push({
            risk: 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: 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_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_CLASS.SHELL_EXEC, label: 'shell exec' },
        { check: hasDbWrite(m.tools), cap: CAPABILITY_CLASS.EXTERNAL_DB_WRITE, label: 'destructive DB writes' },
        { check: hasFilesystemWrite(m.tools), cap: 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: 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: 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.',
            },
        ];
    }
    // ASI-09: an approval GATE proves presence, not practice (EU AI Act Art. 14).
    // When the agent has HITL gates but oversight-effectiveness scoring isn't
    // enabled, rubber-stamping goes undetected.
    if (approvals.length > 0 && m.oversightScoringEnabled !== true) {
        return [
            {
                risk: OWASP_AGENTIC_RISK.A09_OVERRELIANCE,
                severity: 'LOW',
                title: 'Human-in-loop gates present but oversight effectiveness is not scored',
                detail: 'This agent routes high-risk actions to human approval, but there is no measurement of ' +
                    'whether reviewers exercise judgment (time-to-decision, override rate, rationale) — so ' +
                    'rubber-stamping (instant near-unanimous approvals) goes undetected (ASI-09 / EU AI Act Art. 14).',
                remediation: 'Enable oversight-effectiveness scoring (scoreOversight in src/governance/oversight-effectiveness.ts) ' +
                    'over the HITL review events to surface rubber-stamp reviewers and a per-reviewer effectiveness score.',
            },
        ];
    }
    return [];
}
/**
 * ASI-08 Cascading Failures — checks whether the agent's PreToolUse enforcement
 * path has a CircuitBreaker wired.
 *
 * Without a breaker in the PreToolUse path, a consecutive-failure spiral in one
 * agent session can cascade: every subsequent tool call is dispatched against a
 * degraded provider or a looping agent, exhausting budgets and creating a blast
 * radius that extends across dependent agents in the same org.
 *
 * Severity: MED (circuit breaker is a defence-in-depth control; not wiring one
 * does not directly expose data, but dramatically increases blast radius of any
 * failure event).
 *
 * suggestedRule: adds circuit_breaker capability with REQUIRE_APPROVAL action so
 * the auto-generated company pack tells the customer to configure the breaker.
 */
function analyzeASI08CascadingFailures(m) {
    if (m.circuitBreakerConfigured === true) {
        return [];
    }
    return [
        {
            risk: OWASP_AGENTIC_RISK.ASI08_CASCADING_FAILURES,
            severity: 'MED',
            title: 'CircuitBreaker not wired into PreToolUse enforcement path',
            detail: 'This agent has no CircuitBreaker configured in its PreToolUse enforcement path. ' +
                'Without it, consecutive failures (provider outage, looping agent, adversarial tool cascade) ' +
                'continue to dispatch tool calls unchecked, amplifying the blast radius to downstream agents ' +
                'and exhausting API budgets (ASI-08 cascading failure vector).',
            remediation: 'Pass a CircuitBreaker instance via the `breakerOpts.circuitBreaker` parameter of ' +
                'evaluatePreFlight() (packages/governance-proxy/src/governance/evaluator.ts). ' +
                'The breaker check is now built into evaluatePreFlight() by default — no separate ' +
                'wrapper (evaluatePreFlightWithBreaker) is required. ' +
                'The breaker defaults to CLOSED (no disruption) until 3 consecutive failures are recorded, ' +
                'then OPEN (denies further calls) for 10 min before entering HALF_OPEN probation.',
            suggestedRule: {
                capability: CAPABILITY_CLASS.SHELL_EXEC, // closest blast-radius capability in the catalog
                action: 'REQUIRE_APPROVAL',
                note: 'ASI-08 suggested-rule: add CircuitBreaker to PreToolUse to prevent cascading failures',
            },
        },
    ];
}
/**
 * ASI-05 Unsafe Code Execution. Fires when the agent can run shell commands but
 * no sandbox policy is enforced — a permitted command then runs with full
 * process authority (network egress, privilege escalation, filesystem escape).
 */
function analyzeASI05CodeExecution(m) {
    if (!hasShellExec(m.tools))
        return [];
    if (m.shellSandboxEnabled === true)
        return [];
    return [
        {
            risk: OWASP_AGENTIC_RISK.ASI05_UNSAFE_CODE_EXECUTION,
            severity: 'HIGH',
            title: 'Shell execution without a sandbox: permitted commands run with full process authority',
            detail: 'This agent can execute shell commands but no sandbox policy is enforced. A permitted ' +
                'command (or an injected chained command) therefore runs with the agent process\'s full ' +
                'authority — network egress, privilege escalation, and filesystem access outside its work ' +
                'area are all possible (ASI-05 unsafe code execution vector).',
            remediation: 'Enforce a ShellSandbox policy (evaluateSandboxedCommand in src/security/shell-sandbox.ts): ' +
                'allowlist binaries, deny network / privilege-escalation / path-escape / shell-injection ' +
                'chaining, and run inside an OS-primitive jail (namespaces / seccomp / container).',
            suggestedRule: {
                capability: CAPABILITY_CLASS.SHELL_EXEC,
                action: 'REQUIRE_APPROVAL',
                note: 'ASI-05: require approval for shell execution until a sandbox policy is enforced',
            },
        },
    ];
}
/**
 * ASI-06 Memory & Context Poisoning.
 *
 * Two checks, only relevant to agents that hold persistent memory or a
 * multi-turn context window (`hasPersistentMemory`):
 *   1. Context-window integrity (HIGH): no per-cycle ContextWindowHasher, so an
 *      in-session rewrite/reorder/truncation of a prior turn or a swapped system
 *      prompt goes undetected.
 *   2. Persistent-memory authenticity (MED): memory protected by a forgeable
 *      plain SHA-256 footer instead of a keyed HMAC — anyone who can write the
 *      file can forge the footer, so memory poisoning is undetectable.
 *
 * No finding when the agent holds no persistent/multi-turn state.
 */
function analyzeASI06MemoryContext(m) {
    if (m.hasPersistentMemory !== true) {
        return [];
    }
    const findings = [];
    if (m.contextIntegrityEnabled !== true) {
        findings.push({
            risk: OWASP_AGENTIC_RISK.ASI06_MEMORY_CONTEXT_POISONING,
            severity: 'HIGH',
            title: 'No context-window integrity hash: in-session context poisoning is undetected',
            detail: 'This agent maintains a multi-turn context window but does not hash and ' +
                're-verify the immutable prefix (system prompt + prior turns) across cycles. ' +
                'A compromised tool, an indirect-injection payload, or a poisoned retrieval ' +
                'can rewrite, reorder, or truncate earlier turns — or swap the system prompt — ' +
                'mid-session without detection (ASI-06 memory/context poisoning vector).',
            remediation: 'Wire ContextWindowHasher (src/security/context-window-hasher.ts) into the ' +
                'agent execution loop: capture() the prefix at cycle start and verify() it at ' +
                'the next cycle. A mismatch raises CONTEXT_INTEGRITY_VIOLATION (advisory per ' +
                'Invariants 2/3 — never throws on the agent path).',
            suggestedRule: {
                capability: CAPABILITY_CLASS.NETWORK_EGRESS,
                action: 'REQUIRE_APPROVAL',
                note: 'ASI-06: require approval for actions after a context-integrity violation until context hashing is enabled',
            },
        });
    }
    if (m.memoryHmacEnabled !== true) {
        findings.push({
            risk: OWASP_AGENTIC_RISK.ASI06_MEMORY_CONTEXT_POISONING,
            severity: 'MED',
            title: 'Persistent memory uses a forgeable plain-hash footer (no keyed HMAC)',
            detail: 'This agent\'s persistent memory is protected by a plain SHA-256 integrity ' +
                'footer. Anyone who can write the memory file can also recompute that footer, ' +
                'so a poisoning rewrite is undetectable. A keyed HMAC footer cannot be forged ' +
                'without the secret (ASI-06 persistent-memory poisoning vector).',
            remediation: 'Configure MemoryIntegrity with an hmacKey (MEMORY_INTEGRITY_HMAC_KEY, >= 32 ' +
                'chars, env-sourced). Footers become keyed hmac-sha256; verifyFile then rejects ' +
                'a downgrade to a plain-hash footer, an unverifiable footer, or a stripped footer.',
            suggestedRule: {
                capability: CAPABILITY_CLASS.FILESYSTEM_WRITE,
                action: 'REQUIRE_APPROVAL',
                note: 'ASI-06: require approval for memory writes until keyed-HMAC integrity is enabled',
            },
        });
    }
    return findings;
}
/**
 * ASI-07 Insecure Inter-Agent Communication.
 *
 * Fires when an agent participates in agent-to-agent messaging (delegation /
 * handoff / multi-agent orchestration) but has NOT enabled per-message
 * Ed25519 signing. Handshake-only identity binding is insufficient: a
 * compromised relay or man-in-the-middle can forge or inject messages once
 * the session is established. The remediation is to enable AgentMessageEnvelope
 * signing on the orchestrator adapter so every inter-agent message is signed
 * and verified (signature + nonce-replay + clock-skew).
 *
 * No finding when the agent does not do inter-agent comms, or when signing is
 * already enabled.
 */
function analyzeASI07InterAgentSigning(m) {
    // Only relevant to agents that actually exchange messages with other agents.
    if (m.interAgentComms !== true) {
        return [];
    }
    if (m.interAgentSigningEnabled === true) {
        return [];
    }
    return [
        {
            risk: OWASP_AGENTIC_RISK.ASI07_INSECURE_INTER_AGENT_COMMS,
            severity: 'HIGH',
            title: 'Inter-agent messages are unauthenticated past the handshake (no per-message signing)',
            detail: 'This agent participates in agent-to-agent messaging (delegation / handoff / ' +
                'multi-agent orchestration) but has not enabled per-message Ed25519 signing. ' +
                'Identity is bound only at the initial handshake, so a compromised relay, a ' +
                'man-in-the-middle, or a co-resident malicious agent can forge or inject ' +
                'messages mid-session without detection (ASI-07 insecure inter-agent ' +
                'communication vector).',
            remediation: 'Enable inter-agent message signing on the orchestrator adapter by providing ' +
                '`interAgentSigning` in OrchestratorAdapterConfig (signingKeyPem + ' +
                'resolvePublicKey). The base adapter then signs every outbound delegation ' +
                'message with AgentMessageEnvelope (src/security/agent-message-envelope.ts) ' +
                'and verifies inbound ones (signature + nonce-replay + clock-skew). ' +
                'Verification never throws and never blocks the agent path (Invariants I-2/I-3); ' +
                'a failed verification maps to a WARN or BLOCK governance decision per ' +
                'the adapter enforcement mode.',
            suggestedRule: {
                capability: CAPABILITY_CLASS.NETWORK_EGRESS,
                action: 'REQUIRE_APPROVAL',
                note: 'ASI-07: add REQUIRE_APPROVAL for inter-agent network egress until per-message signing is enabled',
            },
        },
    ];
}
/**
 * ASI-10 Rogue Agents — replaces the A10-model-theft stub.
 *
 * A10 (Model Theft) is a platform-infrastructure concern (audit chain hash +
 * cross-tenant RLS) enforced platform-side per Locked Invariant 7 — no
 * per-agent scanner finding needed there. Instead this slot is used for the
 * ASI-10 Rogue Agent controls gap: the operator kill-switch, revocation
 * capability, and autonomy-level checks that are genuinely per-agent.
 *
 * Three checks:
 *   1. Kill-switch (HIGH): no forceOpen/suspend capability → rogue agent
 *      cannot be hard-stopped without process restart.
 *   2. Revocation (MED): no revocation list entry capability → compromised
 *      passport cannot be invalidated without TTL expiry.
 *   3. Autonomy overreach (HIGH for strict packs): autonomy level > 3 when
 *      HIPAA / PCI DSS-class tools are declared → unchecked autonomous action
 *      in regulated domains.
 *
 * All three emit suggestedRules so the auto-generated company pack can
 * surface remediation to the customer.
 */
function analyzeASI10RogueAgent(m) {
    const findings = [];
    // Check 1: Kill-switch capability (HIGH severity).
    if (!m.killSwitchCapable) {
        findings.push({
            risk: OWASP_AGENTIC_RISK.ASI10_ROGUE_AGENT,
            severity: 'HIGH',
            title: 'No operator kill-switch: agent cannot be hard-stopped at runtime',
            detail: 'This agent has no declared kill-switch capability. Without a ' +
                'forceOpen(agentId) / POST /api/v1/agents/:id/suspend endpoint, a rogue ' +
                'or compromised agent can only be stopped by restarting the entire process. ' +
                'In production, this means the blast radius of a rogue agent is bounded only ' +
                'by the process lifetime (ASI-10 rogue-agent vector).',
            remediation: 'Register this agent on a governance platform that exposes ' +
                'POST /api/v1/agents/:id/suspend (ORG_ADMIN). The CircuitBreaker ' +
                'at packages/governance-proxy/src/security/circuit-breaker.ts now ' +
                'implements forceOpen(agentId). Wire the suspend endpoint to call ' +
                'CircuitBreaker.forceOpen(agentId) so that a suspended agent is ' +
                'immediately blocked at the proxy pre-flight, not just marked in the registry.',
            suggestedRule: {
                capability: CAPABILITY_CLASS.SHELL_EXEC,
                action: 'REQUIRE_APPROVAL',
                note: 'ASI-10: add REQUIRE_APPROVAL for shell execution until kill-switch is wired',
            },
        });
    }
    // Check 2: Revocation capability (MED severity).
    if (!m.revocationCapable) {
        findings.push({
            risk: OWASP_AGENTIC_RISK.ASI10_ROGUE_AGENT,
            severity: 'MED',
            title: 'Passport revocation not declared: compromised credentials persist until TTL',
            detail: 'This agent\'s passport has no declared revocation capability. If the agent ' +
                'credential is compromised, it remains valid until TTL expiry (up to 24h for ' +
                'standard passports). A revocation list backed by the governance registry ' +
                '(RevocationListEntry in passport-signer.ts) allows instant invalidation ' +
                'without waiting for TTL.',
            remediation: 'Ensure the agent\'s deployment org has a RevocationListEntry store wired ' +
                'to POST /api/v1/agents/:id/suspend. The suspend endpoint should also add ' +
                'the agent\'s active passport_ids to the revocation list so verifiers ' +
                '(verifyPassportOffline) reject the credential immediately.',
            suggestedRule: {
                capability: CAPABILITY_CLASS.NETWORK_EGRESS,
                action: 'REQUIRE_APPROVAL',
                note: 'ASI-10: add REQUIRE_APPROVAL for network egress until revocation is wired',
            },
        });
    }
    // Check 3: Autonomy overreach in strict-pack contexts (HIGH severity).
    // Strict packs are those with regulatory mandates for human oversight.
    const strictPackTools = ['hipaa', 'pci_dss', 'eu_ai_act'];
    const hasStrictPack = (m.systemPrompt ?? '')
        .toLowerCase()
        .split(/\s+/)
        .some((w) => strictPackTools.some((s) => w.includes(s)));
    // Infer strict context from tool names too (HIPAA/PCI agents often
    // have phi_ or pci_ tool prefixes).
    const strictToolPrefix = m.tools.some((t) => t.toLowerCase().startsWith('phi_') ||
        t.toLowerCase().startsWith('pci_') ||
        t.toLowerCase().includes('patient') ||
        t.toLowerCase().includes('cardholder'));
    if (typeof m.autonomyLevel === 'number' &&
        m.autonomyLevel > 3 &&
        (hasStrictPack || strictToolPrefix)) {
        findings.push({
            risk: OWASP_AGENTIC_RISK.ASI10_ROGUE_AGENT,
            severity: 'HIGH',
            title: `Autonomy level ${m.autonomyLevel}/5 exceeds threshold for regulated-domain deployment`,
            detail: `This agent declares autonomy level ${m.autonomyLevel} (>3) and operates in ` +
                `a regulated context (HIPAA / PCI DSS / EU AI Act indicators detected). ` +
                `Fully autonomous action (>3) in regulated domains creates an unchecked ` +
                `blast radius for rogue behaviour: a single adversarial prompt or supply-chain ` +
                `compromise can trigger unmonitored access to PHI / cardholder data.`,
            remediation: 'Reduce autonomy level to ≤3 for regulated-domain agents, or add a ' +
                'REQUIRE_APPROVAL gate for all high-risk capability classes. ' +
                'Consider wiring HITL notification (HITL_NOTIFY_DISPATCHED) for every ' +
                'action touching patient or cardholder data.',
            suggestedRule: {
                capability: CAPABILITY_CLASS.EXTERNAL_DB_WRITE,
                action: 'REQUIRE_APPROVAL',
                note: `ASI-10: autonomy ${m.autonomyLevel}/5 in regulated context — require approval for external DB writes`,
            },
        });
    }
    // F-5 / ASI-10: SDK-embedded-no-proxy bypass gap (documentation finding).
    //
    // The proxy-tier forceOpen() kill-switch is effective ONLY when traffic flows
    // through `packages/governance-proxy`. An SDK-embedded agent with no proxy
    // bypasses the proxy PreToolUse gate entirely.
    //
    // ASI-10 part 2 (SDK path): the gap is now CLOSED when sdkKillSwitchEnabled=true,
    // meaning the GovernedAgent.execute() PreToolUse path checks
    // circuitBreaker.allowExecutionForAgent(agentId) with a SuspendStateCache keeping
    // the forced-OPEN set current. The LOW finding is ONLY emitted when NEITHER proxy
    // routing NOR the SDK kill-switch is active.
    //
    // Condition: killSwitchCapable=true (platform supports the kill-switch endpoint)
    // AND proxyRouted is false/absent (agent traffic does not pass through the proxy)
    // AND sdkKillSwitchEnabled is false/absent (SDK path enforcement is not active).
    if (m.killSwitchCapable && !m.proxyRouted && !m.sdkKillSwitchEnabled) {
        findings.push({
            risk: OWASP_AGENTIC_RISK.ASI10_ROGUE_AGENT,
            severity: 'LOW',
            title: 'Kill-switch partially covered: agent is SDK-embedded without proxy or SDK enforcement',
            detail: 'This agent is registered on a kill-switch-capable platform ' +
                '(POST /api/v1/agents/:id/suspend is available) but its traffic does NOT ' +
                'route through the governance proxy, and the SDK-path kill-switch ' +
                '(GovernedAgentConfig.circuitBreaker + SuspendStateCache) is not configured. ' +
                'The proxy-tier CircuitBreaker.forceOpen() shortcut and the SDK PreToolUse ' +
                'kill-switch check are both absent. The kill-switch enforces only via the ' +
                'registry lifecycleState path, which depends on the SDK adapter or LLM provider ' +
                'honouring the lifecycleState === \'suspended\' flag on each call. ' +
                'A rogue SDK-embedded agent without either enforcement layer can continue ' +
                'executing until the next restart.',
            remediation: 'Option A (proxy): Route this agent\'s traffic through the governance proxy ' +
                '(packages/governance-proxy) so that POST /suspend immediately triggers ' +
                'CircuitBreaker.forceOpen(agentId) and blocks all proxy-level traffic. ' +
                'Option B (SDK): Pass a CircuitBreaker instance to GovernedAgentConfig.circuitBreaker ' +
                'and start a SuspendStateCache (src/security/suspend-state-cache.ts) that polls ' +
                'the governance server and propagates suspend state to the breaker\'s forced-OPEN set. ' +
                'Set GovernedAgentConfig.sdkKillSwitchEnabled=true once both are wired.',
            suggestedRule: {
                capability: CAPABILITY_CLASS.SHELL_EXEC,
                action: 'REQUIRE_APPROVAL',
                note: 'ASI-10: SDK-embedded-no-proxy gap — require approval for shell exec until proxy or SDK kill-switch is in place',
            },
        });
    }
    return findings;
}
// ---------------------------------------------------------------------------
// 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';
}
/**
 * Risk findings derived from a declarative agent manifest.json (the fleet
 * convention). The code-based A01..A10 analyzers run against the built
 * AgentManifest (capabilities inferred from source); this complements them by
 * reading the risk the manifest *declares about itself* — unrestricted tool
 * scopes, disabled audit logging, wildcard egress, sensitive-data handling.
 * These are the "issues / missing items" surfaced on the onboarding scan page.
 *
 * Pure + defensive — NEVER throws on a malformed/partial manifest (Invariant 10).
 *
 * @param raw the parsed manifest.json object (any shape)
 */
export function analyzeManifestRisks(raw) {
    const findings = [];
    if (!raw || typeof raw !== 'object')
        return findings;
    const m = raw;
    const tools = Array.isArray(m['tools']) ? m['tools'] : [];
    const handlesSensitiveData = tools.some((t) => {
        const dc = typeof t?.['data_class'] === 'string' ? t['data_class'].toLowerCase() : '';
        return dc === 'phi' || dc === 'pii' || dc === 'pci' || dc === 'phi/pii';
    });
    // 1. Audit logging disabled — actions are unobservable (no governance trail).
    const audit = typeof m['audit_logging'] === 'string' ? m['audit_logging'].toLowerCase() : m['audit_logging'];
    if (audit === 'disabled' || audit === 'off' || audit === false || audit === 'none') {
        findings.push({
            risk: OWASP_AGENTIC_RISK.A08_EXCESSIVE_AGENCY,
            severity: 'HIGH',
            title: 'Audit logging disabled — agent actions are unobservable',
            detail: 'The manifest declares audit_logging is off, so tool calls and decisions leave no ' +
                'tamper-evident trail. Governance cannot attribute or review what this agent did.',
            remediation: 'Bind a pack that emits the audit chain (every governed agent does) and enable ' +
                'audit logging — required for HIPAA/SOC2/GDPR evidence.',
        });
    }
    // 2. Unrestricted / dangerous tools (excessive agency).
    const DANGEROUS = new Set(['shell_exec', 'file_write', 'network_request', 'exec', 'code_exec', 'eval']);
    for (const t of tools) {
        const name = typeof t?.['name'] === 'string' ? t['name'] : '';
        const scope = typeof t?.['scope'] === 'string' ? t['scope'].toLowerCase() : '';
        const unrestricted = scope.includes('unrestricted') || scope === 'any' || scope === 'filesystem_unrestricted';
        if (DANGEROUS.has(name) || unrestricted) {
            findings.push({
                risk: OWASP_AGENTIC_RISK.A08_EXCESSIVE_AGENCY,
                severity: name === 'shell_exec' || scope === 'filesystem_unrestricted' ? 'CRIT' : 'HIGH',
                title: `Unrestricted tool: ${name || '(unnamed)'}${scope ? ` (scope: ${scope})` : ''}`,
                detail: `The manifest grants the agent a high-blast-radius capability (${name || 'tool'}` +
                    `${scope ? `, scope "${scope}"` : ''}) with no declared constraint. Combined with ` +
                    'untrusted input this is a direct path to consequential harm.',
                remediation: 'Set a per-rule action of REQUIRE_APPROVAL or BLOCK for this capability in the ' +
                    'bound pack, or narrow the tool scope to the minimum it needs.',
            });
        }
    }
    // 3. Wildcard egress — unrestricted outbound network.
    const domains = Array.isArray(m['allowed_domains']) ? m['allowed_domains'] : [];
    if (domains.some((d) => d === '*' || (typeof d === 'string' && d.includes('*')))) {
        findings.push({
            risk: OWASP_AGENTIC_RISK.A08_EXCESSIVE_AGENCY,
            severity: 'HIGH',
            title: 'Unrestricted network egress (allowed_domains: *)',
            detail: 'The manifest allows outbound requests to any domain, enabling data exfiltration ' +
                'and SSRF-style pivots if the agent is compromised via prompt injection.',
            remediation: 'Replace the wildcard with an explicit allow-list of required domains.',
        });
    }
    // 4. Sensitive-data exposure — PHI/PII handling, especially with logging declared.
    const phiHandling = typeof m['phi_handling'] === 'string' ? m['phi_handling'].toLowerCase() : '';
    const logsSensitive = /\blog/.test(phiHandling);
    if (logsSensitive || (handlesSensitiveData && (audit === 'disabled' || audit === 'off'))) {
        findings.push({
            risk: OWASP_AGENTIC_RISK.A06_SENSITIVE_DISCLOSURE,
            severity: 'CRIT',
            title: 'Sensitive data (PHI/PII) exposure risk',
            detail: logsSensitive
                ? `The manifest declares sensitive-data handling that logs records ("${String(m['phi_handling'])}").`
                : 'The agent handles PHI/PII-classed data but audit logging is disabled, so sensitive access is untracked.',
            remediation: 'Bind the relevant compliance pack (HIPAA / GDPR) and route sensitive-data tools through ' +
                'PII Shield + REQUIRE_APPROVAL; never log raw records.',
        });
    }
    // 5. High concurrent action ceiling — blast radius / DoS.
    const maxConcurrent = typeof m['max_concurrent_actions'] === 'number' ? m['max_concurrent_actions'] : 0;
    if (maxConcurrent > 5) {
        findings.push({
            risk: OWASP_AGENTIC_RISK.A04_DOS,
            severity: 'MED',
            title: `High concurrent-action ceiling (${maxConcurrent})`,
            detail: 'A high concurrency limit increases blast radius if the agent loops or is hijacked, ' +
                'and can exhaust downstream rate limits/budgets.',
            remediation: 'Lower max_concurrent_actions to the minimum the workload needs.',
        });
    }
    return findings;
}
/**
 * Run all A01..A10 analyzers against a manifest and produce a structured report.
 */
export function scanAgent(manifest) {
    const findings = [
        ...analyzeA01PromptInjection(manifest),
        ...analyzeA02InsecureOutput(manifest),
        ...analyzeA03RagPoisoning(manifest),
        ...analyzeA04Dos(manifest),
        ...analyzeA05SupplyChain(manifest),
        ...analyzeA06SensitiveDisclosure(manifest),
        ...analyzeA07InsecurePlugin(manifest),
        ...analyzeA08ExcessiveAgency(manifest),
        ...analyzeA09Overreliance(manifest),
        ...analyzeASI10RogueAgent(manifest),
        ...analyzeASI08CascadingFailures(manifest),
        ...analyzeASI07InterAgentSigning(manifest),
        ...analyzeASI06MemoryContext(manifest),
        ...analyzeASI05CodeExecution(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] : [])));
    const asiCoverage = computeAsiCoverage(findings);
    const asiAssessedCount = asiCoverage.filter((e) => e.assessed).length;
    return {
        agentId: manifest.agentId,
        findings,
        countsBySeverity: counts,
        suggestedRules,
        buildStructureScore: score,
        verdict: verdictFromScore(score, counts.CRIT),
        asiCoverage,
        asiAssessedCount,
        coverageComplete: asiAssessedCount === 10,
    };
}
/**
 * Compute per-ASI-item coverage from the produced findings + the static
 * assessed-items set. Component #1 of the Trust Score: the build axis
 * must never imply coverage it does not have, so an item with no analyzer is
 * reported `assessed:false` rather than silently "clean".
 */
function computeAsiCoverage(findings) {
    const sevRank = { LOW: 1, MED: 2, HIGH: 3, CRIT: 4 };
    return Object.keys(ASI_TOP_10).map((asi) => {
        const risks = ASI_RISK_COVERAGE[asi] ?? [];
        const itemFindings = findings.filter((f) => risks.includes(f.risk));
        let worst = null;
        for (const f of itemFindings) {
            if (worst === null || sevRank[f.severity] > sevRank[worst])
                worst = f.severity;
        }
        return {
            asi,
            name: ASI_TOP_10[asi],
            assessed: ASI_ASSESSED_ITEMS.has(asi),
            findingCount: itemFindings.length,
            worstSeverity: worst,
            remediations: itemFindings.map((f) => ({
                severity: f.severity,
                title: f.title,
                detail: f.detail,
                remediation: f.remediation,
            })),
        };
    });
}
//# sourceMappingURL=owasp-agentic-scanner.js.map