# SPDX-License-Identifier: AGPL-3.0-only # Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 """Persistent, per-user trust_remote_code approval cache. Remembers a user's explicit approval so the consent gate can skip only the DIALOG on a later load of the SAME unchanged code. The gate ALWAYS re-scans (the cache never skips the scan), so CRITICAL is hard-blocked every time and a hand-edited store cannot auto-approve malicious code. Keyed per subject; honored only when the content fingerprint matches AND the scanner-rules version matches AND (when resolvable) the commit SHA matches. CRITICAL is never stored or honored, and any store/SHA error degrades to "ask again", never auto-approve. """ from __future__ import annotations import contextlib import json import os import threading from dataclasses import dataclass from datetime import datetime, timezone from typing import Optional from loggers import get_logger from utils.paths import storage_roots from utils.security.remote_code_scan import CRITICAL, SCAN_RULES_VERSION logger = get_logger(__name__) _SCHEMA_VERSION = 1 _lock = threading.RLock() # Re-exported so the gate can compare a stored approval's ruleset to the live one. SCANNER_VERSION = SCAN_RULES_VERSION @dataclass class StoredApproval: commit_sha: Optional[str] fingerprint: str max_severity: Optional[str] approved_at: str scanner_version: int = 0 def cache_disabled() -> bool: return os.environ.get("UNSLOTH_TRC_APPROVAL_CACHE_DISABLE", "").lower() in ("1", "true", "yes") def _store_path(): return storage_roots.studio_root() / "security" / "remote_code_approvals.json" def _env_offline() -> bool: return os.environ.get("HF_HUB_OFFLINE", "").lower() in ("1", "true", "yes") or os.environ.get( "TRANSFORMERS_OFFLINE", "" ).lower() in ("1", "true", "yes") def approval_target_key(targets) -> str: """Stable key for the combined load unit (a LoRA pins adapter + base together), using the same casing normalization as the fingerprint so identity never disagrees.""" from utils.security.consent import _fingerprint_target_key keys = sorted(_fingerprint_target_key(t) for t in dict.fromkeys(targets) if t) return "\x1f".join(keys) def _load() -> dict: """Parsed store, or an empty skeleton on any error (fail-safe = re-prompt).""" try: with open(_store_path()) as f: data = json.load(f) # Validate the shape, not just the version: a hand-edited ``subjects`` that is not a # dict (e.g. ``[]``) would otherwise crash lookup/record instead of failing safe. if ( isinstance(data, dict) and data.get("version") == _SCHEMA_VERSION and isinstance(data.get("subjects"), dict) ): return data except FileNotFoundError: pass except Exception as exc: logger.warning("Could not read remote-code approvals (%s); ignoring", exc) return {"version": _SCHEMA_VERSION, "subjects": {}} def _save(data: dict) -> None: """Atomic write (tmp + os.replace), best-effort 0600.""" path = _store_path() storage_roots.ensure_dir(path.parent) tmp = path.parent / f".{path.name}.tmp-{os.getpid()}" try: with open(tmp, "w") as f: json.dump(data, f, indent = 2) try: os.chmod(tmp, 0o600) except OSError: pass os.replace(tmp, path) except Exception as exc: logger.warning("Could not write remote-code approvals (%s)", exc) try: tmp.unlink(missing_ok = True) except OSError: pass @contextlib.contextmanager def _file_lock(): """Best-effort cross-process exclusive lock over the store. Inference/export/training record approvals from separate subprocesses, so the in-process RLock is not enough: two processes could each read the same JSON and clobber the other's entry on ``os.replace``. Holding this around the read-modify-write serializes them. Degrades to a no-op if OS locking is unavailable (the consequence is only an occasional extra prompt).""" path = _store_path() try: storage_roots.ensure_dir(path.parent) fd = os.open(str(path.parent / f"{path.name}.lock"), os.O_CREAT | os.O_RDWR, 0o600) except Exception: yield return try: try: if os.name == "nt": import msvcrt msvcrt.locking(fd, msvcrt.LK_LOCK, 1) else: import fcntl fcntl.flock(fd, fcntl.LOCK_EX) except Exception: pass # locking unavailable; the thread lock still applies yield finally: try: if os.name == "nt": import msvcrt with contextlib.suppress(Exception): msvcrt.locking(fd, msvcrt.LK_UNLCK, 1) else: import fcntl fcntl.flock(fd, fcntl.LOCK_UN) finally: os.close(fd) def lookup(subject: str, target_key: str) -> Optional[StoredApproval]: """The stored approval for (subject, target_key), or None. A CRITICAL entry (e.g. a hand-edited store) is refused so it can never seed an approval.""" if not subject or cache_disabled(): return None with _lock: subj = _load().get("subjects", {}).get(subject, {}) entry = subj.get(target_key) if isinstance(subj, dict) else None if not isinstance(entry, dict) or not entry.get("fingerprint"): return None if entry.get("max_severity") == CRITICAL: return None return StoredApproval( commit_sha = entry.get("commit_sha"), fingerprint = entry["fingerprint"], max_severity = entry.get("max_severity"), approved_at = entry.get("approved_at", ""), scanner_version = entry.get("scanner_version", 0), ) def record( subject: str, target_key: str, *, commit_sha: Optional[str], fingerprint: str, max_severity: Optional[str], scanner_version: int = SCANNER_VERSION, ) -> None: """Persist a user's explicit approval. CRITICAL is never stored.""" if not subject or not fingerprint or cache_disabled() or max_severity == CRITICAL: return with _lock, _file_lock(): data = _load() subjects = data.setdefault("subjects", {}) subj = subjects.get(subject) if not isinstance(subj, dict): # tolerate a hand-edited non-dict entry subj = subjects[subject] = {} subj[target_key] = { "commit_sha": commit_sha, "fingerprint": fingerprint, "max_severity": max_severity, "scanner_version": scanner_version, "approved_at": datetime.now(timezone.utc).isoformat(), } _save(data) def forget(subject: str, target_key: str) -> None: """Drop an approval (e.g. the user declined / discarded the download).""" if not subject: return with _lock, _file_lock(): data = _load() subj = data.get("subjects", {}).get(subject) if isinstance(subj, dict) and subj.pop(target_key, None) is not None: _save(data) def clear() -> None: """Test helper: drop the on-disk store.""" with _lock: try: _store_path().unlink(missing_ok = True) except OSError: pass def resolve_commit_sha(target: str, hf_token: Optional[str] = None) -> Optional[str]: """Current HF commit SHA for *target*, or None (local path / offline / error). Resolved fresh every call: the default branch is mutable, so a cached SHA could mask a moved repo and reuse stale consent. None falls back to the authoritative fingerprint (never fail-open). """ from utils.paths import is_local_path try: if is_local_path(target) or _env_offline(): return None from huggingface_hub import HfApi return HfApi().model_info(target, token = hf_token).sha except Exception as exc: logger.debug("Could not resolve commit sha for '%s': %s", target, exc) return None def resolve_combined_sha(targets, hf_token: Optional[str] = None) -> Optional[str]: """Combined SHA over the primary targets; None if ANY is unresolvable. A cheap secondary gate only -- the fingerprint (which also covers external auto_map repos) stays authoritative, so a None here just falls back to the fingerprint, never weakens it.""" from utils.security.consent import _fingerprint_target_key parts = [] for target in dict.fromkeys(targets): if not target: continue sha = resolve_commit_sha(target, hf_token) if sha is None: return None parts.append(f"{_fingerprint_target_key(target)}={sha}") return "\x1f".join(sorted(parts)) if parts else None