Files
unslothai--unsloth/studio/backend/tests/test_bypass_permissions.py
T
wehub-resource-sync e93507a09c
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:59:56 +08:00

737 lines
27 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
"""Tests for Bypass Permissions (skip confirmation + disable sandbox).
Covers the secret-name classifier, the two env builders, the
``disable_sandbox`` branch of ``_python_exec`` / ``_bash_exec`` (which env is
used, which pre-exec is used, and that safety checks / the blocklist are
skipped), the request-model default, the confirm-vs-bypass precedence rule the
route enforces, and that the agentic loop forwards ``disable_sandbox`` while
never gating under bypass.
Run with: ``PYTHONPATH=studio/backend python -m pytest studio/backend/tests/test_bypass_permissions.py -q``
"""
import os
import sys
import pytest
import core.inference.tools as tools
from core.inference.tools import (
_bash_exec,
_build_bypass_env,
_build_safe_env,
_is_cred_location_env_name,
_is_secret_env_name,
_is_secret_env_value,
_python_exec,
)
from core.inference.safetensors_agentic import run_safetensors_tool_loop
_POSIX_ONLY = pytest.mark.skipif(
sys.platform == "win32", reason = "preexec_fn / setsid are POSIX-only"
)
# ── secret-name classifier ──────────────────────────────────────────
@pytest.mark.parametrize(
"name",
[
"HF_TOKEN",
"HUGGING_FACE_HUB_TOKEN",
"WANDB_API_KEY",
"GH_TOKEN",
"GITHUB_TOKEN",
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"AWS_SECRET_ACCESS_KEY",
"AWS_ACCESS_KEY_ID",
"AZURE_CLIENT_SECRET",
"GOOGLE_APPLICATION_CREDENTIALS",
"MY_DB_PASSWORD",
"x_api_key",
"SOME_PRIVATE_KEY",
"LD_PRELOAD",
],
)
def test_secret_names_are_flagged(name):
assert _is_secret_env_name(name) is True
@pytest.mark.parametrize(
"name", ["PATH", "HOME", "LANG", "TERM", "PWD", "SHELL", "HOSTVAR", "MY_VAR"]
)
def test_benign_names_are_not_flagged(name):
assert _is_secret_env_name(name) is False
# ── env builders ────────────────────────────────────────────────────
def test_bypass_env_keeps_benign_strips_secret_repoints_home(monkeypatch, tmp_path):
monkeypatch.setenv("HOSTVAR", "benign-123")
monkeypatch.setenv("HF_TOKEN", "secret-abc")
env = _build_bypass_env(str(tmp_path))
assert env.get("HOSTVAR") == "benign-123" # full host env inherited
assert "HF_TOKEN" not in env # ...minus secrets
assert env["HOME"] == str(tmp_path) # $HOME-based cred lookups defused
assert env["TMPDIR"] == str(tmp_path)
def test_safe_env_excludes_host_and_secret(monkeypatch, tmp_path):
monkeypatch.setenv("HOSTVAR", "benign-123")
monkeypatch.setenv("HF_TOKEN", "secret-abc")
env = _build_safe_env(str(tmp_path))
assert "HOSTVAR" not in env # whitelist build -> host vars never reach child
assert "HF_TOKEN" not in env
# ── Popen kwargs capture (no real execution) ────────────────────────
class _FakeProc:
returncode = 0
def communicate(self, timeout = None):
return ("FAKEOUT", None)
def poll(self):
return 0
def kill(self):
pass
@pytest.fixture
def captured_popen(monkeypatch):
cap = {}
def fake_popen(cmd, **kwargs):
cap["cmd"] = cmd
cap["kwargs"] = kwargs
return _FakeProc()
monkeypatch.setattr(tools.subprocess, "Popen", fake_popen)
return cap
@_POSIX_ONLY
def test_python_sandboxed_uses_sandbox_preexec_and_safe_env(captured_popen, monkeypatch):
monkeypatch.setenv("HF_TOKEN", "secret-abc")
_python_exec("print(1)", None, 5, "t", disable_sandbox = False)
assert captured_popen["kwargs"]["preexec_fn"] is tools._sandbox_preexec
assert "HF_TOKEN" not in captured_popen["kwargs"]["env"]
@_POSIX_ONLY
def test_python_bypass_uses_bypass_preexec_and_bypass_env(captured_popen, monkeypatch):
monkeypatch.setenv("HOSTVAR", "benign-xyz")
monkeypatch.setenv("HF_TOKEN", "secret-abc")
_python_exec("print(1)", None, 5, "t", disable_sandbox = True)
assert captured_popen["kwargs"]["preexec_fn"] is tools._bypass_preexec
env = captured_popen["kwargs"]["env"]
assert env.get("HOSTVAR") == "benign-xyz"
assert env.get("PYTHONIOENCODING") == "utf-8"
assert "HF_TOKEN" not in env
def test_bash_blocklist_enforced_when_sandboxed(captured_popen):
out = _bash_exec("rm -rf /", None, 5, "t", disable_sandbox = False)
assert "Blocked" in out
assert "cmd" not in captured_popen # never reached Popen
def test_bash_blocklist_skipped_when_bypassed(captured_popen):
out = _bash_exec("rm -rf /", None, 5, "t", disable_sandbox = True)
assert out == "FAKEOUT" # blocklist skipped -> reached (faked) execution
assert captured_popen["cmd"][0] in ("bash", "cmd")
@_POSIX_ONLY
def test_bash_bypass_uses_bypass_preexec(captured_popen, monkeypatch):
# bypass inherits benign host vars; clear so we assert _bash_exec adds none.
monkeypatch.delenv("PYTHONIOENCODING", raising = False)
_bash_exec("echo hi", None, 5, "t", disable_sandbox = True)
assert captured_popen["kwargs"]["preexec_fn"] is tools._bypass_preexec
assert "PYTHONIOENCODING" not in captured_popen["kwargs"]["env"]
# ── real end-to-end python execution under bypass ───────────────────
@_POSIX_ONLY
def test_python_bypass_real_exec_sees_host_env_but_not_secret(monkeypatch):
monkeypatch.setenv("HOSTVAR", "benign-xyz")
monkeypatch.setenv("HF_TOKEN", "secret-pqr")
code = (
"import os;"
"print('H=' + str(os.environ.get('HOSTVAR')),"
" 'T=' + str(os.environ.get('HF_TOKEN')))"
)
out = _python_exec(code, None, 30, "test-bypass", disable_sandbox = True)
assert "H=benign-xyz" in out # unrestricted: real host var visible
assert "T=None" in out # ...but the secret was stripped
assert "secret-pqr" not in out
# ── _bypass_preexec is setsid-only (no rlimits) ─────────────────────
@_POSIX_ONLY
def test_bypass_preexec_only_sets_session(monkeypatch):
calls = {"setsid": 0}
monkeypatch.setattr(
tools.os, "setsid", lambda: calls.__setitem__("setsid", calls["setsid"] + 1)
)
# _resource must not be touched by the bypass pre-exec.
if tools._resource is not None:
monkeypatch.setattr(
tools._resource,
"setrlimit",
lambda *a, **k: pytest.fail("bypass pre-exec must not set rlimits"),
)
tools._bypass_preexec()
assert calls["setsid"] == 1
# ── request model default ───────────────────────────────────────────
def test_request_model_bypass_default_false():
from models.inference import ChatCompletionRequest
assert ChatCompletionRequest.model_fields["bypass_permissions"].default is False
# ── confirm-vs-bypass precedence (mirrors the route rule) ───────────
@pytest.mark.parametrize(
"confirm,bypass,effective_confirm",
[
(False, False, False),
(True, False, True),
(False, True, False),
(True, True, False),
],
)
def test_confirm_precedence_rule(confirm, bypass, effective_confirm):
# The route computes: confirm_tool_calls = confirm and not bypass.
assert (bool(confirm) and not bool(bypass)) is effective_confirm
# ── agentic loop forwards disable_sandbox, never gates under bypass ──
_DEFAULT_TOOLS = [
{"type": "function", "function": {"name": "python"}},
{"type": "function", "function": {"name": "web_search"}},
]
def _tool_call(name, args_json):
return f'<tool_call>{{"name": "{name}", "arguments": {args_json}}}</tool_call>'
def _multi_turn(turns):
it = iter(turns)
def _gen(_messages):
try:
yield next(it)
except StopIteration:
return
return _gen
def test_loop_forwards_disable_sandbox_and_does_not_gate():
seen = []
def fake_exec(
name,
arguments,
*,
cancel_event = None,
timeout = None,
session_id = None,
rag_scope = None,
disable_sandbox = False,
):
seen.append(disable_sandbox)
return f"RAN[{name}]"
events = list(
run_safetensors_tool_loop(
single_turn = _multi_turn([_tool_call("python", '{"code": "x"}'), "done"]),
messages = [{"role": "user", "content": "hi"}],
tools = _DEFAULT_TOOLS,
execute_tool = fake_exec,
session_id = "s",
confirm_tool_calls = False, # route forces this off under bypass
bypass_permissions = True,
)
)
assert seen == [True] # disable_sandbox threaded through
starts = [e for e in events if e["type"] == "tool_start"]
assert starts and starts[0]["awaiting_confirmation"] is False
assert starts[0]["approval_id"] == ""
def test_loop_bypass_overrides_confirm_for_direct_callers():
# Even if a direct internal caller passes confirm_tool_calls=True, bypass
# must suppress the confirm gate at the loop level (not only at the route).
def fake_exec(
name,
arguments,
*,
cancel_event = None,
timeout = None,
session_id = None,
rag_scope = None,
disable_sandbox = False,
):
return f"RAN[{name}]"
events = list(
run_safetensors_tool_loop(
single_turn = _multi_turn([_tool_call("python", '{"code": "x"}'), "done"]),
messages = [{"role": "user", "content": "hi"}],
tools = _DEFAULT_TOOLS,
execute_tool = fake_exec,
session_id = "s",
confirm_tool_calls = True, # raw caller leaves this on...
bypass_permissions = True, # ...but bypass must still win
)
)
starts = [e for e in events if e["type"] == "tool_start"]
assert starts and starts[0]["awaiting_confirmation"] is False
assert starts[0]["approval_id"] == ""
def test_gguf_loop_confirm_gate_respects_bypass():
# The GGUF loop needs a live llama-server, so (per the other llama_cpp
# tests) assert via AST that its _needs_confirm gate applies the bypass
# precedence, mirroring the safetensors behavioral test above.
import ast
import inspect
import textwrap
llama_cpp = pytest.importorskip("core.inference.llama_cpp")
src = textwrap.dedent(
inspect.getsource(llama_cpp.LlamaCppBackend.generate_chat_completion_with_tools)
)
gates = [
node
for node in ast.walk(ast.parse(src))
if isinstance(node, ast.Assign)
and any(getattr(t, "id", None) == "needs_confirm" for t in node.targets)
]
assert gates, "could not find the needs_confirm gate in the GGUF loop"
names = {n.id for g in gates for n in ast.walk(g.value) if isinstance(n, ast.Name)}
assert "confirm_tool_calls" in names
assert "bypass_permissions" in names # bypass must suppress the GGUF gate
# ── broker / capability env vars are stripped (regression) ──────────
@pytest.mark.parametrize(
"name",
["SSH_AUTH_SOCK", "SSH_AGENT_PID", "GPG_AGENT_INFO", "GNUPGHOME", "KUBECONFIG"],
)
def test_broker_capability_names_are_flagged(name):
# Not secrets by value, but they hand the child the operator's live agent
# (ssh/gpg) or kube credentials, so bypass mode must drop them.
assert _is_secret_env_name(name) is True
# ── credential-bearing URL values stripped regardless of name ───────
@pytest.mark.parametrize(
"value",
[
"https://user:s3cr3t@feed.example.invalid/simple", # user:pass@
"https://ghp_deadbeef@github.com/org/private.git", # token-only@
"https://__token__@pypi.example.invalid/simple",
"https://ghp_1234:@npm.pkg.github.com/simple", # empty password
"postgres://dbuser:dbpass@db.example.invalid/app",
],
)
def test_url_userinfo_values_are_flagged(value):
assert _is_secret_env_value(value) is True
@pytest.mark.parametrize(
"value",
[
"https://example.invalid/simple", # no userinfo
"http://proxy.corp.example:8080", # benign proxy
"https://pypi.corp.example/simple", # benign internal index
"redis://localhost:6379/0", # no creds
"https://example.invalid/path?ref=a@b", # '@' only in query, not userinfo
],
)
def test_non_credential_url_values_are_not_flagged(value):
assert _is_secret_env_value(value) is False
def test_url_userinfo_value_is_stripped_even_with_benign_name(monkeypatch, tmp_path):
# NAME dodges the classifier, but the VALUE embeds userinfo -> must go.
monkeypatch.setenv("MY_FEED", "https://user:s3cr3t@feed.example.invalid/simple")
monkeypatch.setenv("REPO_URL", "https://ghp_deadbeef@github.com/org/private.git")
# A URL without credentials is harmless and should be kept.
monkeypatch.setenv("PLAIN_URL", "https://example.invalid/simple")
env = _build_bypass_env(str(tmp_path))
assert "MY_FEED" not in env
assert "REPO_URL" not in env
assert env.get("PLAIN_URL") == "https://example.invalid/simple"
def test_bypass_env_keeps_noncredential_proxy_and_index_urls(monkeypatch, tmp_path):
# Benign routing/config vars must survive bypass mode (proxy-only or
# internal-index networks); only credentialed values are dropped.
monkeypatch.setenv("HTTP_PROXY", "http://proxy.corp.example:8080")
monkeypatch.setenv("PIP_INDEX_URL", "https://pypi.corp.example/simple")
monkeypatch.setenv("PIP_EXTRA_INDEX_URL", "https://user:token@pypi.example.invalid/simple")
env = _build_bypass_env(str(tmp_path))
assert env["HTTP_PROXY"] == "http://proxy.corp.example:8080"
assert env["PIP_INDEX_URL"] == "https://pypi.corp.example/simple"
assert "PIP_EXTRA_INDEX_URL" not in env # this one carries credentials
# ── AWS IMDS-disable hardening flag is kept (regression) ────────────
def test_aws_imds_disable_flag_is_kept_but_creds_stripped(monkeypatch, tmp_path):
# AWS_EC2_METADATA_DISABLED is a non-secret opt-out: dropping it would let a
# bypassed boto/AWS-CLI call fall back to the instance role via IMDS even
# though the operator disabled that path. Keep it; drop the real creds.
monkeypatch.setenv("AWS_EC2_METADATA_DISABLED", "true")
monkeypatch.setenv("AWS_ACCESS_KEY_ID", "AKIAEXAMPLE")
monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "shhh")
assert _is_secret_env_name("AWS_EC2_METADATA_DISABLED") is False
assert _is_secret_env_name("AWS_ACCESS_KEY_ID") is True
env = _build_bypass_env(str(tmp_path))
assert env.get("AWS_EC2_METADATA_DISABLED") == "true"
assert "AWS_ACCESS_KEY_ID" not in env
assert "AWS_SECRET_ACCESS_KEY" not in env
# ── connection-string env vars are stripped (regression) ────────────
@pytest.mark.parametrize(
"name",
[
"SQLCONNSTR_DB", # Azure App Service injected connection strings
"MYSQLCONNSTR_DB",
"SQLAZURECONNSTR_DB",
"POSTGRESQLCONNSTR_DB",
"CUSTOMCONNSTR_CACHE",
"WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
],
)
def test_connection_string_names_are_flagged(name):
assert _is_secret_env_name(name) is True
@pytest.mark.parametrize(
"value",
[
"Server=tcp:db;Database=app;User ID=u;Password=p@ss;", # ADO.NET
"DefaultEndpointsProtocol=https;AccountName=x;AccountKey=abc123==;", # storage
"Endpoint=sb://x;SharedAccessKeyName=n;SharedAccessKey=zzz=", # Service Bus
],
)
def test_connection_string_values_are_flagged(value):
assert _is_secret_env_value(value) is True
@pytest.mark.parametrize(
"value",
[
"Server=tcp:db;Database=app;User ID=u;", # no password field
"Endpoint=sb://x;SharedAccessKeyName=n", # key NAME only, no secret
"AccountName=x;EndpointSuffix=core.windows.net", # no AccountKey
],
)
def test_connection_string_noncredential_values_are_not_flagged(value):
assert _is_secret_env_value(value) is False
def test_connection_string_value_stripped_even_with_benign_name(monkeypatch, tmp_path):
# NAME dodges the classifier, but the VALUE is a credentialed conn string.
monkeypatch.setenv("APP_DB", "Server=tcp:db;Database=app;User ID=u;Password=p@ss;")
monkeypatch.setenv("SQLCONNSTR_DB", "DefaultEndpointsProtocol=https;AccountKey=abc==")
env = _build_bypass_env(str(tmp_path))
assert "APP_DB" not in env # value-based catch
assert "SQLCONNSTR_DB" not in env # name-based catch
# ── temp dirs repointed on every platform (regression) ──────────────
def test_bypass_env_repoints_all_temp_vars(monkeypatch, tmp_path):
# Windows tempfile honours TEMP/TMP, not TMPDIR; all three must repoint.
monkeypatch.setenv("TEMP", "/host/tmp")
monkeypatch.setenv("TMP", "/host/tmp")
env = _build_bypass_env(str(tmp_path))
assert env["TMPDIR"] == str(tmp_path)
assert env["TEMP"] == str(tmp_path)
assert env["TMP"] == str(tmp_path)
# ── credential-location redirect vars are dropped (regression) ──────────
# Vars that point SDKs at the real home/cache/config (cached tokens), e.g.
# HF_HOME which startup always sets -> the live leak the HOME repoint missed.
@pytest.mark.parametrize(
"name",
[
"HF_HOME",
"HF_HUB_CACHE",
"HUGGINGFACE_HUB_CACHE",
"HF_XET_CACHE",
"TRANSFORMERS_CACHE",
"HF_DATASETS_CACHE",
"XDG_CONFIG_HOME",
"XDG_CACHE_HOME",
"XDG_DATA_HOME",
"NETRC",
"BOTO_CONFIG",
"PIP_CONFIG_FILE",
"CLOUDSDK_CONFIG",
"KAGGLE_CONFIG_DIR",
"DOCKER_CONFIG",
"WANDB_DIR",
"WANDB_CONFIG_DIR",
"NPM_CONFIG_USERCONFIG",
"NPM_CONFIG_GLOBALCONFIG",
"YARN_RC_FILENAME",
"GIT_CONFIG_GLOBAL",
"GIT_CONFIG_SYSTEM",
"CARGO_HOME",
"RCLONE_CONFIG",
"GIT_ASKPASS",
"SSH_ASKPASS",
"BASH_ENV",
"HOMEDRIVE",
"HOMEPATH",
],
)
def test_cred_location_names_are_flagged(name):
assert _is_cred_location_env_name(name) is True
@pytest.mark.parametrize("name", ["PATH", "HOME", "LANG", "PWD", "MY_VAR"])
def test_benign_names_not_flagged_as_cred_location(name):
assert _is_cred_location_env_name(name) is False
def test_bypass_env_drops_hf_home_so_cached_token_unreachable(monkeypatch, tmp_path):
# The live leak: startup sets HF_HOME at the real cache, whose $HF_HOME/token
# holds the operator's token. Repointing HOME does not stop huggingface_hub
# from reading $HF_HOME/token, so HF_HOME must be dropped in bypass mode.
real_cache = tmp_path / "real_hf_cache"
real_cache.mkdir()
(real_cache / "token").write_text("hf_cachedOperatorToken")
monkeypatch.setenv("HF_HOME", str(real_cache))
monkeypatch.setenv("HF_HUB_CACHE", str(real_cache / "hub"))
env = _build_bypass_env(str(tmp_path))
assert "HF_HOME" not in env # dropped -> HF falls back to $HOME/.cache (empty)
assert "HF_HUB_CACHE" not in env
def test_bypass_env_hf_token_resolves_outside_real_cache(monkeypatch, tmp_path):
# End-to-end: even when HF_HOME and XDG_CACHE_HOME both point at the real
# cache, the bypass env must make huggingface_hub resolve the token under the
# workdir (guards the XDG fallback chain, not just "HF_HOME absent").
pytest.importorskip("huggingface_hub")
import subprocess
real_cache = tmp_path / "real_hf"
real_cache.mkdir()
workdir = tmp_path / "sandbox"
workdir.mkdir()
monkeypatch.setenv("HF_HOME", str(real_cache))
monkeypatch.setenv("XDG_CACHE_HOME", str(real_cache))
monkeypatch.setenv("XDG_CONFIG_HOME", str(real_cache))
env = _build_bypass_env(str(workdir))
token_path = subprocess.run(
[
sys.executable,
"-c",
"import huggingface_hub.constants as c; print(c.HF_TOKEN_PATH)",
],
env = env,
capture_output = True,
text = True,
).stdout.strip()
assert str(real_cache) not in token_path # never the operator's cache
assert token_path.startswith(str(workdir)) # resolved under the sandbox
def test_bypass_env_drops_credential_config_path_vars(monkeypatch, tmp_path):
# NETRC / BOTO_CONFIG / PIP_CONFIG_FILE point clients at real credential
# files before $HOME, so they must not survive into the bypassed child.
monkeypatch.setenv("NETRC", "/home/op/.netrc")
monkeypatch.setenv("PGPASSFILE", "/home/op/.pgpass")
monkeypatch.setenv("BOTO_CONFIG", "/home/op/.boto")
monkeypatch.setenv("PIP_CONFIG_FILE", "/home/op/.pip/pip.conf")
env = _build_bypass_env(str(tmp_path))
assert "NETRC" not in env
assert "PGPASSFILE" not in env
assert "BOTO_CONFIG" not in env
assert "PIP_CONFIG_FILE" not in env
def test_bypass_env_strips_npm_auth_and_mysql_pwd(monkeypatch, tmp_path):
# NPM_CONFIG__AUTH (npm _auth, base64) and MYSQL_PWD dodge the URL-value
# check and the PASSWD marker, but must still be dropped.
monkeypatch.setenv("NPM_CONFIG__AUTH", "aGVsbG86c2VjcmV0")
monkeypatch.setenv("MYSQL_PWD", "db-password")
assert _is_secret_env_name("NPM_CONFIG__AUTH") is True
assert _is_secret_env_name("MYSQL_PWD") is True
env = _build_bypass_env(str(tmp_path))
assert "NPM_CONFIG__AUTH" not in env
assert "MYSQL_PWD" not in env
@_POSIX_ONLY
def test_bash_bypass_does_not_source_bash_env(monkeypatch, tmp_path):
# bash -c sources $BASH_ENV for non-interactive shells; an operator startup
# file could re-export stripped secrets, so a real bypass call must not see it.
startup = tmp_path / "startup.sh"
startup.write_text("export RECOVERED=leaked\n")
monkeypatch.setenv("BASH_ENV", str(startup))
out = _bash_exec("echo R=$RECOVERED", None, 30, "bash-env-test", disable_sandbox = True)
assert "R=leaked" not in out # BASH_ENV dropped -> startup not sourced
assert "R=" in out
def test_bypass_env_repoints_windows_profile_vars(monkeypatch, tmp_path):
# On Windows, SDKs read cached creds under USERPROFILE/APPDATA/LOCALAPPDATA,
# not $HOME. Set ones are repointed at the workdir; HOMEDRIVE/HOMEPATH drop.
monkeypatch.setenv("USERPROFILE", "/host/profile")
monkeypatch.setenv("APPDATA", "/host/profile/AppData/Roaming")
monkeypatch.setenv("LOCALAPPDATA", "/host/profile/AppData/Local")
monkeypatch.setenv("HOMEDRIVE", "C:")
monkeypatch.setenv("HOMEPATH", "\\Users\\op")
env = _build_bypass_env(str(tmp_path))
assert env["USERPROFILE"] == str(tmp_path)
assert env["APPDATA"] == str(tmp_path)
assert env["LOCALAPPDATA"] == str(tmp_path)
assert "HOMEDRIVE" not in env
assert "HOMEPATH" not in env
def test_bypass_env_does_not_add_unset_windows_profile_vars(monkeypatch, tmp_path):
# Only repoint Windows profile vars that were actually set (no pollution on
# Linux/macOS where they are absent).
monkeypatch.delenv("USERPROFILE", raising = False)
monkeypatch.delenv("APPDATA", raising = False)
monkeypatch.delenv("LOCALAPPDATA", raising = False)
env = _build_bypass_env(str(tmp_path))
assert "USERPROFILE" not in env
assert "APPDATA" not in env
assert "LOCALAPPDATA" not in env
# ── parent /proc env-leak hardening (regression) ────────────────────
@_POSIX_ONLY
def test_bypass_exec_hardens_parent_proc_env(monkeypatch, captured_popen):
# Stripping the child env is not enough: a same-UID child can read the
# parent's /proc environ. The exec paths must invoke the parent hardening
# when (and only when) the sandbox is disabled.
calls = {"n": 0}
def fake_harden():
calls["n"] += 1
return True
monkeypatch.setattr(tools, "_harden_parent_against_proc_env_leak", fake_harden)
_python_exec("print(1)", None, 5, "t", disable_sandbox = True)
_bash_exec("echo hi", None, 5, "t", disable_sandbox = True)
assert calls["n"] == 2
calls["n"] = 0
_python_exec("print(1)", None, 5, "t", disable_sandbox = False)
_bash_exec("echo hi", None, 5, "t", disable_sandbox = False)
assert calls["n"] == 0 # never hardened on the sandboxed path
def test_bypass_exec_fails_closed_when_hardening_fails(monkeypatch, captured_popen):
# If the parent cannot be hardened (e.g. prctl denied), the unsandboxed
# child must NOT run - otherwise the parent environ stays readable.
monkeypatch.setattr(tools, "_harden_parent_against_proc_env_leak", lambda: False)
out_py = _python_exec("print(1)", None, 5, "t", disable_sandbox = True)
out_sh = _bash_exec("echo hi", None, 5, "t", disable_sandbox = True)
assert "refusing bypass execution" in out_py
assert "refusing bypass execution" in out_sh
assert "cmd" not in captured_popen # never reached Popen
@_POSIX_ONLY
def test_proc_env_unreadable_after_hardening():
# Mechanism check: after hardening, a same-UID child can no longer read the
# parent process /proc environ. Restores the dumpable flag afterwards so the
# process-global state does not leak into later tests.
import subprocess
if tools._libc is None:
pytest.skip("no libc/prctl available")
pid = os.getpid()
probe = (
"try:\n"
f" open('/proc/{pid}/environ', 'rb').read()\n"
" print('READABLE')\n"
"except PermissionError:\n"
" print('DENIED')\n"
)
prev_dumpable = tools._libc.prctl(3, 0, 0, 0, 0) # PR_GET_DUMPABLE
prev_guard = tools._parent_proc_hardened
try:
# Establish a clean readable baseline: another test may have already
# cleared the dumpable flag on this process.
tools._libc.prctl(4, 1, 0, 0, 0) # PR_SET_DUMPABLE = 1
before = subprocess.run(
[sys.executable, "-c", probe], capture_output = True, text = True
).stdout.strip()
if before != "READABLE":
pytest.skip("/proc already restricted in this environment")
tools._parent_proc_hardened = False
assert tools._harden_parent_against_proc_env_leak() is True
after = subprocess.run(
[sys.executable, "-c", probe], capture_output = True, text = True
).stdout.strip()
assert after == "DENIED"
finally:
if prev_dumpable in (0, 1):
try:
tools._libc.prctl(4, prev_dumpable, 0, 0, 0)
except (OSError, AttributeError):
pass
tools._parent_proc_hardened = prev_guard
# ── Anthropic request model declares the field (regression) ─────────
def test_anthropic_request_model_bypass_default_false():
# Omitting the field on the Anthropic path must default to False rather than
# raising AttributeError (extra='allow' does not set absent attributes).
from models.inference import AnthropicMessagesRequest
assert AnthropicMessagesRequest.model_fields["bypass_permissions"].default is False
req = AnthropicMessagesRequest(model = "x", messages = [], max_tokens = 8)
assert bool(req.bypass_permissions) is False