A Strands agent runs inside your own process, so every tool it calls, every MCP server it connects to and every AWS API it reaches uses the credentials and permissions of that process, and the SDK settings you choose in code are the main control between a model's output and your systems. Strands Agents is an open-source SDK, published under the strands-agents GitHub organisation, for building agents in Python and TypeScript; its documentation describes it as "a library, not a platform", with Amazon Bedrock as the default model provider. This guide covers the SDK layer: model credentials and IAM, tool selection, MCP client authentication and filtering, approval interrupts and hooks, execution limits, session storage and telemetry. The hosting runtime is covered separately in the AWS Bedrock AgentCore Security Guide, so this page stays with what the developer controls in code. It is for platform engineers, application security teams and reviewers signing off Strands-based agents. Settings were checked against the Strands documentation on 14 November 2026.
This is an independent guide from NHI Mgmt Group, based on the Strands Agents public documentation as of November 2026. It is not sponsored or reviewed by AWS. Product features change, so confirm settings against the current Strands documentation.
Key takeaways
- The host process is the permission boundary. The Strands tools documentation states that all tools execute code with the permissions of the host process, so the IAM role or credentials behind that process set the real blast radius, not the system prompt.
- Scope the model permissions to specific models. Strands needs
bedrock:InvokeModelWithResponseStreamfor streaming andbedrock:InvokeModelotherwise; the sample policy uses"Resource": "*"and the docs say to narrow it to model ARNs in production. - Filter MCP tools at the client.
MCPClientacceptstool_filterswithallowedandrejectedlists, so an agent sees only the server tools it needs, and credentials can be kept out of config with${VAR}interpolation. - Put approval in a hook, not a prompt.
BeforeToolCallEventlets code pause a call withevent.interrupt()or block it withcancel_tool, which holds even when the model has been steered by injected content. - Session files and traces hold sensitive data. Sessions persist messages and agent state, and trace spans can include prompts and tool inputs and outputs, so both need access control, retention and redaction.
Where the SDK sits in the trust model
Strands runs an agent loop: the model receives the conversation and tool descriptions, returns text or tool calls, the SDK executes those calls and feeds results back, and the loop repeats until a stop reason is reached. Each element that touches the outside world is wired up in your code.
| Layer | What Strands provides | What you configure and own |
|---|---|---|
| Model access | BedrockModel and other providers, boto3 credential chain | Which credentials, which region, which model ARNs the role may invoke |
| Tools | @tool decorator, vended tools (shell, Python REPL, HTTP, file write and edit) | The explicit tool list, what each tool can read, write and call |
| MCP | MCPClient over stdio, Streamable HTTP and SSE | Which servers, their authentication, tool filters |
| Control points | Hooks, interrupts, HumanInTheLoop, limits | Which calls need approval, quotas, deadlines |
| State | Session managers (local file, S3, custom repository) | Session IDs, storage permissions, retention |
| Telemetry | OpenTelemetry traces and metrics | Exporters, redaction, who can read traces |
If the agent is deployed to AgentCore Runtime, the runtime adds a dedicated microVM per user session and inbound authentication; the SDK controls on this page still apply inside it.
Model credentials and IAM
Strands uses boto3 for Bedrock, so credentials resolve through the standard boto3 chain: environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN), shared config and profiles, or a custom boto3 Session passed as boto_session. TypeScript takes credentials in clientConfig.credentials. The docs also describe an api_key option for an Amazon Bedrock API key, which uses bearer token authentication instead of SigV4 signing.
- Prefer roles to keys: run the agent under an IAM role (instance profile, task role, or the AgentCore execution role) so credentials are temporary. Long-lived access keys in environment variables or
.envfiles are the pattern ATT&CK tracks as T1552.001 Credentials In Files. See the AWS IAM Roles vs IAM Users. - Treat a Bedrock API key as a secret: if you use
api_key, store it in a secrets manager, rotate it and scope it, because a bearer token works for whoever holds it. - Pin the region: Python resolves the region from
region_name, then the boto3 session region (AWS_DEFAULT_REGIONor the profile), thenAWS_REGION, then a default of us-west-2. The docs warn thatAWS_REGIONranks below profile regions. Setregion_nameexplicitly where data residency matters, and use the regional inference profile prefix (for exampleeu.) that matches it. - Pin the model: with no model specified the SDK picks a default based on the credentials' region. The production guide says to pass an explicit model configuration (
model_id,max_tokensand sampling settings) instead of relying on defaults.
| Need | IAM action (from the Strands Bedrock page) | Scoping advice |
|---|---|---|
| Streaming responses | bedrock:InvokeModelWithResponseStream | Resource: the specific foundation model or inference profile ARNs |
| Non-streaming responses | bedrock:InvokeModel | Same model ARNs; grant only the mode you use |
| S3 document or media references | s3:GetObject | Only the bucket and prefix the agent reads |
| Guardrail evaluation | Bedrock guardrail permissions per the Bedrock docs | The specific guardrail ARN and version |
| Tool calls to AWS services | Whatever each tool's API calls need | Separate role or session policy per tool where practical |
The last row is where most of the risk sits. A tool that calls AWS APIs uses the same process credentials as the model client unless you give it its own. Where a tool needs wider rights than the model client, have the tool assume a narrower role, or apply a session policy and a permissions boundary. See the AWS IAM Permissions Boundaries Guide and validate policies with the IAM Access Analyzer Guide.
Choosing and constraining tools
The production guide's first recommendation is to specify tools explicitly rather than loading everything available, and to keep automatic loading and reloading of tools disabled, which is the default (load_tools_from_directory=False). Turning directory loading on means any Python file written to that directory becomes a callable tool, which matters if anything, including the agent itself, can write there.
| Tool type | Typical risk | Constraint to apply |
|---|---|---|
| Shell or bash | Arbitrary command execution as the host user | Run only in an isolated sandbox or container with no credentials; require approval per call |
| Python REPL | Arbitrary code, access to environment variables and files | Same as shell; never in a process holding production credentials |
| HTTP request | Data exfiltration, server-side request forgery to metadata endpoints | Egress allowlist at the network layer; block link-local addresses |
| File write and edit | Overwriting code, config or tool directories | Path allowlist resolved with real paths; read-only mounts elsewhere |
Custom @tool calling AWS | Over-broad IAM, destructive operations | Narrow role per tool, approval for writes and deletes |
Direct calls (agent.tool.name()) | Bypass interrupts, which the docs say do not apply to direct calls | Reserve for trusted application code paths, not model-driven flows |
The Responsible AI page sets five tool design principles: least privilege, input validation, clear documentation, error handling and audit logging. In practice, validate tool arguments against an allowlist inside the tool, resolve file paths to their canonical form and compare against allowed directories with a trailing separator, and log the call with its arguments and outcome. Treat tool output as untrusted input to the next model turn: a web page or document fetched by one tool can carry instructions that target the next. For the wider pattern, see Agent Sandboxing vs Tool Allowlisting and Agent Egress Control and Network Isolation Guide.
MCP clients: authentication, filtering and lifecycle
Strands connects to MCP servers through MCPClient (Python) or McpClient (TypeScript), with stdio for local processes, Streamable HTTP for remote servers and SSE for older HTTP servers. Each connected server adds tools the model can call, so an MCP server is a supplier of capabilities and should go through the same review as code. See the MCP Security Guide and MCP Server Trust Assessment.
| Authentication option | How Strands configures it | When to use |
|---|---|---|
| Static headers | headers on MCPClient (TypeScript: requestInit.headers) | Servers that only accept a bearer token; load it from a secrets store, never a literal |
| OAuth client credentials | url plus an auth block with client_id, client_secret, optional scopes | Machine-to-machine access; tokens are fetched and refreshed automatically |
| Interactive OAuth | auth_provider with an httpx.Auth implementation such as OAuthClientProvider | Agents acting for a signed-in user (authorisation code flow) |
| AWS IAM (SigV4) | aws_iam_streamablehttp_client from mcp-proxy-for-aws, with endpoint, region and service | MCP servers hosted behind AWS IAM, such as AgentCore Gateway |
- Scopes are requests, not limits: the docs state that
scopesis advisory and that scopes advertised by the server take precedence. Enforce least privilege on the authorisation server and the MCP server, not only in the client config. See the MCP Authorisation Specification Guide. - Keep secrets out of config files: in
load_serversconfiguration, use${VAR}placeholders forclient_idandclient_secretand inject the values from a secrets manager at start-up. - Filter tools: set
tool_filterswith anallowedlist (strings or compiled regular expressions). In Pythonallowedis applied beforerejected; in TypeScript rejection wins when both match. An allowlist is safer than a blocklist because new server tools stay hidden until reviewed. - Prefixes are labels, not controls: a
prefixrenames the tool for the agent only; calls still send the original server tool name. Use prefixes to avoid name collisions between servers, and write approval rules against the names the hook actually sees. - Handle elicitation deliberately: a server can pause a call and ask for input through elicitation. If you set
elicitation_callback, route requests to a real user or decline them; never auto-answer with stored data. - Use managed lifecycle and timeouts: passing the client directly in
Agent(tools=[...])lets Strands manage the connection; the MCP page also recommends timeouts on tool calls.
For stdio servers, remember that the server is a child process running with your process's rights and environment. Pin the package version, avoid passing the full environment, and prefer remote servers with their own identity for anything that touches production data.
Approval, hooks and execution limits
Hooks are the SDK's enforcement points, and they run in code regardless of what the model decides. The events most relevant to security are BeforeToolCallEvent, AfterToolCallEvent, BeforeInvocationEvent, AfterInvocationEvent and the model call events.
| Control | SDK mechanism | Security use |
|---|---|---|
| Pause for approval | event.interrupt(name, reason) on BeforeToolCallEvent; result has stop_reason == "interrupt" | Human approval for writes, payments, deletes, external messages |
| Block a call | event.cancel_tool = "reason" (TypeScript: event.cancel) | Deny by policy, for example tools outside business hours or over a quota |
| Rewrite arguments | tool_use["input"] on BeforeToolCallEvent | Force fixed values such as a tenant ID or read-only flag |
| Inspect results | result on AfterToolCallEvent | Redact secrets or personal data before it reaches the model |
| Sanitise input | messages on BeforeInvocationEvent | Strip or flag content before the loop starts |
| Vended approval | HumanInTheLoop with allowed_tools, classifier, enable_trust | Ready-made approval flow with optional session trust |
- Know the HumanInTheLoop defaults: tools in
allowed_toolsskip approval entirely and"*"disables the handler. Negated tools ("!tool_name") always prompt and cannot be trusted for the session. The docs note that"auto"mode may change in a minor release and that small decision models can rate risky calls as safe, so pin the strategy and negate tools that must always ask. - Bound the loop: set turn
limits, invocation token limits and a wall-clock deadline (agent.cancel()from a watchdog in Python, acancelSignalin TypeScript). The docs warn that token limits are checked between turns, so one model call can exceed the total. In multi-agent Swarm and Graph setups, bound both the orchestrator and each node. - Quota side-effecting tools: the documented Limit Tool Counts hook counts calls per tool and cancels calls past a limit, resetting on each invocation.
These controls address OWASP LLM06:2025 Excessive Agency and LLM10:2025 Unbounded Consumption. For design patterns beyond the SDK, see the Human-in-the-Loop for AI Agents Guide and Prompt Guardrails vs Policy Engines for AI Agent Actions.
Guardrails at the model boundary
BedrockModel accepts guardrail_id and guardrail_version to apply an Amazon Bedrock guardrail to model calls, with guardrail_trace for debugging. When a guardrail intervenes, the response has stop_reason == "guardrail_intervened". Input redaction (guardrail_redact_input) is on by default and overwrites the blocked input in conversation history; output redaction (guardrail_redact_output) is off by default, so turn it on if blocked output must not persist in sessions or traces. guardrail_latest_message limits evaluation to the newest user message, which reduces cost but means earlier content, including tool results, is not re-checked.
To trial a guardrail without blocking, the docs show a shadow mode: a hook calls the Bedrock ApplyGuardrail API on input and output and logs what would have been blocked. Run shadow mode in a pilot, review the hits, then switch to enforcing. Guardrails filter content; they do not authorise actions, so keep them alongside the hook and IAM controls above, not instead of them.
Sessions and state
Session managers persist conversation messages, agent state and conversation manager state; snapshots also include interrupt state, model state and the system prompt. Options include SnapshotSessionManager with LocalFileStorage or S3Storage, FileSessionManager, S3SessionManager and a custom RepositorySessionManager. With no storage configured, Python falls back to ./.strands/.
- One session ID per user and conversation: the docs warn that agents sharing a session ID and agent ID write to the same history, and the default agent ID makes that collision easy. Derive session IDs server-side from the authenticated user, never from client input alone. See Agent Identity vs Agent Session Identity.
- Session storage is trusted input: do not load sessions from untrusted sources such as files in a cloned repository; a tampered session can inject messages, state and approval decisions stored in
agent.state. - Lock down storage: restrict filesystem permissions to the agent process. The docs note the SDK does not block symlinks in the storage directory, so in shared hosts use a dedicated directory no other user can write. For S3, use a dedicated bucket and prefix, block public access, encrypt with a customer managed KMS key and set lifecycle expiry.
- Plan for concurrency: session managers are not thread-safe and use no distributed lock; serialise writes per session in your application.
Detection and measurement
Strands emits OpenTelemetry traces with spans for model and tool invocations. Model spans may include the system prompt, parameters, input and output messages and token usage; tool spans may include tool inputs and outputs. Route traces through an OpenTelemetry collector that redacts personal data and secrets, restrict who can read the trace store, and set retention. See Agent Observability vs Audit Logging.
Alongside traces, keep AWS-side evidence: CloudTrail records for Bedrock invocations and for every AWS API the tools call, tied to the role session name so calls can be traced to an agent run. Log every interrupt, approval, denial and cancel_tool decision with the tool name and arguments.
- Tool allowlist coverage: share of agents whose tools are an explicit list and whose MCP clients use an
allowedfilter (target 100 per cent). - Approval coverage: share of side-effecting tools behind an interrupt or
cancel_toolpolicy. - Denied and cancelled calls: count per agent per week; a sudden rise suggests injection attempts or a broken prompt.
- Limit hits: how often turn, token and deadline limits are reached, which the production guide recommends tracking to tune limits.
- Wildcard model permissions: number of agent roles still granting Bedrock invoke on
"*"(target zero). - Guardrail interventions: rate of
guardrail_intervenedstop reasons, reviewed for false positives and real attacks.
Rollout plan
- Inventory: list every Strands agent, its model provider, tools, MCP servers, session storage and the identity it runs as. Record owners. The MCP Server Inventory Template covers the server side.
- Fix identity first: move agents from access keys to roles, scope Bedrock permissions to model ARNs, and split tool permissions into narrower roles where tools touch AWS resources.
- Constrain tools: replace any dynamic loading with explicit tool lists, add
allowedfilters to everyMCPClient, and move shell, REPL and file tools into an isolated sandbox. - Add control hooks in observe mode: log what an approval or quota hook would block, and run guardrails in shadow mode, for one or two weeks.
- Enforce: switch hooks to
interrupt()andcancel_tool, enable guardrails, set turn, token and deadline limits. Pilot with one agent, then widen. - Secure state and telemetry: move sessions to protected storage with server-side session IDs, add trace redaction, and set retention.
- Test and keep testing: add prompt injection and tool misuse cases to CI using the AI Agent Security Testing Checklist. Roll back by removing a tool or MCP server from the tool list and redeploying.
Common failure patterns
- Developer credentials in production: an agent prototyped with
aws configurekeys ships with the same keys in an environment variable, giving the agent a human's rights. - Every tool available: a full vended tool set, including shell and Python REPL, left in a production agent that only needed two read tools.
- MCP server with no filter: a connected server adds new write tools in an update and the agent can call them at once because there is no
allowedlist. - Approval rules in the prompt: "always ask before deleting" lives only in the system prompt, and injected content in a fetched document talks the model out of it. Put the rule in a hook.
- Shared session IDs: a fixed or client-supplied session ID lets one user's history and trusted approvals load into another user's agent.
- Traces as a data leak: full prompts and tool outputs, including customer records and tokens, exported to a tracing backend many people can read.
Framework mapping
| Control area | NIST SP 800-53 Rev 5 | NIST CSF 2.0 | OWASP LLM Top 10 2025 / MITRE ATLAS |
|---|---|---|---|
| Scoped IAM for model and tools | AC-6 Least Privilege, AC-3 Access Enforcement | PR.AA-05 | LLM06 Excessive Agency |
| Credentials for Bedrock and MCP | IA-5 Authenticator Management | PR.AA-05 | LLM02 Sensitive Information Disclosure |
| Approval hooks and tool filters | AC-3 Access Enforcement | PR.AA-05 | LLM01 Prompt Injection; AML.T0053 AI Agent Tool Invocation |
| Guardrails at the model boundary | AC-3 Access Enforcement | DE.CM-09 | LLM01 Prompt Injection; AML.T0051 LLM Prompt Injection |
| Execution limits | AC-6 Least Privilege | DE.CM-09 | LLM10 Unbounded Consumption |
| Session storage protection | SC-28 Protection of Information at Rest | PR.AA-05 | LLM02 Sensitive Information Disclosure |
| Traces and audit logs | AU-2 Event Logging, AU-12 Audit Record Generation | DE.CM-09 | LLM06 Excessive Agency |
Relevant ATT&CK techniques for the identity side are T1078.004 Cloud Accounts, for misuse of the agent's role, and T1552.001 Credentials In Files, for keys left in config. See the OWASP Top 10 for LLM Applications Guide.
Practitioner checklist
- Run every Strands agent under an IAM role with temporary credentials; no access keys in environment variables or files.
- Scope
bedrock:InvokeModelorbedrock:InvokeModelWithResponseStreamto specific model or inference profile ARNs. - Set
region_nameand the model ID explicitly in code. - Pass an explicit tool list and keep
load_tools_from_directoryoff. - Give every
MCPClientanallowedtool filter and load MCP credentials from a secrets manager. - Gate side-effecting tools with
BeforeToolCallEventinterrupts orcancel_toolpolicy, and negate must-ask tools inHumanInTheLoop. - Set turn, token and wall-clock limits at agent and multi-agent node level.
- Enable Bedrock guardrails, and turn on output redaction where blocked output must not persist.
- Derive session IDs server-side, protect session storage and never load untrusted sessions.
- Redact traces in an OpenTelemetry collector, restrict access and set retention.
Standards and references
- Strands Agents: Operating agents in production
- Strands Agents: Amazon Bedrock model provider
- Strands Agents: MCP tools
- Strands Agents: MCP transports
- Strands Agents: Interrupts
- Strands Agents: Session management
- Strands Agents: Guardrails
- OWASP: Top 10 for LLM Applications 2025
Related NHI Mgmt Group resources: AWS Bedrock AgentCore Security Guide · Amazon Bedrock Agents Security Guide · MCP Security Guide · Agent Identity and Access Guide · Non-Human Identity (NHI) Guide