Studio: stop handing CI/user secrets to downloaded llama.cpp binaries (#6696)
Some checks are pending
Core / Core (HF=default + TRL=default) (push) Waiting to run
Core / Core (HF=4.57.6 + TRL<1) (push) Waiting to run
Core / Core (HF=latest + TRL=latest) (push) Waiting to run
Core / llama.cpp build + smoke (push) Waiting to run
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Waiting to run
MLX CI on Mac M1 / dispatch (push) Waiting to run
Security audit / pip scan-packages :: extras (push) Waiting to run
Security audit / pip scan-packages :: studio (push) Waiting to run
Security audit / pip scan-packages :: hf-stack (push) Waiting to run
Security audit / advisory audit (pip + npm + cargo) (push) Waiting to run
Security audit / npm scan-packages (Studio frontend tarballs) (push) Waiting to run
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Waiting to run
Security audit / pytest tests/security (push) Waiting to run
Security audit / npm provenance + new install-script diff (push) Waiting to run
Studio API CI / Studio API & Auth Tests (push) Waiting to run
Backend CI / (Python 3.10) (push) Waiting to run
Backend CI / (Python 3.11) (push) Waiting to run
Backend CI / (Python 3.12) (push) Waiting to run
Backend CI / (Python 3.13) (push) Waiting to run
Backend CI / Repo tests (CPU) (push) Waiting to run
Frontend CI / Frontend build + bundle sanity (push) Waiting to run
Studio GGUF CI / OpenAI, Anthropic API tests (push) Waiting to run
Studio GGUF CI / Tool calling Tests (push) Waiting to run
Studio GGUF CI / JSON, images (push) Waiting to run
Mac Studio API CI / Studio API & Auth Tests (push) Waiting to run
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Waiting to run
Mac Studio GGUF CI / Tool calling Tests (push) Waiting to run
Mac Studio GGUF CI / JSON, images (push) Waiting to run
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Waiting to run
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Waiting to run
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Waiting to run
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Waiting to run
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Waiting to run
Mac Studio UI CI / Chat UI Tests (push) Waiting to run
Mac Studio Update CI / Studio Updating Tests (push) Waiting to run
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Waiting to run
Studio UI CI / Chat UI Tests (push) Waiting to run
Studio Update CI / Studio Updating Tests (push) Waiting to run
Windows Studio API CI / Studio API & Auth Tests (push) Waiting to run
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Waiting to run
Windows Studio GGUF CI / Tool calling Tests (push) Waiting to run
Windows Studio GGUF CI / JSON, images (push) Waiting to run
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Waiting to run
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Waiting to run
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Waiting to run
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Waiting to run
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Waiting to run
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Waiting to run
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Waiting to run
Windows Studio UI CI / Chat UI Tests (push) Waiting to run
Windows Studio Update CI / Studio Updating Tests (push) Waiting to run
Wheel CI / Wheel build + content sanity + import smoke (push) Waiting to run

* Studio: stop handing CI/user secrets to downloaded llama.cpp binaries

The macOS prebuilt path installs llama.cpp from the unslothai/llama.cpp
fork's latest (unpinned, mutable) release and then executes the
downloaded llama-server / llama-quantize binaries during install-time
validation. binary_env() built that child environment from a full
os.environ.copy(), so a compromised or tampered prebuilt would inherit
every secret in the process: HF_TOKEN and the workflow GitHub tokens in
CI, and HF / cloud credentials for end users running install.sh /
setup.sh.

We publish prebuilts daily, so pinning a release tag is not workable.
Instead, neutralise the impact: these binaries have no reason to read any
token, so strip secret-bearing variables (exact names plus
TOKEN/SECRET/PASSWORD/CREDENTIAL/PRIVATE_KEY/API_KEY markers) before
handing the env to a downloaded binary. The installer's own GitHub and
Hugging Face API calls read os.environ directly, so authentication and
release-API rate limiting are unaffected; PATH, LD_LIBRARY_PATH,
DYLD_LIBRARY_PATH and CUDA/ROCm vars are preserved. One change covers the
install-time validation path for all six macOS workflows and end users.

Follow-up (separate, sequenced): publish build-provenance attestations
from the fork's prebuilt workflows and verify them in CI, so a forged
release is rejected rather than merely starved of secrets.

* Strip KUBECONFIG, SSH_AUTH_SOCK, and PASSPHRASE-marked vars from binary env

Extend the deny-list per PR review: KUBECONFIG and SSH_AUTH_SOCK are
credential pointers/capabilities a downloaded binary never needs, and a
PASSPHRASE marker catches SSH_PASSPHRASE / GPG_PASSPHRASE. Tests updated.

* Studio: also scrub proxy/index env vars and URL-embedded credentials before running prebuilt binaries

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Scope mlx-ci secrets to the install + download commands for PR #6696

Drop the ambient step-level env block and pass GH/GITHUB/HF tokens only
on the installer and GGUF-download commands, so the directly invoked
llama-quantize / llama-server smoke runs see no secrets. The installer
still reads tokens from os.environ for the releases API and probe fetch.

* Trim verbose comments around the secret-env scrubber for PR #6696

Comment-only: condense the block comments added across this PR. Logic
unchanged (comment_tools.py check confirms code-only signature equal).

* Redirect HOME / cache pointers to an empty dir for prebuilt binaries (PR #6696)

Address Codex P2: stripping token env vars still let a tampered binary
read on-disk token stores (~/.cache/huggingface/token, ~/.aws/credentials,
~/.config/gh) through $HOME and the cache/config pointers. Point HOME plus
the HF / XDG / Windows home pointers at a single empty throwaway dir for
the downloaded-binary env. Defense in depth: a binary resolving the real
home via getpwuid is out of scope and needs OS sandboxing.

* Close residual credential-probe gaps for PR #6696

Address the latest Codex review:
- Strip token-only URL userinfo too (scheme://ghp_token@host), not just
  the user:pass form.
- Redirect HOMEDRIVE/HOMEPATH alongside USERPROFILE so a Windows binary
  cannot reconstruct the real profile from %HOMEDRIVE%%HOMEPATH%.
- Drop explicit credential-file pointers (NETRC, PIP_CONFIG_FILE,
  DOCKER_CONFIG, GIT_CONFIG_GLOBAL) that live outside HOME.
- Probe ldd with a secret-free env: linux_runtime_dirs ran ldd on the
  untrusted prebuilt with the inherited os.environ, and ldd may execute
  the binary, so it could observe HF_TOKEN/GITHUB_TOKEN during the probe.

Factored the shared scrub into secret_free_environ().

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Separate token-bearing install from binary smoke; drop CI command files (PR #6696)

Address the two P1s in the latest review:
- mlx-ci: GitHub bakes secrets into the run-script text, so inline token
  assignments in a step that later runs the prebuilt let a tampered binary
  read them from the script. Split into a token-bearing install + download
  step that never launches a binary, and a secret-free smoke step that runs
  llama-quantize / llama-server.
- secret_free_environ now drops the GitHub Actions command files
  (GITHUB_ENV, GITHUB_PATH, GITHUB_OUTPUT, GITHUB_STEP_SUMMARY, BASH_ENV) and
  the smoke step unsets them, so a tampered prebuilt cannot inject PATH/env
  into the later token-bearing MLX steps.

* Run the prebuilt smoke last, after all token-bearing steps (PR #6696)

Address the P1 workspace-poisoning vector: even with no secrets in its env,
a tampered prebuilt could edit the checkout or installed modules, and the
later HF_TOKEN MLX steps would then execute that poisoned code on push
builds. Move the prebuilt install + smoke to the end of the job so the
untrusted binary runs after every token-bearing step, leaving nothing for it
to corrupt. The MLX GGUF reload uses a source-built llama-cli, not this
prebuilt, so nothing depends on the earlier position.

* Trim comments around the secret-env scrubber and prebuilt CI steps (PR #6696)

Comment-only: condense the security-rationale block comments and merge the
duplicated prebuilt-step description in mlx-ci. Logic unchanged
(comment_tools.py check confirms the code-only signature is equal; install
suite still passes).

* Authenticate the GGUF export release-API lookup with the read-only GITHUB_TOKEN (PR #6696)

* Rename env scrubber off the secret-named identifier CodeQL flags as a clear-text sink (PR #6696)

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Daniel Han 2026-06-27 05:21:05 -07:00 committed by GitHub
parent 98a01e70cd
commit 4c72e09480
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 482 additions and 95 deletions

View file

@ -231,99 +231,6 @@ jobs:
tests/studio/test_is_mlx_dispatch_gate.py \
tests/studio/test_mlx_training_worker_behaviors.py
# Studio prebuilt llama.cpp install + GGUF inference. Mirrors the
# path Studio's setup.sh takes on macOS since #5963: plan against
# the unslothai/llama.cpp fork's latest release, which ships the
# bin-macos-arm64 bundle plus the llama-prebuilt-manifest.json the
# default policy reads. After install, downloads a small published
# GGUF (unsloth/gemma-3-270m-it-GGUF, Q4_K_M) and validates
# llama-server /completion end to end. An install failure or a
# non-zero binary exit is an Unsloth/Studio bug.
- name: Studio prebuilt llama.cpp install + GGUF inference (Mac M1)
env:
# Withheld on PR: this step runs checked-out PR code; public GGUF still downloads.
HF_TOKEN: ${{ github.event_name != 'pull_request' && secrets.HF_TOKEN || '' }}
# install_llama_prebuilt.py hits the GitHub releases API to
# resolve the asset URL. Anonymous calls share the runner-IP
# rate-limit bucket and 403 quickly -- pass the workflow's
# automatic GITHUB_TOKEN to bump us to the 5000/hr authenticated
# bucket.
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
INSTALL_DIR="$HOME/.unsloth-studio-prebuilt-test/llama.cpp"
rm -rf "$INSTALL_DIR"
# Mirror studio/setup.sh on macOS (the install.sh user path):
# it plans against the unslothai/llama.cpp fork's latest
# release with no policy or tag flags.
python studio/install_llama_prebuilt.py \
--install-dir "$INSTALL_DIR" \
--published-repo unslothai/llama.cpp
# Studio bundles only llama-server + llama-quantize from the
# prebuilt (not llama-cli) -- inference goes through
# llama-server's HTTP /completion endpoint. Validate both:
# llama-quantize --help proves the dynamic libs link, then
# spin up llama-server and POST a /completion request on a
# tiny published GGUF.
LLAMA_SERVER="$INSTALL_DIR/build/bin/llama-server"
LLAMA_QUANT="$INSTALL_DIR/build/bin/llama-quantize"
[ -x "$LLAMA_SERVER" ] || { echo "::error::llama-server missing at $LLAMA_SERVER"; find "$INSTALL_DIR/build" -type f | head -40; exit 1; }
[ -x "$LLAMA_QUANT" ] || { echo "::error::llama-quantize missing at $LLAMA_QUANT"; exit 1; }
echo "llama-server : $LLAMA_SERVER"
echo "llama-quantize: $LLAMA_QUANT"
"$LLAMA_QUANT" --help >/dev/null && echo " llama-quantize loads OK"
mkdir -p /tmp/ggufs
bash .github/scripts/hf-download-with-retry.sh \
'unsloth/gemma-3-270m-it-GGUF' \
'gemma-3-270m-it-Q4_K_M.gguf' \
/tmp/ggufs
PORT=18080
echo "=== starting llama-server on 127.0.0.1:$PORT ==="
"$LLAMA_SERVER" \
-m /tmp/ggufs/gemma-3-270m-it-Q4_K_M.gguf \
--host 127.0.0.1 \
--port "$PORT" \
-c 256 \
-n 16 \
--no-warmup \
> /tmp/llama-server.log 2>&1 &
SERVER_PID=$!
trap 'kill "$SERVER_PID" 2>/dev/null || true' EXIT
# Wait for /health to come up
for i in $(seq 1 30); do
if curl -sf "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then
echo " server up after ${i}s"
break
fi
sleep 1
done
if ! curl -sf "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then
echo "::error::llama-server never became healthy"
tail -40 /tmp/llama-server.log
exit 1
fi
PROMPT="Hello, my name is"
echo "=== POST /completion ==="
RESP=$(curl -sf -X POST "http://127.0.0.1:$PORT/completion" \
-H 'Content-Type: application/json' \
-d "{\"prompt\":\"$PROMPT\",\"n_predict\":16,\"temperature\":0,\"seed\":3407}")
echo "raw response (head): $(echo "$RESP" | head -c 600)"
CONTENT=$(echo "$RESP" | python -c "import json,sys; print(json.loads(sys.stdin.read()).get('content',''))")
echo "completion content: $CONTENT"
if [ -z "$CONTENT" ]; then
echo "::error::llama-server /completion returned empty content"
tail -40 /tmp/llama-server.log
exit 1
fi
echo "OK: Studio prebuilt llama.cpp on Mac M1 + GGUF /completion works"
# Real MLX training + inference smoke test. Trains
# unsloth/gemma-3-270m-it for 7 deterministic LoRA steps
# (batch_size=2, gradient_accumulation_steps=3) on a single
@ -338,6 +245,9 @@ jobs:
UNSLOTH_COMPILE_DISABLE: '1'
run: |
mkdir -p mlx_workdir
# Authenticate llama.cpp's release-API lookup (anonymous 403s on rate-limit);
# read-only GITHUB_TOKEN scoped here only, never to steps that run binaries.
GH_TOKEN="${{ secrets.GITHUB_TOKEN }}" GITHUB_TOKEN="${{ secrets.GITHUB_TOKEN }}" \
python tests/studio/run_real_mlx_smoke.py train \
--workdir "$PWD/mlx_workdir"
@ -406,3 +316,88 @@ jobs:
cat "$f" 2>/dev/null || echo "(missing)"
echo
done
# Validates the macOS prebuilt path Studio's setup.sh uses (#5963): install the
# unslothai/llama.cpp fork's latest release, download a small public GGUF, and
# check llama-server /completion end to end. Split and placed last so the
# untrusted binary runs only in the final smoke step, after every HF_TOKEN step,
# leaving no token-bearing step or shared workspace for a tampered prebuilt to
# corrupt. GH_TOKEN: releases API; HF_TOKEN (withheld on PR): probe + GGUF fetch.
- name: Studio prebuilt llama.cpp install + GGUF download (Mac M1)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HF_TOKEN: ${{ github.event_name != 'pull_request' && secrets.HF_TOKEN || '' }}
run: |
set -euo pipefail
INSTALL_DIR="$HOME/.unsloth-studio-prebuilt-test/llama.cpp"
rm -rf "$INSTALL_DIR"
# Download only -- no llama-quantize / llama-server launch in this step.
python studio/install_llama_prebuilt.py \
--install-dir "$INSTALL_DIR" \
--published-repo unslothai/llama.cpp
mkdir -p /tmp/ggufs
bash .github/scripts/hf-download-with-retry.sh \
'unsloth/gemma-3-270m-it-GGUF' \
'gemma-3-270m-it-Q4_K_M.gguf' \
/tmp/ggufs
# Final step: runs the downloaded binaries with no secrets present, and clears
# the GitHub Actions command files so a tampered prebuilt cannot influence the job.
- name: Studio prebuilt llama.cpp GGUF inference smoke (Mac M1)
run: |
set -euo pipefail
unset GITHUB_ENV GITHUB_PATH GITHUB_OUTPUT GITHUB_STEP_SUMMARY
INSTALL_DIR="$HOME/.unsloth-studio-prebuilt-test/llama.cpp"
# Studio bundles only llama-server + llama-quantize (not llama-cli);
# inference goes through llama-server's HTTP /completion endpoint.
LLAMA_SERVER="$INSTALL_DIR/build/bin/llama-server"
LLAMA_QUANT="$INSTALL_DIR/build/bin/llama-quantize"
[ -x "$LLAMA_SERVER" ] || { echo "::error::llama-server missing at $LLAMA_SERVER"; find "$INSTALL_DIR/build" -type f | head -40; exit 1; }
[ -x "$LLAMA_QUANT" ] || { echo "::error::llama-quantize missing at $LLAMA_QUANT"; exit 1; }
echo "llama-server : $LLAMA_SERVER"
echo "llama-quantize: $LLAMA_QUANT"
"$LLAMA_QUANT" --help >/dev/null && echo " llama-quantize loads OK"
PORT=18080
echo "=== starting llama-server on 127.0.0.1:$PORT ==="
"$LLAMA_SERVER" \
-m /tmp/ggufs/gemma-3-270m-it-Q4_K_M.gguf \
--host 127.0.0.1 \
--port "$PORT" \
-c 256 \
-n 16 \
--no-warmup \
> /tmp/llama-server.log 2>&1 &
SERVER_PID=$!
trap 'kill "$SERVER_PID" 2>/dev/null || true' EXIT
# Wait for /health to come up
for i in $(seq 1 30); do
if curl -sf "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then
echo " server up after ${i}s"
break
fi
sleep 1
done
if ! curl -sf "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then
echo "::error::llama-server never became healthy"
tail -40 /tmp/llama-server.log
exit 1
fi
PROMPT="Hello, my name is"
echo "=== POST /completion ==="
RESP=$(curl -sf -X POST "http://127.0.0.1:$PORT/completion" \
-H 'Content-Type: application/json' \
-d "{\"prompt\":\"$PROMPT\",\"n_predict\":16,\"temperature\":0,\"seed\":3407}")
echo "raw response (head): $(echo "$RESP" | head -c 600)"
CONTENT=$(echo "$RESP" | python -c "import json,sys; print(json.loads(sys.stdin.read()).get('content',''))")
echo "completion content: $CONTENT"
if [ -z "$CONTENT" ]; then
echo "::error::llama-server /completion returned empty content"
tail -40 /tmp/llama-server.log
exit 1
fi
echo "OK: Studio prebuilt llama.cpp on Mac M1 + GGUF /completion works"

View file

@ -7,6 +7,7 @@
from __future__ import annotations
import argparse
import atexit
import errno
import fnmatch
import hashlib
@ -5203,7 +5204,8 @@ def ldconfig_runtime_dirs(required_libraries: Iterable[str]) -> list[str]:
def linux_runtime_dirs(binary_path: Path) -> list[str]:
missing = linux_missing_libraries(binary_path)
# ldd may execute the binary, so probe it with a secret-free env.
missing = linux_missing_libraries(binary_path, env = scrubbed_environ())
if not missing:
return []
return linux_runtime_dirs_for_required_libraries(missing)
@ -5499,6 +5501,140 @@ def _wsl_system_rocm_lib_dirs() -> list[str]:
return out
# Secrets a downloaded llama.cpp binary never needs; keep them out of binary_env().
# The installer's own API calls read os.environ directly, so auth is unaffected.
_SECRET_ENV_EXACT_NAMES = frozenset(
{
"HF_TOKEN",
"HUGGING_FACE_HUB_TOKEN",
"GH_TOKEN",
"GITHUB_TOKEN",
"WANDB_API_KEY",
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY",
"AWS_SESSION_TOKEN",
"GOOGLE_APPLICATION_CREDENTIALS",
"AZURE_CLIENT_SECRET",
"ACTIONS_ID_TOKEN_REQUEST_TOKEN",
"ACTIONS_ID_TOKEN_REQUEST_URL",
"ACTIONS_RUNTIME_TOKEN",
# Credential pointers (cluster / remote-host access).
"KUBECONFIG",
"SSH_AUTH_SOCK",
}
)
# Case-insensitive substring markers for names we do not enumerate (no bare "KEY",
# which would hit benign runtime vars).
_SECRET_ENV_MARKERS = (
"TOKEN",
"SECRET",
"PASSWORD",
"PASSWD",
"PASSPHRASE",
"CREDENTIAL",
"PRIVATE_KEY",
"API_KEY",
)
# Proxy / index URLs embed creds in their value; the offline binaries never need them.
_SECRET_ENV_URL_NAMES = frozenset(
{
"HTTP_PROXY",
"HTTPS_PROXY",
"ALL_PROXY",
"FTP_PROXY",
"RSYNC_PROXY",
"PIP_INDEX_URL",
"PIP_EXTRA_INDEX_URL",
"UV_INDEX_URL",
"UV_DEFAULT_INDEX",
"UV_EXTRA_INDEX_URL",
}
)
# Also drop values with URL userinfo creds (scheme://user:secret@host or token@host).
_URL_USERINFO_CREDENTIAL_RE = re.compile(r"://[^/@\s]+@")
def is_secret_env_name(name: str) -> bool:
upper = name.upper()
return (
upper in _SECRET_ENV_EXACT_NAMES
or upper in _SECRET_ENV_URL_NAMES
or any(marker in upper for marker in _SECRET_ENV_MARKERS)
)
def scrub_env(env: dict[str, str]) -> dict[str, str]:
"""Drop secret-bearing variables before handing an env to a downloaded binary."""
return {
key: value
for key, value in env.items()
if not is_secret_env_name(key) and not _URL_USERINFO_CREDENTIAL_RE.search(value or "")
}
# Home / cache pointers to on-disk token stores (~/.cache/huggingface/token,
# ~/.aws/credentials, ...). Stripping env tokens is not enough; point these at an
# empty home so the binary cannot read those files via $HOME.
_RUNTIME_HOME_POINTER_VARS = (
"HOME",
"USERPROFILE",
"APPDATA",
"LOCALAPPDATA",
"XDG_CACHE_HOME",
"XDG_CONFIG_HOME",
"XDG_DATA_HOME",
"HF_HOME",
"HUGGINGFACE_HUB_CACHE",
"HF_HUB_CACHE",
)
# Credential / config file pointers outside HOME; drop so lookups fall back to the
# empty home.
_CREDENTIAL_FILE_POINTER_VARS = (
"NETRC",
"PIP_CONFIG_FILE",
"DOCKER_CONFIG",
"GIT_CONFIG_GLOBAL",
)
# GitHub Actions command files: appending to these injects PATH/env into later steps.
_CI_COMMAND_FILE_VARS = (
"GITHUB_ENV",
"GITHUB_PATH",
"GITHUB_OUTPUT",
"GITHUB_STEP_SUMMARY",
"BASH_ENV",
)
_isolated_runtime_home_dir: str | None = None
def isolated_runtime_home() -> str:
# Empty dir, created lazily and removed at exit. (A binary resolving the real
# home via getpwuid is out of scope; that needs OS sandboxing.)
global _isolated_runtime_home_dir
if _isolated_runtime_home_dir is None:
path = tempfile.mkdtemp(prefix = "unsloth-prebuilt-home-")
atexit.register(shutil.rmtree, path, ignore_errors = True)
_isolated_runtime_home_dir = path
return _isolated_runtime_home_dir
def scrubbed_environ() -> dict[str, str]:
# os.environ minus secrets, with home / credential pointers neutralised. Used for
# the binary env and any probe (e.g. ldd) that runs the untrusted binary.
env = scrub_env(os.environ.copy())
runtime_home = isolated_runtime_home()
for pointer in _RUNTIME_HOME_POINTER_VARS:
env[pointer] = runtime_home
# Windows rebuilds the profile from %HOMEDRIVE%%HOMEPATH% (no-op pair on POSIX).
drive, tail = os.path.splitdrive(runtime_home)
env["HOMEDRIVE"], env["HOMEPATH"] = drive, tail or runtime_home
for pointer in (*_CREDENTIAL_FILE_POINTER_VARS, *_CI_COMMAND_FILE_VARS):
env.pop(pointer, None)
return env
def binary_env(
binary_path: Path,
install_dir: Path,
@ -5506,7 +5642,7 @@ def binary_env(
*,
runtime_line: str | None = None,
) -> dict[str, str]:
env = os.environ.copy()
env = scrubbed_environ()
if host.is_windows:
path_dirs = [
str(binary_path.parent),

View file

@ -22,6 +22,9 @@ SPEC.loader.exec_module(INSTALL_LLAMA_PREBUILT)
PrebuiltFallback = INSTALL_LLAMA_PREBUILT.PrebuiltFallback
extract_archive = INSTALL_LLAMA_PREBUILT.extract_archive
binary_env = INSTALL_LLAMA_PREBUILT.binary_env
is_secret_env_name = INSTALL_LLAMA_PREBUILT.is_secret_env_name
scrub_env = INSTALL_LLAMA_PREBUILT.scrub_env
isolated_runtime_home = INSTALL_LLAMA_PREBUILT.isolated_runtime_home
HostInfo = INSTALL_LLAMA_PREBUILT.HostInfo
AssetChoice = INSTALL_LLAMA_PREBUILT.AssetChoice
ApprovedArtifactHash = INSTALL_LLAMA_PREBUILT.ApprovedArtifactHash
@ -779,6 +782,259 @@ def test_binary_env_linux_includes_binary_parent_in_ld_library_path(
assert str(install_dir) in ld_dirs
def test_scrub_env_drops_secrets_and_keeps_runtime_vars():
raw = {
# secrets
"HF_TOKEN": "hf_x",
"HUGGING_FACE_HUB_TOKEN": "hf_y",
"GH_TOKEN": "gh_x",
"GITHUB_TOKEN": "gh_y",
"WANDB_API_KEY": "wandb_x",
"AWS_SECRET_ACCESS_KEY": "aws_x",
"ACTIONS_ID_TOKEN_REQUEST_TOKEN": "oidc_x",
"ACTIONS_ID_TOKEN_REQUEST_URL": "https://oidc",
"SOME_VENDOR_API_KEY": "vendor_x",
"DB_PASSWORD": "pw",
"MY_PRIVATE_KEY": "pk",
"KUBECONFIG": "/home/runner/.kube/config",
"SSH_AUTH_SOCK": "/tmp/ssh-agent.sock",
"SSH_PASSPHRASE": "ssh_pass",
# runtime vars to keep
"PATH": "/usr/bin",
"LD_LIBRARY_PATH": "/opt/lib",
"DYLD_LIBRARY_PATH": "/opt/dyld",
"HOME": "/home/runner",
"TMPDIR": "/tmp",
"CUDA_VISIBLE_DEVICES": "0",
"HSA_OVERRIDE_GFX_VERSION": "11.0.0",
}
cleaned = scrub_env(raw)
for secret in (
"HF_TOKEN",
"HUGGING_FACE_HUB_TOKEN",
"GH_TOKEN",
"GITHUB_TOKEN",
"WANDB_API_KEY",
"AWS_SECRET_ACCESS_KEY",
"ACTIONS_ID_TOKEN_REQUEST_TOKEN",
"ACTIONS_ID_TOKEN_REQUEST_URL",
"SOME_VENDOR_API_KEY",
"DB_PASSWORD",
"MY_PRIVATE_KEY",
"KUBECONFIG",
"SSH_AUTH_SOCK",
"SSH_PASSPHRASE",
):
assert secret not in cleaned, f"{secret} must be stripped from binary env"
for keep in (
"PATH",
"LD_LIBRARY_PATH",
"DYLD_LIBRARY_PATH",
"HOME",
"TMPDIR",
"CUDA_VISIBLE_DEVICES",
"HSA_OVERRIDE_GFX_VERSION",
):
assert cleaned[keep] == raw[keep], f"{keep} must be preserved for the binary"
# no bare "KEY" marker: benign KEY-containing names survive
assert is_secret_env_name("API_KEY") is True
assert is_secret_env_name("SSH_KEYFILE_PATH") is False
assert is_secret_env_name("PATH") is False
def test_scrub_env_drops_proxy_index_and_embedded_url_credentials():
raw = {
# proxy / package-index URLs whose values commonly embed credentials
"HTTPS_PROXY": "https://user:secret@proxy:8080",
"https_proxy": "https://user:secret@proxy:8080", # lower-case variant
"ALL_PROXY": "socks5://user:secret@proxy:1080",
"PIP_INDEX_URL": "https://u:p@pypi.internal/simple",
"UV_INDEX_URL": "https://u:p@index.internal/simple",
# credentials embedded in an otherwise benign-named variable's value
"MY_DB_DSN": "postgres://admin:secret@db:5432/app",
# benign vars the binary needs, including a URL with no userinfo
"PATH": "/usr/bin",
"CUDA_VISIBLE_DEVICES": "0",
"NO_PROXY": "localhost,127.0.0.1",
"SOME_ENDPOINT": "https://example.com:8080/v1",
}
cleaned = scrub_env(raw)
for secret in (
"HTTPS_PROXY",
"https_proxy",
"ALL_PROXY",
"PIP_INDEX_URL",
"UV_INDEX_URL",
"MY_DB_DSN",
):
assert secret not in cleaned, f"{secret} must be stripped from binary env"
for keep in ("PATH", "CUDA_VISIBLE_DEVICES", "NO_PROXY", "SOME_ENDPOINT"):
assert cleaned[keep] == raw[keep], f"{keep} must be preserved for the binary"
assert is_secret_env_name("HTTPS_PROXY") is True
assert is_secret_env_name("https_proxy") is True
assert is_secret_env_name("NO_PROXY") is False
def test_binary_env_strips_secrets_from_downloaded_binary_environment(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
install_dir = tmp_path / "llama.cpp"
bin_dir = install_dir / "build" / "bin"
bin_dir.mkdir(parents = True)
binary_path = bin_dir / "llama-server"
binary_path.write_bytes(b"fake")
host = HostInfo(
system = "Linux",
machine = "x86_64",
is_windows = False,
is_linux = True,
is_macos = False,
is_x86_64 = True,
is_arm64 = False,
nvidia_smi = None,
driver_cuda_version = None,
compute_caps = [],
visible_cuda_devices = None,
has_physical_nvidia = False,
has_usable_nvidia = False,
)
monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "linux_runtime_dirs", lambda _bp: [])
monkeypatch.setenv("HF_TOKEN", "hf_secret_from_ci")
monkeypatch.setenv("GITHUB_TOKEN", "gh_secret_from_ci")
monkeypatch.setenv("GH_TOKEN", "gh_secret_from_ci")
monkeypatch.setenv("WANDB_API_KEY", "wandb_secret_from_ci")
monkeypatch.setenv("CUDA_VISIBLE_DEVICES", "1")
env = binary_env(binary_path, install_dir, host)
assert "HF_TOKEN" not in env
assert "GITHUB_TOKEN" not in env
assert "GH_TOKEN" not in env
assert "WANDB_API_KEY" not in env
# library/runtime resolution unaffected
assert str(bin_dir) in env["LD_LIBRARY_PATH"].split(os.pathsep)
assert env["CUDA_VISIBLE_DEVICES"] == "1"
def test_binary_env_redirects_home_away_from_real_credential_stores(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
install_dir = tmp_path / "llama.cpp"
bin_dir = install_dir / "build" / "bin"
bin_dir.mkdir(parents = True)
binary_path = bin_dir / "llama-server"
binary_path.write_bytes(b"fake")
host = HostInfo(
system = "Linux",
machine = "x86_64",
is_windows = False,
is_linux = True,
is_macos = False,
is_x86_64 = True,
is_arm64 = False,
nvidia_smi = None,
driver_cuda_version = None,
compute_caps = [],
visible_cuda_devices = None,
has_physical_nvidia = False,
has_usable_nvidia = False,
)
monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "linux_runtime_dirs", lambda _bp: [])
real_home = str(tmp_path / "real_home")
monkeypatch.setenv("HOME", real_home)
monkeypatch.setenv("HF_HOME", real_home + "/.cache/huggingface")
env = binary_env(binary_path, install_dir, host)
# HOME and the cache pointers are redirected to a single empty, existing dir.
assert env["HOME"] != real_home
assert env["HF_HOME"] == env["HOME"]
assert env["HOME"] == isolated_runtime_home()
assert os.path.isdir(env["HOME"])
assert os.listdir(env["HOME"]) == []
# Windows reconstructs the profile from HOMEDRIVE + HOMEPATH.
assert env["HOMEDRIVE"] + env["HOMEPATH"] == env["HOME"]
def test_scrub_env_drops_token_only_url_userinfo():
raw = {
"GENERIC_REPO": "https://ghp_tokenonly@github.com/org/repo",
"GENERIC_OK": "https://example.com:8080/v1",
}
cleaned = scrub_env(raw)
assert "GENERIC_REPO" not in cleaned
assert cleaned["GENERIC_OK"] == raw["GENERIC_OK"]
def test_binary_env_drops_explicit_credential_file_pointers(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
host = HostInfo(
system = "Linux",
machine = "x86_64",
is_windows = False,
is_linux = True,
is_macos = False,
is_x86_64 = True,
is_arm64 = False,
nvidia_smi = None,
driver_cuda_version = None,
compute_caps = [],
visible_cuda_devices = None,
has_physical_nvidia = False,
has_usable_nvidia = False,
)
monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "linux_runtime_dirs", lambda _bp: [])
dropped = (
"NETRC",
"PIP_CONFIG_FILE",
"DOCKER_CONFIG",
"GIT_CONFIG_GLOBAL",
"GITHUB_ENV",
"GITHUB_PATH",
"GITHUB_OUTPUT",
"GITHUB_STEP_SUMMARY",
"BASH_ENV",
)
for var in dropped:
monkeypatch.setenv(var, "/home/realuser/secret")
env = binary_env(tmp_path / "llama-server", tmp_path, host)
for var in dropped:
assert var not in env
def test_linux_runtime_dirs_probes_with_secret_free_env(monkeypatch: pytest.MonkeyPatch):
captured: dict[str, object] = {}
def fake_missing(binary_path, *, env = None):
captured["env"] = env
return []
monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "linux_missing_libraries", fake_missing)
monkeypatch.setenv("HF_TOKEN", "hf_secret")
monkeypatch.setenv("GITHUB_TOKEN", "gh_secret")
INSTALL_LLAMA_PREBUILT.linux_runtime_dirs(Path("/fake/llama-server"))
probe_env = captured["env"]
assert probe_env is not None
assert "HF_TOKEN" not in probe_env
assert "GITHUB_TOKEN" not in probe_env
def test_install_prebuilt_falls_back_to_older_release_plan(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):