Is usestrix/strix safe?
- Python shell/command execution
- Python network egress
- Python filesystem read
What to do: Nothing here argues against installing it. Grant the capabilities it lists only if you expect the tool to need them.
usestrix/strix is a PyPI package analyzed by SkillTotal's deterministic static scanner. The scan found no malicious indicators, though 1 risky construct is reported for review. It can: delegated authentication, filesystem read, filesystem write, network egress and shell execution — capabilities are what the code can do, not a verdict on intent. Risk score 10/100 (low).
strix-agent 1.7.0
Automated static-analysis result. It can contain false positives and false negatives, and is not a claim about the intent of usestrix/strix'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 (7)
The component can run operating-system commands or spawn processes.
subprocess.run( # noqa: S603 - fixed build command using the resolved Go binary
[
go,
"build",
"-trimpath",
"-ldflags=-s -w",
"-o", …process = subprocess.run( # noqa: S603
command,
check=False,
capture_output=capture_output,
text=True,
env=wallet_env, …return subprocess.run( # noqa: S603
command,
check=False,
capture_output=True,
text=True,
env=wallet_env,
cwd=wallet_ro …return subprocess.run( # noqa: S603
[*_npx_prefix(npx, wallet_root), _LINK_CLI_PACKAGE, *arguments],
check=False,
capture_output=capture_output,
text=True,
env=_wallet_environment …process = subprocess.Popen( # noqa: S603 # nosec B603
command,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)result = subprocess.run( # noqa: S603 # nosec B603
[git, "-C", str(source), "rev-parse", "--show-toplevel"],
check=False,
capture_output=True,
text=True,
)process = await asyncio.create_subprocess_exec(
*command, env=env, cwd=cwd, pass_fds=(child_socket.fileno(),)
)windows_process = subprocess.Popen(command, env=env, cwd=cwd) # noqa: S603
result = subprocess.run(command, check=False) # noqa: S603
return subprocess.run( # noqa: S603
["git", "-C", str(repo_path), *args], # noqa: S607
capture_output=True,
text=True,
check=check,
)return subprocess.run( # noqa: S603
["git", "-C", str(repo_path), *args], # noqa: S607
capture_output=True,
check=check,
)subprocess.run( # noqa: S603
[
git_executable,
"clone",
repo_url,
str(clone_path),
],
capture_output=True, …result = subprocess.run( # noqa: S603
["git", "-C", str(path), *args], # noqa: S607
capture_output=True,
text=True,
check=False,
timeout=5,
)result = subprocess.run( # noqa: S603
["git", "-C", str(repo), *args], # noqa: S607
capture_output=True,
text=True,
check=False,
timeout=_GIT_TIMEOUT_SECONDS,
)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.
if host_lower in ("localhost", "0.0.0.0", "::1"): # nosec B104Why 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.
data = json.loads(AUTH_PATH.read_text(encoding="utf-8"))
data = json.loads(path.read_text(encoding="utf-8"))
snap = json.loads(agents_path.read_text(encoding="utf-8"))
data = _LOGO_PATH.read_bytes()
text = path.read_text(encoding="utf-8")
data = json.loads(AUTH_PATH.read_text(encoding="utf-8"))
raw = json.loads(IDENTITY_PATH.read_text(encoding="utf-8"))
agents_data = json.loads(agents_path.read_text(encoding="utf-8"))
record = json.loads((run_dir / "run.json").read_text(encoding="utf-8"))
return Path(os.devnull).open("a", buffering=1, encoding="utf-8")content = git_meta.read_text(encoding="utf-8").strip()
payload = json.loads(path.read_text(encoding="utf-8"))
for line in path.read_text(encoding="utf-8").splitlines()
"content": source.read_bytes(),
data = json.loads(AUTH_PATH.read_text(encoding="utf-8"))
content = target.read_bytes()
return report_path.read_text(encoding="utf-8")
return json.loads(path.read_text(encoding="utf-8"))
data = json.loads(path.read_text(encoding="utf-8"))
return json.loads(path.read_text(encoding="utf-8"))
data = json.loads(json_path.read_text(encoding="utf-8"))
data = json.loads(path.read_text(encoding="utf-8"))
content = git_file.read_text(encoding="utf-8", errors="replace")
content = file_path.read_text(encoding="utf-8")
content = file_path.read_text(encoding="utf-8")
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.
_CACHE_PATH.write_text(json.dumps(cache), encoding="utf-8")
shutil.copy2(new_binary, staged)
path.write_text(json.dumps(collection, indent=2), encoding="utf-8")
shutil.copy2(source, staging / name)
shutil.rmtree(clone_path)
atomic_write_text(path, json.dumps(document, ensure_ascii=False, indent=2, default=str))
atomic_write_text(
run_record_path(run_dir),
json.dumps(run_record, ensure_ascii=False, indent=2, default=str),
)atomic_write_text(
vuln_dir / f"{report['id']}.md",
render_vulnerability_md(report),
)atomic_write_text(csv_path, csv_buf.getvalue())
atomic_write_text(
run_dir / "vulnerabilities.json",
json.dumps(vulnerability_reports, ensure_ascii=False, indent=2, default=str),
)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.
const res = await fetch(path, { cache: "no-store" });const res = await fetch(path, {void fetch("/api/event", { method: "POST", body: payload, keepalive: true });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.
The component makes outbound network requests.
import requests
with requests.post(
TOKEN_URL,
data=payload,
headers={"Accept": "application/json"},
timeout=_TOKEN_TIMEOUT,
) as response:import httpx
http_client = httpx.AsyncClient(
timeout=httpx.Timeout(600.0, connect=30.0),
event_hooks={"request": [_auth_hook]},
)timeout=httpx.Timeout(600.0, connect=30.0),
import requests
response = requests.request(
method,
url,
headers=headers,
params={
key: ("true" if value else "false") if isinstance(value, bool) else value
for key, value in …response = requests.put(
signed_url,
data=stream,
headers={
"Authorization": f"Bearer {upload_token}",
"Content-Type": "application/zip", …import requests
response = requests.request(
"POST",
state.upstream_url,
headers=headers,
data=body,
timeout=state.timeout,
allow_redire …import requests
import requests
response = requests.post(
f"{app_url}/api/v1/cli/login",
timeout=_HTTP_TIMEOUT_S,
allow_redirects=False,
)poll = requests.post(
f"{app_url}/api/v1/cli/login/poll",
json=poll_body,
timeout=_HTTP_TIMEOUT_S,
allow_redirects=False,
)response = requests.post(
f"{app_url}/api/v1/cli/login/complete",
json=body,
timeout=_HTTP_TIMEOUT_S,
allow_redirects=False,
)response = requests.delete(
f"{app_url.rstrip('/')}/api/v1/cli/session",
headers=_session_headers(record),
timeout=_HTTP_TIMEOUT_S,
allow_redirects=False,
)requests.delete(
f"{previous['app_url']}/api/v1/cli/session",
headers=_session_headers(previous),
timeout=_HTTP_TIMEOUT_S,
allow_redirects=False,
)import requests
with requests.get(
f"https://api.github.com/repos/{GITHUB_REPO}/releases/latest",
timeout=REQUEST_TIMEOUT_SECONDS,
) as response:with requests.get(
f"https://pypi.org/pypi/{PYPI_PACKAGE}/json",
timeout=REQUEST_TIMEOUT_SECONDS,
) as response:with requests.get(
f"https://api.github.com/repos/{GITHUB_REPO}/releases/tags/v{version}",
timeout=REQUEST_TIMEOUT_SECONDS,
) as response:with requests.get( # nosec B113
url,
stream=True,
timeout=REQUEST_TIMEOUT_SECONDS * 12,
) as response:import requests
with requests.get(check_url, headers={"User-Agent": "git/2.43.0"}, timeout=10) as resp:import requests
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 OAuth 2.0 / OpenID Connect delegated-authentication flow was detected (authorization-code / refresh-token / token-exchange grant, an OIDC authorize/discovery endpoint or id_token, or a delegation library). Tools authenticate with the end user's delegated, scoped credentials rather than a long-lived embedded service credential. (5 occurrence(s) shown as evidence).
AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize"
data.get("id_token") if isinstance(data.get("id_token"), str) else """grant_type": "authorization_code",
"grant_type": "refresh_token",
Fix: Delegated auth is a lower-blast-radius execution context than an embedded static credential. Confirm the requested scopes are minimal and that tokens are never logged or forwarded off-host.
How attackers abuse these capabilities
Interactive labs on the attack class behind the rules above. They show the technique, not anything found in usestrix/strix.
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 →