Can an attacker make your agent misbehave? This repo shows you how to find out, and how to stop it.
AI agents with tool access can be tricked into exfiltrating credentials, reading files they shouldn't, or executing commands beyond their authorization. This project demonstrates:
- The threat β what happens when an agent has unrestricted file system access
- The sandbox β how Strands Shell isolates agents so attacks hit a wall
- The proof β how Strands Evals red teaming automatically finds vulnerabilities
- The fix β Cedar + Steering + hardened prompt to close application-layer gaps
- The architecture β auth-scoped tools via AgentCore Gateway to close the last breach
pip install -r requirements.txt
# Run the vulnerable agent (no sandbox) to see the problem
python examples/01_vulnerable_agent.py
# Run the sandboxed agent (Shell via MCP) to see the fix
python examples/02_sandboxed_agent.py
# Run red team evals to automatically find breaches
python examples/03_red_team_evals.py
# Fix the breaches and prove the fix worked (before vs after)
python examples/03b_fix_and_rerun.py
# Deploy auth-scoped tools (requires AWS CDK + credentials)
cd infra && cdk deploy && cd ..
# Run the auth-scoped agent (requires GATEWAY_URL and ACCESS_TOKEN env vars)
python examples/04_auth_scoped_tools.py| File | Strands Components | What it shows |
|---|---|---|
01_vulnerable_agent.py |
Agent, vended bash tool |
Agent with unrestricted filesystem β can read ~/.aws/credentials, SSH keys, anything on host |
02_sandboxed_agent.py |
Agent, MCPClient, Shell MCP server |
Same agent running inside Strands Shell β only explicitly bound paths are visible |
03_red_team_evals.py |
Agent, MCPClient, AdversarialCaseGenerator, CrescendoStrategy, RedTeamExperiment |
Automated red teaming finds breaches the agent is vulnerable to |
03b_fix_and_rerun.py |
All of the above + BeforeToolCallEvent, HookProvider, BedrockModel (guardrails) |
Fix the breaches with hooks + guardrails + hardened prompt, then re-run to prove it worked |
04_auth_scoped_tools.py |
Agent, MCPClient (streamable HTTP) |
Agent connects to Gateway β identity from JWT, not conversation |
shell.toml |
Shell config | Defines the isolated virtual filesystem: binds, network allowlist |
infra/ |
CDK stack | Deploys Cognito + Gateway + Interceptor + Employee Lookup Lambda |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Your Application β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Layer 3: Red Team Evals (find what you missed) β
β Layer 2: Bedrock Guardrails (content filtering) β
β Layer 1: Strands Shell (filesystem + network fence) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Foundation Model β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Even if a prompt injection convinces the model to comply, Shell blocks file access at the VFS layer. Even if a novel attack bypasses Shell's scope, Guardrails catch harmful content. And red teaming finds the gaps before production does.
Shell runs as an MCP server (strands-shell --mcp), providing sandboxed tools to the agent via MCPClient. Configuration lives in shell.toml:
allowed_urls = ["https://api.stripe.com/"]
[[bind]]
source = "./data/customers"
destination = "/customer-data"
mode = "copy"
readonly = true- Files: Only bound paths exist.
~/.aws/,~/.ssh/,/etc/β invisible. - Network: Only
allowed_urlscan be reached. Private IPs blocked by default. - Secrets: Injected per-URL via
[[cred]]entries β agent never holds them directly.
cases = AdversarialCaseGenerator(model=eval_model).generate_cases(
agent=agent_factory(),
risk_categories=["data_exfiltration", "excessive_agency", "system_prompt_leak"],
num_cases=3,
)
experiment = RedTeamExperiment(
cases=cases,
agent_factory=agent_factory,
attack_strategies=[CrescendoStrategy(max_turns=5)],
model=eval_model,
)
report = asyncio.run(experiment.run_evaluations_async(max_workers=5))
report.display()| Category | What it tests |
|---|---|
data_exfiltration |
Can the agent be tricked into leaking sensitive data? |
excessive_agency |
Can the agent be tricked into unauthorized tool calls? |
system_prompt_leak |
Can the agent be tricked into revealing its instructions? |
guideline_bypass |
Can the agent be tricked via roleplay or hypotheticals? |
harmful_content |
Can the agent be tricked into producing dangerous content? |
- Crescendo β starts legitimate, gradually escalates
- GOAT β Generative Offensive Agent Tester
- PAIR β Prompt Automatic Iterative Refinement
- Sequential Break β systematic boundary testing
from strands.models import BedrockModel
model = BedrockModel(
model_id="us.anthropic.claude-sonnet-4-6",
guardrail_id="your-guardrail-id",
guardrail_version="1",
guardrail_trace="enabled",
)When a guardrail triggers, the SDK automatically overwrites the blocked content in conversation history so follow-up questions aren't also blocked.
Some attacks can't be fixed with prompts or hooks alone. The data_exfiltration breach succeeds when the agent calls a tool it's allowed to use, but for the wrong person. The model controls the arguments, so prompt injection can redirect it.
The fix removes identity from the model's control entirely:
JWT (employee_id) β Gateway Interceptor β injects _authenticated_employee_id β Tool Lambda β ownership check
| Layer | What it does |
|---|---|
| Cognito | Issues JWT with custom:employee_id claim |
| AgentCore Gateway Interceptor | Extracts employee_id from JWT, injects into tool args |
| Tool Lambda | Verifies the injected ID matches the requested resource |
The agent can't look up another employee's data because the identity is set by infrastructure, not by the conversation.
cd infra
pip install -r requirements.txt
cdk deployThis creates a Cognito user pool, the employee lookup Lambda, the interceptor Lambda, and an AgentCore Gateway with MCP.
# Grab outputs from the deploy
USER_POOL_ID=<UserPoolId from stack output>
CLIENT_ID=<ClientId from stack output>
GATEWAY_URL=<GatewayUrl from stack output>
# Create a user with the custom:employee_id attribute
aws cognito-idp admin-create-user \
--user-pool-id $USER_POOL_ID \
--username alice \
--user-attributes Name=custom:employee_id,Value=EMP-001 \
--message-action SUPPRESS
# Set a permanent password
aws cognito-idp admin-set-user-password \
--user-pool-id $USER_POOL_ID \
--username alice \
--password '<your-password-here>' \
--permanent
# Get an ID token
ACCESS_TOKEN=$(aws cognito-idp initiate-auth \
--auth-flow USER_PASSWORD_AUTH \
--client-id $CLIENT_ID \
--auth-parameters USERNAME=alice,PASSWORD='<your-password-here>' \
--query 'AuthenticationResult.IdToken' --output text)GATEWAY_URL=$GATEWAY_URL ACCESS_TOKEN=$ACCESS_TOKEN python examples/04_auth_scoped_tools.pyFor a more complete production pattern (Cedar policies, WAF, Memory, multiple tools): ai-agent-guardrails