Is smolagents safe?
- Python shell/command execution
- Unsafe deserialization
- Python dynamic code execution
smolagents is an AI python_package analyzed by SkillTotal's deterministic static scanner. The scan found no malicious indicators, though 7 risky constructs are reported for review. It can: dynamic code execution, 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 20/100 (low).
smolagents 1.26.0
Automated static-analysis result. It can contain false positives and false negatives, and is not a claim about the intent of smolagents's authors. Report a false positive.
Findings (7)
It loads data with a format that can rebuild arbitrary objects (e.g. pickle, or unsafe YAML).
return pickle.loads(base64.b64decode(encoded_value[7:]))
return pickle.loads(base64.b64decode(data[7:]))
return pickle.loads(base64.b64decode(data))
Why it matters: Feeding such a loader untrusted data can execute code hidden inside that data.
Fix: Deserialize untrusted data with a safe format/loader: JSON, or yaml.safe_load / Loader=SafeLoader. Reserve pickle/marshal for data you fully control.
The code turns strings into live code at runtime (eval / new Function / exec).
exec(tool_code, module.__dict__)
Why it matters: If those strings aren't fixed and trusted, they become a way to run arbitrary code.
Fix: Avoid evaluating dynamically constructed code; if unavoidable, ensure the input is a trusted constant and never derived from external data.
The component can run operating-system commands or spawn processes.
subprocess.run([deno_path, "--version"], capture_output=True, check=True)
self.server_process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)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.
The component reads files from disk.
agent_dict = json.loads((folder / "agent.json").read_text())
tool_code = (folder / "tools" / f"{tool_name}.py").read_text()importlib.resources.files("smolagents.prompts").joinpath("toolcalling_agent.yaml").read_text()importlib.resources.files("smolagents.prompts").joinpath("structured_code_agent.yaml").read_text()importlib.resources.files("smolagents.prompts").joinpath("code_agent.yaml").read_text()tool_code = Path(tool_file).read_text()
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.
with open(os.path.join(output_dir, "prompts.yaml"), "w", encoding="utf-8") as f:
with open(os.path.join(output_dir, "agent.json"), "w", encoding="utf-8") as f:
with open(os.path.join(output_dir, "requirements.txt"), "w", encoding="utf-8") as f:
with open(os.path.join(output_dir, "app.py"), "w", encoding="utf-8") as f:
shutil.copy(file_path, dest_path)
with open(self.runner_path, "w") as f:
shutil.rmtree(self.runner_dir)
file_path.write_text(content, encoding="utf-8")
with open(os.path.join(folder, "__init__.py"), "w"):
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 requests
response = requests.get(self._path)
import requests
response = requests.get(base_url, params=params)
import requests
response = requests.get(self.endpoint, headers=self.headers, params=params)
import requests
response = requests.get(
"https://lite.duckduckgo.com/lite/",
params={"q": query},
headers={"User-Agent": "Mozilla/5.0"},
)import requests
response = requests.get(
"https://www.bing.com/search",
params={"q": query, "format": "rss"},
)import requests
response = requests.post(
"https://api.exa.ai/search",
headers={
"x-api-key": api_key,
"Content-Type": "application/json",
"x-exa-integration": "smolagents", …import requests
from requests.exceptions import RequestException
response = requests.get(url, timeout=20)
import requests
response = requests.get(self.server_url)
response = requests.post(self.server_url, json=payload, timeout=self.timeout)
import requests
from requests.exceptions import RequestException
r = requests.post(crate_kernel_endpoint, headers=headers)
if requests.get(f"{self.base_url}/api/kernelspecs?token={token}", timeout=2).status_code == 200:resp = requests.get(f"https://{host}/api/kernelspecs?token={token}")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.
source_code = get_source(self.forward).replace("@tool", "")# If tool was created using '@tool' decorator, it has only a forward pass, so it's simpler to just get its code
forward_source_code = forward_source_code.replace("@tool", "").strip()else: # If the tool was not created by the @tool decorator, it was made by subclassing Tool
f"Multiple @tool decorators found on function '{func_node.name}'. Only one @tool decorator is allowed."f"Multiple @tool decorators found on function '{func_node.name}'. Only one @tool decorator is allowed."f"Function '{func_node.name}' has decorators other than @tool. "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 componentOr get notified if this component's risk changes:
How we determine this: deterministic static analysis (regex + AST), evidence-anchored, no code execution. Methodology →