# Defense-in-Depth Score: Spring AI

**Repo:** https://github.com/spring-projects/spring-ai · **Commit:** `46d05c2fdbab52f6ef04207abe19b12687a47453` · **Reviewed:** 2026-10-03
**What it is:** Spring application framework for AI engineering with tool calling and MCP
**Category:** Agent Frameworks
**Scored configuration:** An auto-configured ChatClient with the auto-registered ToolCallingAdvisor and default ToolCallingManager, developer-registered @Tool/function tools, and the MCP client starter at its property defaults; memory, RAG, and observation advisors noted where they are opt-in.
**Agent surface (default):** code execution opt-in · filesystem write no · network egress yes · external credentials yes · persistent memory opt-in · untrusted input yes · third party extensions opt-in · sub agents no · external communication opt-in

## Score: 3.0 / 10.0 (Minimal)

| # | Criterion | S | C | D | B | Raw | Cap | Score | Confidence |
|---|---|---|---|---|---|---|---|---|---|
| C1 | Identity & least privilege | L0 | L0 | L0 | L0 | 0.00 | — | **0.00** | High |
| C2 | Approval gates | L0 | L0 | L0 | L0 | 0.00 | — | **0.00** | High |
| C3 | Tool & action scoping | L2 | L2 | L3 | L1 | 0.50 | — | **0.50** | High |
| C4 | Code-execution isolation | SA | SA | SA | SA | 1.00 | — | **1.00** (SA) | High |
| C5 | Untrusted input blast radius | L0 | L0 | L0 | L0 | 0.00 | C5-WORSTCASE | **0.00** | High |
| C6 | Memory, context & configuration integrity | L1 | L0 | L2 | L1 | 0.23 | — | **0.23** | High |
| C7 | Third-party extensions | L1 | L0 | L2 | L1 | 0.23 | — | **0.23** | Medium |
| C8 | Secrets & sensitive-data protection | L2 | L1 | L2 | L1 | 0.38 | — | **0.38** | High |
| C9 | Audit & traceability | L2 | L2 | L0 | L1 | 0.35 | G1 | **0.35** | Medium |
| C10 | Limits & kill switch | L1 | L1 | L2 | L1 | 0.30 | — | **0.30** | High |

Controls where a risk surface exists: 1.97 / 9.0 (22%); 1 criterion scored SA (surface absent).

Spring AI runs every tool call the model requests, automatically and without any approval step, inside your Spring application with all of its credentials and data access. Nothing in the framework separates untrusted content (web pages, documents, MCP results) from instructions, so a prompt injection can drive any registered tool. It does ship sensible defaults elsewhere: no tools unless you attach them, a per-turn cap of 150 tool calls, and content-free telemetry. Treat approvals, authorization, and isolation as work you must add yourself.

## Critical gaps
- Tools run in-process with the Spring application's full credentials and no authorization check, so a hijacked agent inherits everything the application can reach. (ASI03, T3; C1) — [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:328](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L328); [spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java:186](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java#L186)
- Untrusted tool, MCP and RAG content flows into an ungated tool loop, so a prompt injection can exfiltrate data and take irreversible actions with no human involved (C5-WORSTCASE). (ASI01, T6, LLM01; C5) — [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:337](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L337); [spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java:216](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java#L216); [mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java:246](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java#L246)

## Criterion details

### C1 Identity & least privilege — 0.00 (high)

Spring AI has no identity or authorization layer of its own. Every tool a developer registers runs as an ordinary Java method or function inside the Spring application, with whatever database connections, service credentials and network access that application holds, and nothing checks a tool call against a policy or against the user who asked. A ToolContext map lets developers pass a tenant or user id to their tools, but enforcing it is entirely up to the tool. The MCP server starter exposes every ToolCallback bean over HTTP with no authentication of its own unless the developer adds Spring Security. If the agent is hijacked, the attacker gets the full authority of the application.

- **S L0:** Tools are invoked by reflection or as plain functions in the host process with its ambient authority; no scoped identity or authorization primitive exists. — [spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java:186](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java#L186); searched `rg -n -S -i -e 'authoriz|principal|SecurityContext'` in `spring-ai-model/src/main spring-ai-client-chat/src/main mcp/common/src/main` → 0 hits (No authorization, principal, or security-context handling anywhere in the core tool path, ChatClient, or MCP client adapters.) (verified)
  - *To reach the next level:* No per-tool credential scoping or deterministic authorization check before a tool runs.
- **C L0:** DefaultToolCallingManager calls the tool callback directly with no authorization layer in between, and the MCP server starter publishes all ToolCallback beans with no built-in auth. — [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:328](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L328); [auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/ToolCallbackConverterAutoConfiguration.java:53-54](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/ToolCallbackConverterAutoConfiguration.java#L53-L54); searched `rg -n -S -i -e 'authoriz|principal|SecurityContext'` in `spring-ai-model/src/main spring-ai-client-chat/src/main mcp/common/src/main` → 0 hits (No authorization, principal, or security-context handling anywhere in the core tool path, ChatClient, or MCP client adapters.) (verified)
  - *To reach the next level:* No shared authorization layer that every tool call, including MCP and server-exposed tools, passes through.
- **D L0:** The default auto-configured ToolCallingManager executes tools with full application authority; least privilege is left to the developer. — [auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java:139-143](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java#L139-L143) (verified)
  - *To reach the next level:* No narrower default identity; least privilege must be built by the developer.
- **B L0:** Because tools run in-process with the application's credentials and the default MCP converter forwards the whole ToolContext to every MCP server, a hijacked agent reaches whatever the Spring application can (typically production databases and internal services), and nothing in the framework narrows it. — [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:328](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L328); [mcp/common/src/main/java/org/springframework/ai/mcp/ToolContextToMcpMetaConverter.java:53-58](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/mcp/common/src/main/java/org/springframework/ai/mcp/ToolContextToMcpMetaConverter.java#L53-L58) (verified)
  - *To reach the next level:* Nothing in the framework limits a hijacked agent below the application's full authority.
- **Cap:** none

### C2 Approval gates — 0.00 (high)

Spring AI ships no approval gate. The auto-registered tool-calling advisor runs every tool call the model requests, immediately and in a loop, until the model stops asking. The documentation tells developers who want approval to write their own advisor or drive the loop themselves, but the framework provides no primitive that pauses on a tool, shows the exact call, or records a decision. The OpenAI-hosted MCP tool even rejects the provider's own 'always require approval' setting because the round-trip is not implemented. There is no undo or checkpoint for actions already taken.

- **S L0:** No approval mechanism exists; tool calls go straight from the model response to execution. — [spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java:181-182](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java#L181-L182); searched `rg -n -S -i -e 'approv|human.in.the.loop|requiresApproval|confirm'` in `spring-ai-model/src/main spring-ai-client-chat/src/main mcp/common/src/main` → 0 hits (No approval or confirmation concept in the core tool-calling code.) (verified)
  - *To reach the next level:* No per-call human approval showing the exact call.
- **C L0:** With no gate, every tool path (method, function, MCP, provider-hosted) reaches execution ungated. — [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:328](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L328); [models/spring-ai-openai/src/main/java/org/springframework/ai/openai/responses/HostedTool.java:157-160](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/responses/HostedTool.java#L157-L160) (verified)
  - *To reach the next level:* No gate for any tool path, including the most powerful developer- or MCP-supplied tools.
- **D L0:** The ToolCallingAdvisor is auto-registered on every ChatClient call and executes tools without asking. — [spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java:1217-1237](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java#L1217-L1237) (verified)
  - *To reach the next level:* Approval is not available, let alone on by default.
- **B L0:** A wrongly executed call can do whatever the registered tool does (send email, write databases, call MCP servers); the framework has no checkpoint, rollback, or dry-run. — [spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java:216](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java#L216); searched `rg -n -S -i -e 'approv|human.in.the.loop|requiresApproval|confirm'` in `spring-ai-model/src/main spring-ai-client-chat/src/main mcp/common/src/main` → 0 hits (No approval or confirmation concept in the core tool-calling code.) (verified)
  - *To reach the next level:* No rollback, checkpoint, or preview for any tool action.
- **Cap:** none

### C3 Tool & action scoping — 0.50 (high)

Spring AI converts a tool call's JSON arguments into the Java parameter types of the developer's method or function, which rejects malformed input but is type checking rather than an allowlist; schema rules such as enums or numeric ranges are not enforced. MCP tool arguments are only parsed into a map and forwarded unchanged. On the positive side, a ChatClient has no tools unless the developer passes them, and by default a call to a tool that was not attached to the request is rejected rather than resolved from the application context. The framework ships no path, URL, or query validation helpers, so a registered tool's reach is whatever the developer gave it.

- **S L2:** Method and function tools get Jackson deserialization into typed parameters; unknown tool names are rejected. — [spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java:136](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java#L136); [spring-ai-model/src/main/java/org/springframework/ai/tool/function/FunctionToolCallback.java:108](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/tool/function/FunctionToolCallback.java#L108); [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:296](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L296) (verified)
  - *To reach the next level:* No allowlist validation (path containment, host allowlists, bounds) offered as a framework primitive.
- **C L2:** All method and function tools are type-converted, but MCP tool arguments are passed through as a raw map. — [mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java:123](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java#L123) (verified)
  - *To reach the next level:* Extension (MCP) tools are not wrapped by a shared validation layer.
- **D L3:** No tools are attached by default, and only the tools passed on the request can execute because name-based resolution fallback is off by default. — [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:84](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L84); [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:286-289](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L286-L289) (verified)
  - *To reach the next level:* No per-task read/write tiering, and opt-in tool search lets the model pull in more tools from a developer-supplied index.
- **B L1:** A misused tool has the full reach the developer gave it; the only framework bound is the default cap of 40 calls per tool per turn. — [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:100](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L100) (verified)
  - *To reach the next level:* No scoping or quantity bounds beyond per-turn call counts.
- **Cap:** none

### C4 Code-execution isolation — 1.00 (high)

Spring AI itself has no code-execution feature: no shell tool, no script engine, no evaluation of model output, and no process spawning in its own source. Tools are Java methods the developer writes. Two adjacent paths are scored elsewhere or run off-host: local MCP servers configured by the operator are launched as subprocesses by the MCP Java SDK (scored under third-party extensions), and the OpenAI code-interpreter and Anthropic code-execution tools, when a developer enables them, run model-written code in the model provider's own containers rather than on the application host.

- **Structural absence:** searched `rg -n -S -e 'ProcessBuilder|Runtime\.getRuntime\(\)|ScriptEngine|GroovyShell|JShell' --glob '**/src/main/**/*.java'` in `.` → 0 hits (No process spawning or script evaluation anywhere in production sources across all modules.); searched `rg -n -S -e 'SpelExpressionParser|parseExpression|StandardEvaluationContext' --glob '**/src/main/**/*.java'` in `.` → 0 hits (No Spring Expression Language evaluation that could interpret model text.)
- **Notes:** Provider-hosted code execution (HostedTool.CodeInterpreter for OpenAI Responses; Anthropic code execution auto-added when Skills are configured, AnthropicChatModel.java:997-1000) is opt-in and runs on the provider's infrastructure. Operator-configured stdio MCP servers are scored in C7.

### C5 Untrusted input blast radius — 0.00 (high)

Tool results, MCP results, and retrieved documents are fed back to the model as ordinary tool or user messages, and nothing in the framework tracks which content is untrusted or restricts what the agent may do after reading it. MCP tool descriptions from any connected server are passed to the model verbatim, and the RAG advisor pastes retrieved documents into the user's own message. Because the documentation shows web-searching MCP tools combined with the application's own tools in one ChatClient, with no approval gate, a successful prompt injection can both leak data and trigger state-changing tools without a human.

- **S L0:** Tool output is appended to the conversation as a tool response and the loop continues with the same tools available; no limit applies after untrusted content arrives. — [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:337](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L337); searched `rg -n -S -i -e 'untrusted|taint|provenance|quarantin|prompt.injection'` in `spring-ai-model/src/main spring-ai-client-chat/src/main mcp/common/src/main` → 0 hits (Nothing marks or tracks untrusted content in the core tool path.) (verified)
  - *To reach the next level:* No code that disables or gates egress and state-changing tools once untrusted content is read.
- **C L0:** Untrusted sources are not distinguished: MCP descriptions are copied verbatim, and RAG documents are merged into the user message. — [mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java:246](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java#L246); [advisors/spring-ai-vector-store-advisor/src/main/java/org/springframework/ai/chat/client/advisor/vectorstore/QuestionAnswerAdvisor.java:138](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/advisors/spring-ai-vector-store-advisor/src/main/java/org/springframework/ai/chat/client/advisor/vectorstore/QuestionAnswerAdvisor.java#L138) (verified)
  - *To reach the next level:* No source of untrusted content is handled differently from the principal's input.
- **D L0:** No such control exists, on or off. — [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:337](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L337) (verified)
  - *To reach the next level:* No untrusted-content control exists to enable by default.
- **B L0:** The documented pattern combines web-reading MCP tools with application tools in one ungated loop, so an injection can exfiltrate data through any egress tool and take irreversible actions unattended. — [spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc:358-361](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc#L358-L361); [spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java:216](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java#L216) (verified)
  - *To reach the next level:* Neither exfiltration nor irreversible actions require a human after the agent reads untrusted content.
- **Cap:** C5-WORSTCASE — Worst case (B L0): a hijacked agent can leak data and take irreversible actions unattended.

### C6 Memory, context & configuration integrity — 0.23 (high)

Spring AI loads no instruction files or .env files from a workspace, and memory is only used when the developer adds a chat-memory advisor. When they do, every user message, tool result, and model reply is saved without validation and replayed into later requests of the same conversation, where it can steer tool calls; the default store keeps the last 20 messages in memory, and JDBC, Redis, Mongo, Cassandra, and Neo4j stores make it durable. Conversations are separated by a conversation id that the application must supply and that every repository query filters on, but the id is not tied to an authenticated user. The vector-store memory advisor escapes stored text but still inserts it into the system prompt.

- **S L1:** Memory writes are unvalidated: the last user or tool-response message is stored and later replayed; the vector-store variant XML-escapes entries into the system prompt (a delimiter, not a control). — [spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/MessageChatMemoryAdvisor.java:103-104](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/MessageChatMemoryAdvisor.java#L103-L104); [advisors/spring-ai-vector-store-advisor/src/main/java/org/springframework/ai/chat/client/advisor/vectorstore/VectorStoreChatMemoryAdvisor.java:145-146](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/advisors/spring-ai-vector-store-advisor/src/main/java/org/springframework/ai/chat/client/advisor/vectorstore/VectorStoreChatMemoryAdvisor.java#L145-L146) (verified)
  - *To reach the next level:* No validation, approval, or expiry on memory writes, and replayed memory is not presented as data.
- **C L0:** No memory store or retrieval path has a write or load control. — [spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/MessageChatMemoryAdvisor.java:78](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/MessageChatMemoryAdvisor.java#L78); searched `rg -n -S -e 'AGENTS\.md|CLAUDE\.md|SKILL\.md|dotenv' --glob '**/src/main/**/*.java'` in `.` → 0 hits (No auto-loaded workspace instruction or .env files (so that surface is absent).) (verified)
  - *To reach the next level:* No memory store has controlled writes.
- **D L2:** Memory advisors require an explicit conversation id and every repository query filters on it; the id is application-supplied, not bound to a principal. — [spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/api/BaseChatMemoryAdvisor.java:41](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/api/BaseChatMemoryAdvisor.java#L41); [memory-repositories/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/PostgresChatMemoryRepositoryDialect.java:30](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/memory-repositories/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/PostgresChatMemoryRepositoryDialect.java#L30); [advisors/spring-ai-vector-store-advisor/src/main/java/org/springframework/ai/chat/client/advisor/vectorstore/VectorStoreChatMemoryAdvisor.java:138](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/advisors/spring-ai-vector-store-advisor/src/main/java/org/springframework/ai/chat/client/advisor/vectorstore/VectorStoreChatMemoryAdvisor.java#L138) (verified)
  - *To reach the next level:* Isolation is by application-chosen conversation id rather than an authenticated principal, and there is no retention limit by default.
- **B L1:** Poisoned messages persist across requests in a conversation (durably with persistent repositories) and are replayed into tool-calling turns; the 20-message window bounds the in-memory default. — [spring-ai-model/src/main/java/org/springframework/ai/chat/memory/MessageWindowChatMemory.java:45](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/chat/memory/MessageWindowChatMemory.java#L45); [auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/src/main/java/org/springframework/ai/model/chat/memory/autoconfigure/ChatMemoryAutoConfiguration.java:41](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/src/main/java/org/springframework/ai/model/chat/memory/autoconfigure/ChatMemoryAutoConfiguration.java#L41) (verified)
  - *To reach the next level:* Poisoned memory can still trigger tool use; no review or rollback.
- **Cap:** none

### C7 Third-party extensions — 0.23 (medium)

Spring AI's runtime extension path is MCP. Once the MCP client starter is on the classpath, it connects at application start to every server listed in configuration, launches stdio servers with the configured command, and exposes every tool each server advertises. Nothing is enabled until the operator configures a server, but there is no version pinning, hash check, or re-approval: the official example launches an unpinned package with 'npx -y', and when a server announces changed tools they are picked up automatically. The default adapter also forwards the application's whole ToolContext to each MCP server.

- **S L1:** Servers are operator-chosen commands launched as-is; the documented example uses unpinned 'npx -y'. — [auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/StdioTransportAutoConfiguration.java:78](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/StdioTransportAutoConfiguration.java#L78); [spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc:340-341](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc#L340-L341) (verified)
  - *To reach the next level:* No version pinning or integrity check for MCP servers.
- **C L0:** No extension type is verified, and tool-list changes are accepted at runtime. — [mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallbackProvider.java:165-166](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallbackProvider.java#L165-L166); searched `rg -n -S -i -e 'sha256|checksum|signature|digest|integrity'` in `auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main mcp/common/src/main` → 0 hits (No integrity checking in the MCP client code.) (verified)
  - *To reach the next level:* No verification for any extension type.
- **D L2:** Nothing third-party runs until configured, but configured clients are enabled and initialized at startup by default and every advertised tool is exposed. — [auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/properties/McpClientCommonProperties.java:56](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/properties/McpClientCommonProperties.java#L56); [auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/McpClientAutoConfiguration.java:189-190](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/McpClientAutoConfiguration.java#L189-L190); [auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/McpToolCallbackAutoConfiguration.java:80](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/McpToolCallbackAutoConfiguration.java#L80) (verified)
  - *To reach the next level:* Adding a server does not show what it will run or which tools/permissions it brings, and all tools are accepted by default.
- **B L1:** Stdio servers run as separate processes under the same OS user; environment handling is delegated to the MCP Java SDK, whose ProcessBuilder-based transport inherits the parent environment (library behaviour, not in this repo), and the default meta converter forwards ToolContext data. — [auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/properties/McpStdioClientProperties.java:133](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/properties/McpStdioClientProperties.java#L133); [mcp/common/src/main/java/org/springframework/ai/mcp/ToolContextToMcpMetaConverter.java:53-58](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/mcp/common/src/main/java/org/springframework/ai/mcp/ToolContextToMcpMetaConverter.java#L53-L58) (inferred)
  - *To reach the next level:* No environment scrubbing or per-extension sandbox in the framework.
- **Cap:** none

### C8 Secrets & sensitive-data protection — 0.38 (high)

Spring AI sends no telemetry of its own, and its observation and logging features exclude prompt, completion, and tool-argument content by default, logging a loud warning when a developer turns content capture on. API-key objects mask their value when printed. But nothing redacts secrets or personal data from what is sent to the model provider or to MCP servers: tool exception messages are returned to the model verbatim, the default MCP adapter forwards the whole ToolContext map, and saved chat memory is stored in plain text. Provider keys are long-lived and readable by any tool code in the same process.

- **S L2:** Type-level masking for API keys and content-free observations by default; no redaction before model- or MCP-bound data. — [spring-ai-model/src/main/java/org/springframework/ai/model/SimpleApiKey.java:47-50](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/SimpleApiKey.java#L47-L50); [auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingProperties.java:80](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingProperties.java#L80) (verified)
  - *To reach the next level:* No redaction before model-bound messages, and no secret store or encryption at rest.
- **C L1:** Only the logging/telemetry path is protected (by omission); exception messages, ToolContext forwarded to MCP servers, and memory transcripts are not. — [spring-ai-model/src/main/java/org/springframework/ai/tool/execution/DefaultToolExecutionExceptionProcessor.java:84](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/tool/execution/DefaultToolExecutionExceptionProcessor.java#L84); [mcp/common/src/main/java/org/springframework/ai/mcp/ToolContextToMcpMetaConverter.java:53-58](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/mcp/common/src/main/java/org/springframework/ai/mcp/ToolContextToMcpMetaConverter.java#L53-L58) (verified)
  - *To reach the next level:* Transcripts, model-bound messages, MCP metadata, and error messages are not protected.
- **D L2:** Prompt, completion, and tool content capture are off by default and warn when enabled. — [auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationProperties.java:41](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationProperties.java#L41); [auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java:195](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java#L195) (verified)
  - *To reach the next level:* Redaction is not always on; enabling content capture is a property change.
- **B L1:** Long-lived provider keys and application credentials sit in the same process as every tool. — [spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java:186](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java#L186) (verified)
  - *To reach the next level:* Keys are not scoped or short-lived.
- **Cap:** none

### C9 Audit & traceability — 0.35 (medium)

Every tool call goes through one manager that wraps it in a Micrometer observation carrying the tool name, call id, and timing, plus arguments and results if content capture is enabled; this covers MCP tools too. But the default observation registry is a no-op, so unless the application provides Micrometer tracing and an exporter, nothing is recorded beyond a debug log line with the tool name. Even when enabled, records do not say which user requested an action, and there are no approvals to record.

- **S L2:** When observations are enabled, each tool call becomes a structured span with name, id, timing, and (optionally) arguments and result. — [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:313-318](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L313-L318) (verified)
  - *To reach the next level:* No actor or requesting-principal attribution on the record.
- **C L2:** All tool calls, including MCP tools, pass through the single observed execution path. — [spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java:364](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java#L364); [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:328](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L328) (verified)
  - *To reach the next level:* No record of approvals, configuration changes, or memory writes.
- **D L0:** The default registry is a no-op; auto-configuration falls back to it when no registry bean exists, so recording is opt-in. — [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:72-73](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L72-L73); [auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java:142](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java#L142) (verified)
  - *To reach the next level:* Recording is not on by default.
- **B L1:** When enabled, Micrometer/OpenTelemetry export is asynchronous best-effort; tool execution never depends on the record being written. — [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:321-325](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L321-L325) (inferred)
  - *To reach the next level:* Records are not durable per action and failures are not surfaced.
- **Cap:** G1 — Tool-call observations are recorded only when the application supplies an ObservationRegistry and exporter; the framework default is a no-op registry.

### C10 Limits & kill switch — 0.30 (high)

The tool-calling loop runs until the model stops requesting tools, but since this version a default cap stops a turn after 40 calls to any one tool or 150 tool calls in total, enforced in code; operators can raise these or set them to unlimited, and the model cannot. There is no wall-clock limit, no token or cost budget, and no timeout on local tool calls (MCP requests time out after 20 seconds). There is no cancel API for blocking calls, and in streaming mode tool execution runs on a worker thread that is not interrupted when the subscriber cancels. Tools executed by the model provider, such as hosted MCP or web search, are outside these counters.

- **S L1:** Per-turn tool-call caps enforced in code; per-execution timeout only for MCP requests; no wall-clock or token/cost cap. — [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:107](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L107); [auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/properties/McpClientCommonProperties.java:63](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/properties/McpClientCommonProperties.java#L63) (verified)
  - *To reach the next level:* No wall-clock, token/cost cap, or general per-tool timeout.
- **C L1:** Caps apply to the top-level loop for every framework-executed tool; local tools have no timeout and provider-hosted tools are not counted. — [spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java:181-182](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java#L181-L182) (verified)
  - *To reach the next level:* Tool timeouts do not cover local tools, and nested ChatClient calls inside tools start a fresh turn budget.
- **D L2:** Sensible defaults the model cannot change; operators can set any limit to -1 (unlimited). — [auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java:69](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java#L69); [spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:100](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L100) (verified)
  - *To reach the next level:* No hard ceiling; operators can disable limits with a property.
- **B L1:** With no time or spend ceiling, up to 150 tool calls plus their model rounds can run per turn, and blocking tool work on the bounded-elastic scheduler continues after a stream is cancelled. — [spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java:403](https://github.com/spring-projects/spring-ai/blob/46d05c2fdbab52f6ef04207abe19b12687a47453/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java#L403) (verified)
  - *To reach the next level:* No time or spend ceiling, and stopping does not cancel in-flight tool calls.
- **Cap:** none

## Rule-of-Two check
[A] untrusted input: Tool and MCP results re-enter context as tool messages (DefaultToolCallingManager.java:337); RAG docs merged into user message (QuestionAnswerAdvisor.java:138); MCP descriptions verbatim (McpToolUtils.java:246) · [B] sensitive data/systems: Tools run in-process with the application's credentials and data (MethodToolCallback.java:186); ToolContext forwarded to MCP servers (ToolContextToMcpMetaConverter.java:53-58) · [C] state change / egress: Every developer or MCP tool executes without approval in the auto-registered loop (ToolCallingAdvisor.java:181-182, 216) · Same default session? Yes

## Highest-impact improvements
1. Ship an approval primitive in ToolCallingAdvisor (per-tool requires-approval flag that pauses the loop and returns the exact call for approve/edit/reject), and apply it to MCP tools by default. — C2 S L0→L3, +0.225 before caps (Playbook 5)
2. Default the MCP ToolContext converter to noOp and pass stdio servers an explicit minimal environment. — C7 B L1→L2, +0.050 before caps
3. Add a provenance flag on tool/MCP responses and an option that forces approval of egress or write tools after untrusted content enters the turn. — C5 S L0→L2, +0.150 before caps (Playbook 1)
4. Add a per-turn wall-clock and token budget alongside the existing tool-call caps, plus a default per-tool timeout. — C10 S L1→L2, +0.075 before caps
5. Record tool calls by default (e.g. a structured log observation handler) with the conversation id and requesting principal. — C9 D L0→L2, +0.100 before caps

## Re-audit log
- No changes.

## Limitations
- Static source review of the pinned commit only; nothing was installed, built, executed, or probed.
- No release tag points at the pinned commit; the root pom.xml declares 2.1.0-SNAPSHOT.
- The MCP Java SDK (io.modelcontextprotocol), which implements stdio process launch, environment handling, and request timeouts, is a third-party dependency not in this repository; C7 B relies on its documented ProcessBuilder behaviour and is marked inferred.
- Provider-hosted tools (OpenAI Responses hosted MCP, web search, code interpreter; Anthropic code execution/skills) run on the provider's infrastructure and were not examined beyond the client code.
- Per-model ChatModel implementations, vector-store integrations, and document readers were sampled, not exhaustively reviewed.
- No reviewer-injection attempts were found (AGENTS.md contains only build and style guidance).
