Is strands-agents safe?
- Python shell/command execution
- Python network egress
- Python filesystem read
strands-agents is an AI python_package analyzed by SkillTotal's deterministic static scanner. The scan found no malicious indicators, though 6 risky constructs are reported for review. It can: filesystem read, filesystem write, mcp tools detected, network egress and shell execution — capabilities are what the code can do, not a verdict on intent. Risk score 10/100 (low).
strands-agents 1.52.0
Automated static-analysis result. It can contain false positives and false negatives, and is not a claim about the intent of strands-agents's authors. Report a false positive.
Behavioral traits
How this component maps to the CSA agentic threat model. Descriptive — it never affects the risk score.
Findings (6)
The component can run operating-system commands or spawn processes.
proc = await asyncio.create_subprocess_exec(
program,
*args,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
start_new_session=_USE_PROCESS_GROUP,
)Why it matters: Powerful and often legitimate — confirm the commands aren't built from untrusted input.
Fix: Confirm the command and its arguments are fully controlled and not derived from untrusted input; avoid shell=True.
A server is bound to all network interfaces (0.0.0.0), not just your own machine.
llama-server -m model.gguf --host 0.0.0.0 --port 8080
host: The host address to bind the server to. Defaults to "0.0.0.0".
Why it matters: Without authentication, other hosts on the network can reach it.
Fix: Bind to 127.0.0.1 for local-only use, or require authentication and restrict access if remote exposure is intended.
The component reads files from disk.
with open(config_path) as f:
return self._resolve_path(path).read_bytes()
with open(path, encoding="utf-8") as f:
with open(path, "rb") as f:
config_dict = json.loads(path.read_text())
return path.read_text(encoding="utf-8")
parsed = json.loads(path.read_text(encoding="utf-8"))
return path.read_text(encoding="utf-8")
return file_path.read_bytes(), content_type
raw = await self._sandbox.read_text(self._artifact_path(self._METADATA_FILE))
result: dict[str, str] = json.loads(metadata_path.read_text(encoding="utf-8"))
skill = Skill.from_content(await sandbox.read_text(md_path), strict=self._strict)
content = skill_md_path.read_text(encoding="utf-8")
file_content = await sandbox.read_text(file_path)
file_content = await sandbox.read_text(file_path)
file_text = await sandbox.read_text(file_path)
Why it matters: Usually legitimate, but worth confirming it can't be steered into reading sensitive files.
Fix: Confirm which files are read and that paths cannot be influenced by untrusted input to reach sensitive locations.
The component writes or deletes files on disk.
full_path.write_bytes(content)
os.unlink(tmp_path)
shutil.rmtree(session_dir)
with open(tmp_path, "wb") as f:
os.unlink(tmp_path)
os.unlink(path)
await self._sandbox.write_text(self._artifact_path(self._METADATA_FILE), json.dumps(self._content_types))
host_path.write_bytes(content)
metadata_path.write_text(json.dumps(self._content_types), encoding="utf-8")
await sandbox.write_text(file_path, file_text)
await sandbox.write_text(file_path, new_content)
await sandbox.write_text(file_path, new_content)
Why it matters: Usually legitimate, but worth confirming the paths can't be controlled by untrusted input.
Fix: Confirm which files are written/deleted and that paths cannot be influenced by untrusted input.
The component makes outbound network requests.
import httpx
async with httpx.AsyncClient(timeout=self.timeout) as client:
async with httpx.AsyncClient(timeout=self.timeout) as httpx_client:
import httpx
timeout_obj = httpx.Timeout(
connect=timeout[0] if len(timeout) > 0 else None,
read=timeout[1] if len(timeout) > 1 else None,
write=timeout[2] if len(timeout) > 2 else None,
po …timeout_obj = httpx.Timeout(timeout or 30.0)
self.client = httpx.AsyncClient(
base_url=self.base_url,
timeout=timeout_obj,
)from urllib.parse import urlparse
parsed = urlparse(url.rstrip("/"))import urllib.error
import urllib.request
req = urllib.request.Request(url, headers={"User-Agent": "strands-agents-sdk"}) # noqa: S310with urllib.request.urlopen(req, timeout=30) as response: # noqa: S310
active_client = httpx.AsyncClient()
Why it matters: Usually legitimate, but confirm the destinations are expected and no sensitive data leaves.
Fix: Confirm the destination hosts are expected and that no sensitive data is sent off-host.
An MCP tool surface (manifest or tool definitions) was found.
- Python-Based Tools: Simple `@tool` decorator with hot reloading
@tool(context=True)
@tool(context=True)
@tool(context=True)
"Python module names, or @tool annotated functions in files.",
or @tool annotated functions). For tools requiring code-based instantiation with constructor
@tool(name=name, description=description, context="tool_context")
# @tool validation does not enforce ``minItems``, so guard here.
to scan for @hook (and optionally @tool) decorated methods, and shared registry
"""Scan an instance's class hierarchy for @tool decorated methods.
Unlike agent-level Plugin, MultiAgentPlugin does not support @tool decorated methods
with @hook and @tool decorators.
tools: Tools attached to the agent, auto-discovered from @tool decorated methods during __init__
Scans the class for methods decorated with @hook and @tool and stores
"""List of tools the plugin provides, auto-discovered from @tool decorated methods."""
Why it matters: Just context — review which tools it offers and their permissions.
Fix: Review the declared MCP tools and their permissions.
Check your own component
Run the same evidence-backed scan on any MCP server, agent skill, or package.
Scan your own componentHow we determine this: deterministic static analysis (regex + AST), evidence-anchored, no code execution. Methodology →