Skip to content

OWASP mapping

How the controls in this repository line up against three OWASP lists. Written to be checked, not to reassure: every "Covered" or "Partly" row names the file, config key and test it rests on, and anything I could not point to in code is marked Not covered.

List Edition Source
OWASP Top 10 for Agentic Applications (ASI01-ASI10) 2026, published 2025-12-09 https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/
OWASP Top 10 for LLM Applications (LLM01-LLM10) 2025 https://genai.owasp.org/llm-top-10/
OWASP MCP Top 10 (MCP01-MCP10) 2025, beta https://owasp.org/www-project-mcp-top-10/

How to read the status

Status Meaning
Covered a control exists, is on by default, has a test, and I found no bypass in the code I read
Partly a control exists but at least one of: no test, off by default, fails open, regex-only, or a known bypass. A control without a test is always Partly
Not covered I found no control. A neighbouring control may help; it is named in the note

Paths are repo-relative, as of the audit date (2026-10-14, before the ADR-0023 split). G = core/guardrails/src/main/java/com/agenticai/core/guardrails, A = core/agents/src/main/java/com/agenticai/core/agents, T = A/tools (most T classes now live under core/agents-tools or core/agents-browser in the same package). "Tests" lists test classes that exercise the control; I did not audit assertion depth for each.

Method and limits

  • Evidence comes from reading the code in this working tree on 2026-10-14. Nothing was run on a stack; Java tests named here were not all re-run (the ones for the new controls were).
  • The Agentic list's entry titles were confirmed from a secondary source (Modulos' framework page) because the OWASP resource page serves only a download link; the official PDF was not opened. IDs ASI01 and its title were confirmed from several independent summaries. If a title differs from the PDF, the ID is the authority.
  • The MCP list is a beta project and may be reordered.
  • The older handbook pages (docs/platform-documentation/security/*.html) make no OWASP-ID claims; this page does not rely on them for coverage.
  • The MCP tool-pinning work (McpToolPin*, V491__mcp_tool_pinning.sql) was uncommitted when this mapping was first written. It has since been committed (with unit and integration tests and the app.mcp.tool-pinning.mode key); the rows below were updated on 2026-10-15 from the code in the repository, not by re-running the tests. It stays scored Partly.

Changes made with this mapping

Three controls were added while writing it, each with tests:

Control Closes part of Where Config
Operator egress allow-list for tenant-configured outbound URLs ASI02, LLM06, MCP09 core/security/.../net/SsrfGuard.java (egressPermits), SsrfGuardConfiguration.java; SsrfGuardEgressAllowListTest app.security.outbound.egress-allow-list (empty = any public host)
Write-time screen on add_to_knowledge_base (injection phrases, credentials) ASI06, LLM04, LLM02 core/knowledge/.../KnowledgeWriteScreen.java, AddToKnowledgeBaseToolCallback.java; AddToKnowledgeBaseToolCallbackTest none (always on)
Per-turn cap on remote A2A calls ASI07, ASI08, LLM10 A/a2a/A2aRemoteAgentToolCallback.java, A2aProperties.Client; A2aClientServiceTest app.a2a.client.max-calls-per-turn (default 5, 0 = off)

The egress list is platform-wide, not per agent. Per-agent egress remains open (issue 01).

Where an operator sees these

Control UI
Egress allow-list, private-host exceptions, A2A client and server settings, per-turn A2A cap Platform Admin -> Safety & Moderation -> "Outbound allow-list" and "Agent-to-agent (A2A)" panels (web/app/platform-admin/components/sections/outbound-policy.tsx), read-only, backed by GET /api/platform/security/outbound-policy (OutboundPolicyController). Read-only on purpose: these are environment settings, and a stolen admin session must not be able to widen egress
A2A REST binding Listed in both Agent Cards' supportedInterfaces and in the console's API reference page
Knowledge-base write refusal No settings UI (always on). The refusal text is the tool result, so it appears in the agent's activity trace and playground
Per-agent egress list Not built (issue 01 includes the agent-editor field)

OWASP Agentic Top 10 (2026)

ID Item Status Controls Tests Gap
ASI01 Agent Goal Hijack Partly Hard-block phrases and secret redaction on tool output: G/PatternBasedGuardrailService. LLM injection judge: G/SemanticGuardrailService (app.guardrails.implementation=semantic). Jailbreak detector: G/impl/JailbreakDetectorGuardrail (app.agents.default-guardrails). Input moderation: A/ChatGuardrailGate, G/moderation/ModerationService. Lethal-trifecta detector: A/tools/LethalTrifectaDetector PatternBasedGuardrailServiceTest, SemanticGuardrailServiceTest, JailbreakDetectorGuardrail*Test, ModerationServiceTest, LethalTrifectaDetectorTest The semantic judge fails closed by default on error or a blank answer (app.guardrails.fail-mode; the output is withheld, open falls back to pattern-only); a missing client is pattern-only. The pattern layer is regex-only. No structural separation of tool output from instructions. app.tools.classification.trifecta-enforcement defaults to warn. ChatGuardrailGate has no direct test
ASI02 Tool Misuse and Exploitation Partly Per-turn step cap, approval gate, verifier, output size cap: G/GuardedToolCallback. Dispatch gate: G/ToolDispatchGate implemented by A/tools/AgentToolGovernance. Argument policies: G/toolargs/*, A/tools/McpToolArgumentGuard. Confirmation gate app.tools.classification.confirmation-gate. New: egress allow-list (platform-wide) GuardedToolCallbackGovernanceTest, ToolArgPolicy*Test, McpToolArgumentGuardTest, SsrfGuardEgressAllowListTest Tool-arg policies are on in the base profile since 2026-10-23 (app.tool-arg-policies.enabled; inert until a policy is saved; off in personal). The dispatch gate fails closed on a lookup error by default (GuardedToolCallback.checkDispatchGate, app.guardrails.fail-mode; open restores the old fail-open). The RIGOROUS tool-call verifier follows the same mode by default (app.guardrails.tool-verifier.fail-mode=inherit), is shown the last app.guardrails.tool-verifier.context-messages (default 6) messages of the conversation as context for the current user message (0 restores judging the message alone), and runs only for RIGOROUS agents. A tool-arg policy lookup that throws now follows app.guardrails.fail-mode too (refused when closed, the default; it used to let the call through whatever the mode; an evaluation that throws on a matching policy refuses it in both modes; ToolArgPolicyCallback, ToolArgPolicyCallbackTest). McpToolArgumentGuard calls itself a coarse tripwire. No per-agent egress
ASI03 Identity and Privilege Abuse Partly Delegation tokens: core/security/.../DelegationTokenAuthFilter, A/trust/DelegationTokenService. Permission matchers: core/security/.../SecurityConfig (PERM_*, see authorization.md). Secrets bound to hosts: core/secrets/.../SecretHostPolicy. Autonomy tiers: A/AgentAutonomyTier DelegationTokenAuthFilterTest, DelegationTokenServiceTest, SecretHostPolicyTest, HttpRequestExecutorSecurityTest Per-tool scope narrowing of delegated credentials was not traced. A/trust/CapabilityManifestService has no test
ASI04 Agentic Supply Chain Vulnerabilities Partly Skill scanning and content-hash pinning: A/skills (SkillScanner, SkillSupplyChainService, app.agents.skills.supply-chain.*). MCP tool name/description clamping: A/tools/McpToolOutputSanitizer, NamespacedMcpToolCallback. MCP tool pinning: core/mcp/.../McpToolPin*, T/McpToolPinGate (app.mcp.tool-pinning.mode). Release signing, SBOM, provenance: .github/workflows/release.yml. Scanners: trivy.yml, license-scan.yml, scorecard.yml, codeql.yml, .github/dependabot.yml SkillSupplyChainServiceTest, SkillScannerTest, SkillIntegrityTest, NamespacedMcpToolCallbackTest, McpToolOutputSanitizerTest, McpToolPinServiceTest, McpToolPinIntegrationTest Tool pinning defaults to warn (record and report; enforce withholds a changed server's tools until approved). Workflow behaviour not exercised here
ASI05 Unexpected Code Execution Partly Launcher allow-list and metacharacter block: core/mcp/.../McpStdioCommandGuard. Env isolation: A/tools/EnvIsolatedStdioClientTransport. Sidecar with seccomp profile (Compose files only; Helm uses RuntimeDefault plus a required VM-isolating RuntimeClass) and isolated network: services/mcp-stdio-runner. Code sandbox: services/tool-sandbox. Design: docs/architecture/MCP-STDIO-SANDBOXING-DESIGN.md McpStdioCommandGuardTest, McpStdioParamsBuilderTest, services/mcp-stdio-runner/server.test.js, services/tool-sandbox/tests The stdio sidecar is off by default (mcp-stdio-sidecar.enabled, env MCP_STDIO_SIDECAR_ENABLED). Without it npx <package> runs package code in the core container; the repo's own threat model says so. Since the image split the release core image is built with MCP_TOOLCHAIN=none (no Node/uv in core, so STDIO servers need the runner); only core-full carries the in-core toolchain (core/Dockerfile, release.yml)
ASI06 Memory and Context Poisoning Partly Per-subject admission caps: A/memory/OwnFactAdmission. Session ids scoped by tenant, agent, subject: A/memory/CallerSessionIds. Provenance: A/memory/MemoryProvenanceService. Write arbitration: core/graphmemory/.../MemoryWriteArbitrationService. New: KB write screen KnowledgeWriteScreen OwnFactAdmissionTest, MemoryProvenanceServiceTest, MemoryWriteArbitrationServiceTest, AddToKnowledgeBaseToolCallbackTest remember_fact and graph-memory writes have no injection scan (issue 05). The KB screen is a regex tripwire; a paraphrased directive passes. RememberFactToolCallback: no test found
ASI07 Insecure Inter-Agent Communication Partly Outbound: signed-card verification (A/a2a/A2aCardVerifier, require-signed-card=true), host allow-list (A/a2a/A2aEgressGuard), untrusted-data framing (A2aClientService.sanitizeRemoteText), new per-turn call cap. Inbound: bearer JWT + RUNNER check (A2aJsonRpcController, A2aRestController); cards signed (A2aCardSigningService) A2aCardVerifierTest, A2aCardVerifierSsrfTest, A2aClientServiceTest, A2aJsonRpcControllerTest, A2aRestControllerTest The outbound client is off by default and per-agent opt-in. No message-level signing or replay protection on A2A requests (transport auth only). No loop/hop counter across instances
ASI08 Cascading Failures Partly Circuit breaker: A/tools/ToolCircuitBreakerService. USD run budget: A/tools/RunBudgetToolCallback, A/collab/RunBudget (app.guardrails.run-budget.usd). Idempotent replay: A/tools/EffectJournal. Spend caps: A/spendcap/SpendCapService. Step cap, delegation depth 1, 8 team branches (see Limits) ToolCircuitBreakerServiceTest, RunBudgetTest, StepJournalTest, GuardedToolCallbackGovernanceTest, DelegateToTeamToolCallbackTest The USD budget covers delegation tools only. A sub-agent gets its own step counter. The depth-1 delegation cap has no direct test
ASI09 Human-Agent Trust Exploitation Partly Approval gate: core/approval/.../HumanApprovalGateService, ApprovalDecisionAuthorizer, ApprovalReplayGrant. Plain-language confirmation text: G/ToolConfirmationDescriber HumanApprovalGateServiceTest, ApprovalControllerTest, ApprovalReplayGrantTest, GuardedToolCallbackConfirmationSummaryTest Fully-autonomous agents skip the classification gate except for certain irreversible send tools. Nothing checks that the approval summary matches what will execute beyond the describer
ASI10 Rogue Agents Partly Quarantined/unclassified tools denied: AgentToolGovernance.decide. Attestation: A/attestation/AgentAttestationService. Tamper-evident audit: core/audit/.../seal/AuditSealService. Per-run budget AuditSealSweepTest, AuditSealA2aSignerTest No behavioural anomaly detection. Attestation has little direct test coverage

OWASP LLM Top 10 (2025)

ID Item Status Controls Tests Gap
LLM01 Prompt Injection Partly As ASI01; plus control-character stripping (A/tools/McpToolOutputSanitizer) and the untrusted-data wrapper on remote A2A replies as ASI01 Regex plus a judge that fails closed by default; no structural separation
LLM02 Sensitive Information Disclosure Partly PII guardrail G/impl/PiiGuardrail; secret/PII redaction of tool output (PatternBasedGuardrailService, AgentToolGovernance.outputRedactorFor); core/secrets/.../SecretRedactor; A/tools/McpSecretRedactor; AES-256-GCM at rest core/crypto/.../EncryptionService; log masking core/security/.../LogMasking; new KB write screen refuses credentials ContentGuardrailsImplementationTest, SecretRedactorTest, EncryptionServiceTest, LogMaskingTest, AddToKnowledgeBaseToolCallbackTest Redaction patterns are a fixed regex list
LLM03 Supply Chain Partly As ASI04; licence allow-list and drift check scripts/ci/license-allowlist.txt, license-drift-check.py; gitleaks scripts/ci/secret-scan.sh none for the CI scripts No model-weight or model-provider provenance control
LLM04 Data and Model Poisoning Partly Skill scanner (A/skills); memory admission caps; new KB write screen skill tests, AddToKnowledgeBaseToolCallbackTest No scan of documents uploaded to knowledge collections (human upload is trusted by design); no embedding-poisoning control
LLM05 Improper Output Handling Partly Tool-output size bound: a result over app.tools.output.max-chars (20,000, per-tool overrides) is truncated with a marker that says how much was cut (G/ToolOutputProperties; the former JSON-well-formedness check and "withhold the result" behaviour were removed, G/ToolCallOutputValidator now only holds the log marker); content guardrail engine G/ContentGuardrailEngine ContentGuardrailEngineTest Web rendering of model output (CSP, escaping) not examined
LLM06 Excessive Agency Partly As ASI02 and ASI09; per-agent tool allow-list Agent.allowedToolNames; autonomy tiers; new egress allow-list as ASI02 An empty tool allow-list means unrestricted (documented in AgentIntegrationToolResolver)
LLM07 System Prompt Leakage Not covered (output side) Only an input-side tripwire for "reveal your system prompt" in G/PatternBasedGuardrailService and JailbreakDetectorGuardrail JailbreakDetector*Test No filter on model output for system-prompt content; no canary. Prompts are not treated as secrets
LLM08 Vector and Embedding Weaknesses Partly Schema-per-tenant isolation (core/tenancy, SchemaNameSanitizer); Cypher literal escaping core/graphmemory/.../CypherLiteralEscaper CypherLiteralEscaperTest, GraphMemoryServiceTest Knowledge isolation tests not audited; no embedding-inversion control
LLM09 Misinformation Partly A/AnswerConsistencyChecker, A/LlmQualityJudgeService, A/ReflexionService, A/ragquality AnswerConsistencyCheckerTest Quality gates are sampled/optional
LLM10 Unbounded Consumption Covered Tool-step cap app.guardrails.max-tool-calls-per-turn (15, scaled by autonomy tier); USD run budget; app.agents.default-max-tokens; per-user budget A/PerUserBudgetService; spend caps; HTTP rate limits core/security/.../ratelimit; body size limit RequestBodySizeLimitFilter; MCP server rate limit McpServerRateLimiter; new A2A call cap GuardedToolCallbackGovernanceTest, PerUserBudgetServiceTest, RequestBodySizeLimitFilterTest, McpServerRateLimiterTest, A2aClientServiceTest The step cap has no per-tenant tier; a sub-agent has its own counter (bounded: depth 1 x 8 branches)

OWASP MCP Top 10 (2025, beta)

ID Item Status Controls Tests Gap
MCP01 Token Mismanagement and Secret Exposure Partly Env-isolated stdio spawn A/tools/EnvIsolatedStdioClientTransport; A/tools/McpSecretRedactor; A/tools/McpRemoteHeaders; vault core/secrets/.../SecretVaultService; envelope encryption core/crypto/.../EnvelopeEncryptionService; hashed MCP keys core/mcpserver/.../McpApiKeyHasher McpRemoteHeaders*Test, SecretVaultServiceTest, EnvelopeEncryptionServiceTest EnvIsolatedStdioClientTransport has no dedicated test that I found
MCP02 Privilege Escalation via Scope Creep Partly Per-key grants core/mcpserver/.../McpServerAccessGrant; PERM_MANAGE_INTEGRATIONS McpApiKeyAuthFilterExposureTest, McpServerStreamableHttpContractTest Per-tool scoping for the inbound MCP server not traced
MCP03 Tool Poisoning Partly Name/description clamp (A/tools/SanitizedMcpToolCallback, McpToolOutputSanitizer); server namespacing (NamespacedMcpToolCallback); tool pinning McpToolPin* (committed; warn by default) SanitizedMcpToolCallbackTest, McpToolOutputSanitizerTest, NamespacedMcpToolCallbackTest, McpToolPinServiceTest, McpToolPinTest, McpToolPinControllerTest, McpToolPinIntegrationTest Config key app.mcp.tool-pinning.mode (off, warn, enforce; default warn, see mcp-spec-alignment.md). Default warn only records and reports; enforce is opt-in. Annotations are not pinned, and static YAML-wired servers were not checked (issue 06)
MCP04 Software Supply Chain and Dependency Tampering Partly As ASI04 as ASI04 as ASI04
MCP05 Command Injection and Execution Partly McpStdioCommandGuard; stdio sidecar McpStdioCommandGuardTest Sidecar off by default (issue 04)
MCP06 Prompt Injection via Contextual Payloads Partly As LLM01 as ASI01 as LLM01
MCP07 Insufficient Authentication and Authorization Partly Inbound: core/mcpserver/.../McpApiKeyAuthFilter. Outbound: origin pinning A/tools/McpOriginPinning, URL guard core/mcp/.../McpRemoteUrlGuard, pre-probe A/tools/RemoteMcpPreProbe McpApiKeyAuthFilterTest, McpOriginPinningTest, McpRemoteUrlGuardTest, RemoteMcpPreProbeTest, McpGuardedSocketTest, GuardedHttpClientBuilderTest The remote pre-probe now goes through the address-pinned HTTP client (see the CHANGELOG, Security). The SDK's own connection no longer has a DNS-rebinding window: the transports are built on the address-pinned client (GuardedHttpClientBuilder), so each connect resolves through SsrfGuard and goes to the validated address (McpGuardedSocketTest). Open: the pre-buffer response-size cap (issue 07). Not re-verified on a live stack
MCP08 Lack of Audit and Telemetry Partly core/audit/.../AuditLogService, audit/seal/*; tool-call logging A/tools/LoggingToolCallback; metrics agent.tool.calls, agent.tool.errors in GuardedToolCallback AuditSealSweepTest, LoggingToolCallback*Test application.yml contains tool-call-log.enabled: false at a path I did not trace to the live default; check before relying on persisted tool logs
MCP09 Shadow MCP Servers Partly Registration with review status core/mcp/.../McpReviewStatus, McpRegistryService; exposure policy A/tools/StaticMcpExposurePolicy; PERM_MANAGE_INTEGRATIONS for registering; new egress allow-list McpRegistryReviewServiceTest, StaticMcpExposurePolicyTest, McpRegistryServiceTest, SsrfGuardEgressAllowListTest Egress list is platform-wide and off by default; no discovery of unregistered servers
MCP10 Context Injection and Over-Sharing Partly Delegates see only the question plus a short summary A/ChatDelegateCall; tenant context propagation TenantContextPropagatingToolCallback; tool selection cap ToolRelevanceSelector (app.tools.auto-selection.max-tools-per-turn) ToolRelevanceSelectorTest No MCP-specific context-minimisation test

Existing limits on depth and fan-out

Limit Where Value Test
Tool calls per turn G/GuardedToolCallback.guardedCallBody: calls over the ceiling are not run and get the tool_budget_exhausted result, app.guardrails.step-limit.grace-calls later it throws ToolStepLimitExceededException; app.guardrails.max-tool-calls-per-turn 15; /2 (min 3) supervised, x2 fully autonomous, both configurable (AgentToolGuard.effectiveMaxStepsFor, app.guardrails.step-limit.*) GuardedToolCallbackStepLimitTest, GuardedToolCallbackGovernanceTest
Delegation depth AgentToolProvider.resolveToolsForDelegatedCall passes allowDelegation=false 1 none direct
Crew handoff depth A/crew/HandoffChain.MAX_DEPTH + cycle check 3 HandoffChainTest
Team fan-out A/collab/DelegateToTeamToolCallback.MAX_BRANCHES 8 branches, per-branch timeout, 4,000-char answers DelegateToTeamToolCallbackTest
Remote A2A calls A2aRemoteAgentToolCallback 5 per turn (new) A2aClientServiceTest

Not verified

  • No control was exercised on a running stack; "Covered" means code plus unit tests.
  • The test-class names come from searching for classes that reference each control. A class existing is not proof it asserts the property in the "Controls" cell.
  • Items I did not trace: per-tool scope narrowing for delegated tokens (ASI03), inbound MCP per-tool scoping (MCP02), web output encoding (LLM05), knowledge isolation tests (LLM08), the live default of tool-call persistence (MCP08).
  • The Agentic Top 10 official PDF was not opened (see Method and limits).

Pending work (kept open on purpose)

Tracked as draft issues in .github/launch-issues/. Nothing below is done.

# Item Items Rated
01 Per-agent egress allow-list (column, resolver wiring, editor field) ASI02, LLM06, MCP09 High
02 Injection judge fail-open is fixed (fail-mode closed by default); no uniform untrusted-data envelope ASI01, LLM01, MCP06 High
03 Dispatch gate lookup error now fails closed by default; tool-arg policies off by default ASI02, LLM06 High
04 Stdio MCP sidecar off by default ASI05, MCP05 High
05 Screen remember_fact and graph-memory writes (extract the shared screen) ASI06, LLM04, LLM02 High
06 MCP tool pinning: remaining hardening (annotations, static servers, default mode); landing, tests and the config key are done MCP03, ASI04 High
07 DNS-rebinding window on outbound MCP connections (pinning done; the pre-buffer response-size cap is open) MCP07 High
- Output-side system-prompt leakage filter / canary LLM07 Medium, no issue yet
- One step/budget total across delegated sub-agents (today bounded by depth 1 x 8 branches) ASI08 Medium
- Direct test for the depth-1 delegation cap; test for EnvIsolatedStdioClientTransport, RememberFactToolCallback, ChatGuardrailGate ASI08, MCP01, ASI06, ASI01 Medium
- A2A: ListTasks, SubscribeToTask, card signing alignment, ETag, gRPC (decision) see a2a-conformance.md -

Follow-up issues

Draft issues for the High gaps are in .github/launch-issues/: 01 per-agent egress, 02 untrusted-data envelope (judge fail-open fixed), 03 tool-arg policies default (dispatch-gate fail-open fixed), 04 stdio sidecar default, 05 memory-write screening, 06 MCP tool-pin hardening, 07 MCP DNS rebinding.

See also the console guide: Administration pages (egress and A2A panels, MCP tool-pin review card, DPDP controls at /admin/data-privacy, see the DPDP page).