Is huggingface-hub safe?
- Python shell/command execution
- Python filesystem read
- Python filesystem write/delete
huggingface_hub-1.27.0 is an AI python_package analyzed by SkillTotal's deterministic static scanner. The scan found no malicious indicators, though 5 risky constructs are 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 0/100 (low).
huggingface_hub-1.27.0 1.27.0
Automated static-analysis result. It can contain false positives and false negatives, and is not a claim about the intent of huggingface-hub'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 (5)
The component can run operating-system commands or spawn processes.
result = subprocess.run(
[self.binary_path, "-json"],
input=full_query,
capture_output=True,
text=True,
check=False,
)result = subprocess.run(cmd)
return subprocess.call(cmd)
subprocess.run([uv_path, "venv", str(venv_dir)], check=True)
subprocess.run(install_cmd, check=True, timeout=_EXTENSIONS_PIP_INSTALL_TIMEOUT)
return subprocess.call([str(executable_path)] + args)
return subprocess.call(["sh", str(executable_path)] + args)
subprocess.run(
"git config lfs.customtransfer.multipart.path hf".split(),
check=True,
cwd=local_path,
)subprocess.run(
f"git config lfs.customtransfer.multipart.args {LFS_MULTIPART_UPLOAD_COMMAND}".split(),
check=True,
cwd=local_path,
)res = subprocess.run(command, start_new_session=True)
subprocess.call([*_hf_argv(), "skills", "update", DEFAULT_SKILL_ID, "-g", "--claude"])
return subprocess.run(
command,
capture_output=True,
check=check,
encoding="utf-8",
errors="replace", # if not utf-8, replace char by �
cwd=folder or os.getcwd(),
**kwargs,
)with subprocess.Popen(
command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
encoding="utf-8",
errors="replace", # if not utf-8, replace char by �
cwd=fold …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.
with open(rel_path, "r") as fp:
long_description=open("README.md", "r", encoding="utf-8").read(),with open(filter_file) as f:
with open(plan_file) as f:
with open(self.path_or_fileobj, "rb") as file:
self._write_ranges(path, Path(local_path).read_bytes(), mode)
with open(local_path, "rb") as f:
with open(ref_path) as f:
with open(ref_path) as f:
with open(path, encoding="utf-8") as f:
env_map.update(load_dotenv(Path(env_file).read_text(), environ=extended_environ))
existing = path.read_text() if path.exists() else ""
with open(tmp_path, "rb") as f:
parsed = json.loads(local_path.read_text(encoding="utf-8"))
content = (skill_dir / "SKILL.md").read_text(encoding="utf-8")
return body_file.read_text(encoding="utf-8")
data = json.loads(manifest_path.read_text())
with open(filepath, "rb") as file:
if not ref_path.exists() or commit_hash != ref_path.read_text():
with open(ref_path) as f:
with open(paths.file_path, "rb") as f:
with open(revision_file) as f:
return ResolvedRevision(resolved=ref_path.read_text().strip(), initial=revision)
with open(index_file) as f:
content=relpath_to_abspath["README.md"].read_text(encoding="utf8"),
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(plan_file, "w") as f:
os.remove(file_path)
gitignore.write_text("*")tag_path.write_text(CACHEDIR_TAG_CONTENT)
with open(local_path, "wb") as f:
with open(local_path, "wb") as f:
with open(ref_path, "w") as f:
formatter.write_text(line)
path.write_text(_completion_script("fish"))(install_dir / "SKILL.md").write_text(content, encoding="utf-8")
shutil.rmtree(install_dir)
shutil.rmtree(backup_dir)
manifest_path.write_text(json.dumps(data, indent=2, sort_keys=True))
shutil.rmtree(extension_dir)
shutil.rmtree(extension_dir)
shutil.rmtree(extension_dir, ignore_errors=True)
executable_path.write_bytes(binary)
shutil.rmtree(path)
shutil.copytree(central_skill_path, link_path)
shutil.copytree(temp_dir.name, persistent_temp_dir, dirs_exist_ok=True)
with open(path, "w") as f:
os.remove(dst)
shutil.move(abs_src, abs_dst, copy_function=_copy_no_matter_what)
shutil.copyfile(abs_src, abs_dst)
tmp_path.write_text(commit_hash)
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
self.client = httpx.Client(
base_url=f"{base_host}/--replicas/+{replica_hash}",
headers=build_hf_headers(token=token),
timeout=CLIENT_TIMEOUT,
)import urllib.parse
redirect_uri = f"{login_uri}?{urllib.parse.urlencode(query_params)}"return RedirectResponse(callback_uri + "?" + urllib.parse.urlencode({"_target_url": redirect_uri}))target = "/?" + urllib.parse.urlencode(request.query_params)
import httpx
self._client = httpx.Client(
headers={
"Authorization": f"Bearer {self._auth_token}",
"X-Sandbox-Token": sandbox_token,
},
limits=httpx.Limits(max_connections=max_connectio …limits=httpx.Limits(max_connections=max_connections, max_keepalive_connections=max_connections),
timeout = kwargs.pop("timeout", httpx.Timeout(60.0, connect=10.0))timeout = kwargs.pop("timeout", httpx.Timeout(70.0, connect=10.0)) # server pings every 15sresponse = self._client.get(self.base_url + "/health", timeout=httpx.Timeout(5.0))
import httpx
from urllib.parse import quote
revision=quote(revision, safe=""),
from urllib.parse import quote
self.base_revision_quoted = quote(self.revision, safe="")
self.commit_revision_quoted = quote(pr.git_reference, safe="")
from urllib.parse import urlsplit
ssh_url = urlsplit(job.status.ssh_url)
from urllib.parse import urlsplit
urlsplit(_HF_DEFAULT_ENDPOINT).hostname,
urlsplit(_HF_DEFAULT_STAGING_ENDPOINT).hostname,
urlsplit(ENDPOINT).hostname,
from httpx import HTTPError, Response
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. (12 occurrence(s) shown as evidence).
"authlib>=1.3.2", # minimum version to include https://github.com/lepture/authlib/pull/644
"httpx", # required for authlib but not included in its dependencies
from authlib.integrations.base_client.errors import MismatchingStateError
from authlib.integrations.starlette_client import OAuth
server_metadata_url=constants.OPENID_PROVIDER_URL + "/.well-known/openid-configuration",
"id_token": "FOOBAR",
_TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"
_ID_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id_token"
_REFRESH_TOKEN_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.
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 →