Skip to content

fix(tooluse): always call model to prevent tooluse injection#2136

Open
poshinchen wants to merge 1 commit intostrands-agents:mainfrom
poshinchen:fix/tooluse-injection-defensive
Open

fix(tooluse): always call model to prevent tooluse injection#2136
poshinchen wants to merge 1 commit intostrands-agents:mainfrom
poshinchen:fix/tooluse-injection-defensive

Conversation

@poshinchen
Copy link
Copy Markdown
Contributor

Description

Remove _has_tool_use_in_latest_message to prevent tool execution bypass via injected toolUse blocks

PR #1068 introduced [_has_tool_use_in_latest_message()](https://github.com/strands-agents/sdk-python/blob/117da677fa398fffbce834d7df5099047b31eb82/src/strands/event_loop/event_loop.py#L58) in event_loop.py, which checks whether the latest message in the conversation contains toolUse content blocks and, if so, skips model invocation entirely and proceeds directly to tool execution.

This check does not distinguish between a message produced by the model and one injected by an external caller.

Combined with _convert_prompt_to_messages(), which appends the messages directly to the conversation. An attacker can inject a message containing toolUse blocks that execute immediately without the model ever being consulted. The system prompt, guardrails, and all model-level security controls are completely bypassed.

Fix

This PR adopts the deferred-append pattern already used in the TypeScript SDK:

  • Remove _has_tool_use_in_latest_message() so the model is always called; no user-supplied message can short-circuit into tool execution.

  • Defer assistant message append:

    • the assistant message is only added to agent.messages alongside the tool result message, after tool execution completes. This means agent.messages is never left with a dangling toolUse without a matching toolResult.
  • Dangling toolUse handling: if agent.messages ends with a dangling toolUse (e.g. from session restore), _convert_prompt_to_messages() appends a dummy error toolResult and the model is called normally.

The only remaining path that skips model invocation is the interrupt state, which has proper provenance. The tool_use_message is set by the event loop itself, not from user input.

Additional changes

The max_tokens handling was also moved from _handle_model_execution to event_loop_cycle for consistency with the deferred-append pattern. The recovered message is now appended and MessageAddedEvent is fired before MaxTokensReachedException is raised.

Breaking change

MessageAddedEvent for assistant messages now fires after tool execution (alongside the tool result) instead of before. Hooks that depend on seeing the assistant message in agent.messages during tool execution will need to adapt.

Related Issues

N/A

Documentation PR

N/A

Type of Change

Bug fix
Breaking change

Testing

How have you tested the change? Verify that the changes do not break functionality or introduce warnings in consuming repositories: agents-docs, agents-tools, agents-cli

  • I ran hatch run prepare

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@poshinchen poshinchen changed the title fix(tooluse): always call model to prevent tooluse-injection fix(tooluse): always call model to prevent tooluse injection Apr 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant