Skip to content

Record this host's vLLM receipt with its qualified local models - #131

Merged
seathatflowsinourveins merged 1 commit into
mainfrom
claude/host-receipt-local-models-20260923
Sep 23, 2026
Merged

seathatflowsinourveins merged 1 commit into
mainfrom
claude/host-receipt-local-models-20260923

Conversation

@seathatflowsinourveins

Copy link
Copy Markdown
Collaborator

First real use of the qualified_models field added in #130.

Checks: validate.py, host_receipts.py validate, component_matrix.py --check, new_host_grand_list.py --check and verdict_flip_candidates.py pass. Guarded Gitleaks: no leaks.

🤖 Generated with Claude Code

First real use of the qualified_models field (#130): a native_proven host receipt for the vllm
component on linux-wsl2-x86_64 (host rtx5090-laptop-20260923, hardware profile this-host-20260923).
Live checks on the resident service: health 200, 2048-dimension embeddings, max-model-len 8192.
Qualified models: Nemotron-3-Embed-1B-BF16 on vLLM 0.25.0 (the one local embedder for code and
memory retrieval; hybrid nDCG@10 +0.078) and Qwen3-8B-AWQ on vLLM 0.29.0 (20/20 tool calls, 4/5
tasks). Regenerated the component matrix and new-host grand list; no platform status changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-23T17:21:52.796795Z 158e869 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@seathatflowsinourveins
seathatflowsinourveins merged commit fcbca41 into main Sep 23, 2026
21 checks passed
@seathatflowsinourveins
seathatflowsinourveins deleted the claude/host-receipt-local-models-20260923 branch September 23, 2026 17:23
seathatflowsinourveins added a commit that referenced this pull request Sep 23, 2026
Hosted macOS run 35894394138 on 51f4145 (3147 tests, failures=2, down
from 124 at the start):

1. tests.test_host_receipts.QualifiedModelRecordTests.
   test_qualified_model_flag_is_recorded_and_validates: the same
   auto-detected-host.os-vs-declared-platform-id mismatch fixed
   elsewhere in this file for #131's new class -- --platform-id
   linux-wsl2-x86_64 with host.os/architecture left on auto-detection,
   then cmd_validate()'s cross-check. Pinned --os linux --architecture
   x86_64 in the shared _record() helper (the only other test using it
   that also calls cmd_validate). Grepped every other record fixture in
   the file claiming a linux platform: none of the others call
   cmd_validate, so none needed the same fix.

2. tests.test_offhost_app_state.EvidenceGuards.
   test_command_timeout_retains_output_and_stops_owned_group: raced a
   full /usr/bin/python3 interpreter's fork/exec + bootstrap against a
   0.1s timeout. macOS process creation (especially on GitHub-hosted
   runners) is measurably slower than Linux's, so the child was
   sometimes killed before it printed anything, leaving stdout empty --
   this looked like a process-group/kill bug but was a startup-latency
   race; os.killpg/setsid semantics are POSIX and unaffected. Fixed
   deterministically, not with a longer sleep: the child is now
   /bin/sh -c 'printf ...; sleep 3', which has no interpreter to
   bootstrap and no stdio buffering to flush, so the timeout budget
   (raised slightly, to .5s, for headroom) is spent almost entirely on
   the kernel's own fork/exec rather than CPython startup. The behavior
   under test (partial output retained, process group killed) is
   unchanged and still asserted on every platform.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
seathatflowsinourveins added a commit that referenced this pull request Sep 23, 2026
…tform-specific tests) (#126)

* Fix macOS false-positive repository-escape refusal in safe_file()

safe_file(root, relative) compared path.resolve() (fully resolved) against
root as given, unresolved. On macOS, TMPDIR sits under a symlinked system
directory (/var -> /private/var), so a legitimate root under TMPDIR no
longer prefix-matched its own resolved descendant, and safe_file raised
"source path escapes the repository" for every caller (catalog_decisions,
host_receipts, component_matrix, freshness_propose, lane_packets) even
though nothing escaped.

Resolve root once at the top of safe_file so both sides of the containment
check are canonicalised the same way. The per-component
"source symlinks are forbidden" check (which detects a symlink actually
placed inside the tree) is untouched and keeps refusing real escapes.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* Fix macOS false-positive symlink-ancestor refusals under TMPDIR

Several independent safe_path()/safe()/target-symlink checks walked every
ancestor of a path (or compared path.resolve() to path.absolute()) looking
for an injected symlink. On macOS, TMPDIR resolves through /var ->
/private/var (or /tmp -> /private/tmp), so a legitimate path under the
system temp directory always contained a "symlink ancestor" and was
refused, even with nothing actually escaping.

Anchor each check on tempfile.gettempdir(): resolve that one system
boundary once, then walk only the components below it (or, for a path
outside the temp tree, every component from the filesystem root, exactly
as before) looking for an injected symlink. A symlink placed inside the
job-owned tree -- an alias pointing back into it, a swapped destination, a
tampered receipt.json -- still breaks that walk and is refused; only the
tolerated system boundary above it stops causing false positives. Applies
to:
  - alpaca-historical/collect.py and catalyst-dataset/dataset.py
    (symlink_or_parent_traversal_refused, shared by security-identity,
    lifecycle-sample and catalyst provenance via the same bridge module)
  - research-efficiency/experiment.py (symlink refused)
  - offhost-restore/verify.py (repository target traverses a symlink)
  - service-reboot/run.py (--work must be a new absolute path)

offhost-app-state/run.py's owned_root() takes a different, narrower shape:
it requires a path directly under the literal /tmp, so instead of walking
ancestors it now resolves both /tmp and the candidate's parent for the
containment compare, while path.is_symlink() (not a resolve() comparison
on path itself) keeps catching a symlinked leaf; the returned path stays
the literal /tmp form the caller passed in.

offhost-app-state/run.py and offhost-restore/verify.py are both pinned by
frozen_sources sha256 entries in offhost-app-state/plan.json and
offhost-restore/hosted-plan.json; both are re-pinned to the new bytes so
test_active_recovery_plans.py's dispatch-source guard passes again.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* Skip Linux-x86_64-only bootstrap-linux.sh tests on other platforms

bootstrap-linux.sh has its own uname-based guard ("This verified asset set
targets x86_64 Linux, not Windows/Git Bash or ARM.") and refuses to run at
all off Linux x86_64 -- correctly, since it installs x86_64 Linux binaries.
On macOS this guard fires before every later check the affected tests
exercise (unknown profile, null-hash pin refusal, unpinned-component
fail-closed, the ECO_INSTALL_ROOT-resolves-to-HOME refusal, and the
missing-tool detection under --skip-system-packages), so those tests saw
the platform-guard message instead of the behavior under test.

Mark exactly the affected test methods (not their whole classes, which
also hold platform-portable tests like --help and missing --profile) with
skipUnless(sys.platform.startswith('linux') and platform.machine() ==
'x86_64'). bootstrap-macos.sh is the already-covered macOS path; no
assertion is deleted or weakened.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* Pin host.os/architecture in record fixtures that later cross-check them

Four RecorderRoundTripTests fixtures called the record CLI with an
explicit --platform-id linux-wsl2-x86_64 but left host.os/architecture on
auto-detection, then ran cmd_validate(), which cross-checks a receipt's
host.os/architecture against adoption/manifest.json's platform_profiles
for the claimed platform_id. On a Linux x86_64 host the auto-detected
values happened to match; on macOS (auto-detected os=macos,
architecture=arm64) they legitimately do not match linux-wsl2-x86_64's
declared linux/x86_64, so validation correctly failed.

Pass --os linux --architecture x86_64 explicitly (the CLI already supports
overriding auto-detection for exactly this), so the fixture is consistent
with its own hardcoded platform_id on every host. This also fixes two
platform_profile_map() KeyErrors elsewhere in the suite: that function
swallows InvalidDecisionIndex and returns {}, which was the effect of the
adjacent safe_file() containment bug now fixed separately -- no schema or
assertion changed here.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* Compare against resolved paths where the source already canonicalises

Three tests compared subprocess/rendered output to a literal, unresolved
temp path, while the code under test deliberately resolves that same path
before emitting it:
  - grand-dashboard install.py resolves dagu_bin/dagu_home before writing
    them into the generated systemd unit (so the unit references a real,
    unambiguous path);
  - worker-recovery run.py's exact_read_rule() resolves its workspace
    argument before embedding it in a generated Bash permission rule (so
    the rule targets the real path, not a symlink alias of it);
  - render_config.py's default_project_root() falls back to Path.cwd(),
    and the getcwd() syscall always returns the fully resolved directory
    (POSIX, not macOS-specific).

On a host where the temp root has a symlinked ancestor (macOS's
/tmp -> /private/tmp, /var -> /private/var, or TMPDIR itself symlinked),
the resolved output legitimately differs from the tests' hardcoded literal
form. Compare against the same .resolve() the source already applies
instead of hardcoding the unresolved path; the source's canonicalisation
is correct and unchanged.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* Re-register changed files in the evidence manifest

Re-hash the source, test and plan files touched by the macOS
portability fixes (host_receipts.register_file per file, then
evidence_manifest.py --write to keep files[] sorted).

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* Security review round 2: root-owned-only symlink trust, TOCTOU and '..' fixes

An independent Opus security review of the round-1 macOS portability fixes
found three weakenings, all unreachable through current callers but real:

1. owned_root() (offhost-app-state/run.py) compared path.parent.resolve()
   to Path('/tmp').resolve(), so /tmp/x -> /tmp made path.parent.resolve()
   match even though path.parent was NOT literally /tmp -- and /tmp/x could
   be repointed elsewhere after this check and before the root is actually
   used (TOCTOU). Fixed: path.parent must literally be /tmp or its resolved
   form, and path.resolve() must land exactly at <resolved /tmp>/<name>.
   The same resolve()-vs-literal-/tmp pattern was also live in three other
   offhost-app-state checks discovered by the round-2 macOS CI run
   (35890224379): cleanup()'s and password()'s checks always differ once
   /tmp itself is a symlink, even with nothing wrong, since self.root is
   already a vetted literal path; fixed by checking only the specific
   component that could actually be swapped (the leaf, or the intermediate
   'private' directory) instead of resolve()-comparing the whole path.
   validate_source() had the identical bug for an unvetted folder argument;
   fixed by routing it through the new shared helper (see 3).

2. decode_repository() (offhost-restore/verify.py) lost its '..' refusal
   when the old target.resolve() != target.absolute() check (which caught
   '..' incidentally, since resolve() collapses it and absolute() does not)
   was replaced with the TMPDIR-anchored symlink walk. Restored as an
   explicit `'..' in target.parts` refusal.

3. The TMPDIR exemption in every one of the six affected checks trusted
   $TMPDIR unconditionally: everything at or above tempfile.gettempdir()
   was skipped, so an attacker-controlled symlink set as TMPDIR (or a
   shared TMPDIR an attacker can plant a link under) was tolerated. Added
   scripts/path_safety.refuse_untrusted_symlinks(), one shared helper used
   by alpaca-historical/collect.py, catalyst-dataset/dataset.py,
   research-efficiency/experiment.py, offhost-restore/verify.py,
   service-reboot/run.py and offhost-app-state/run.py's validate_source():
   it walks every component from the filesystem root (never anchored on
   $TMPDIR) and tolerates a symlinked ancestor only when it is owned by
   root and not group/world-writable -- exactly macOS's own
   /tmp -> /private/tmp, /var -> /private/var, /etc -> /private/etc, never
   a symlink any unprivileged account (including the one running this
   process) could have planted or repointed. catalog_decisions.safe_file()
   does not adopt it: its containment model is a confined, already-resolved
   root with zero symlink tolerance inside the tree, which this helper's
   "tolerate a root-owned ancestor" relaxation would only weaken.

collect.py's own diff is kept to an import and a one-line call (per
compatibility note below); the other five callers follow the same shape.

New regression tests (each reproduces the weakening against c1f30cb before
passing against this fix): scripts/path_safety's own ownership/mode
behavior including the TMPDIR case (tests/test_path_safety.py, with the
root-owned/non-writable case simulated via a Path.lstat mock -- Linux does
not enforce meaningful symlink permission bits the way macOS does, so a
real Linux symlink cannot demonstrate the "tolerated" branch); owned_root's
symlinked-parent TOCTOU gap and decode_repository's '..' refusal
(tests/test_offhost_app_state.py, tests/test_offhost_restore.py); the same
TMPDIR-trust weakening reproduced end-to-end through experiment.safe() and
service-reboot's run() (tests/test_research_efficiency.py,
tests/test_service_reboot.py).

Compatibility: security-identity/ledger.py and quality.py (frozen
base-collector.py = collect.py bytes) and research-efficiency/experiment.py
(frozen self-comparison) fail-closed re-verification of any
identity-readiness private run captured before this change, since the
guarded files' bytes changed. This is the correct, deliberate consequence
of fixing a real check in a file those runs pin; there is no way to fix the
check without changing the file it lives in.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* Re-pin frozen_sources hashes after the round-2 security fixes

offhost-app-state/run.py, offhost-restore/verify.py and the two test files
that gained new regression coverage are all pinned by frozen_sources
sha256 entries in these two plans; re-pin them to the fixed bytes so
test_active_recovery_plans.py's dispatch-source guard passes again.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* Re-register changed files in the evidence manifest (round 2)

Re-hash the files touched by the round-2 security fixes (host_receipts
.register_file per file, then evidence_manifest.py --write to keep
files[] sorted).

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* Security review round 3, findings A+B: tighten the trusted-link shape

Two lows in the round-2 shared helper:

A. Once a root-owned link was tolerated, the walk followed it and every
   component through it, with no check on where in the tree that link
   sat. A root-owned link deeper in the tree (a hard link to, or a copy
   of, a relative framework link such as macOS's Versions/Current,
   planted somewhere under /tmp) could redirect the path. Fixed: a
   symlink is tolerated only when it sits directly under the filesystem
   root (candidate.parent == Path(candidate.anchor)) -- exactly macOS's
   /tmp, /var and /etc; anything else is refused regardless of ownership.

B. Checking the *link's own* mode did nothing: a symlink's permission
   bits are not what gates replacing it, and Linux does not even enforce
   them (lstat reports 0o777 for every symlink regardless of chmod).
   What actually controls whether an unprivileged account can repoint or
   replace the link is whether it can write to the directory the link
   lives in. Fixed: check that the link's parent directory (which, given
   A, is always /) is root-owned and not group/world-writable, instead
   of the link's own stat.

New/adjusted tests in tests/test_path_safety.py, each exercising the two
conditions independently: a root-owned link (mocked) NOT directly under /
is still refused; a link directly under / (mocked, since a real one
cannot be planted at the actual filesystem root here) whose parent is
mocked writable is refused; the positive case (link directly under /,
real /'s own genuinely root-owned/non-writable stat, no mock needed for
the parent check) is tolerated.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* Fix two more macOS-only failures found by the round-3 hosted run

Hosted macOS run 35894394138 on 51f4145 (3147 tests, failures=2, down
from 124 at the start):

1. tests.test_host_receipts.QualifiedModelRecordTests.
   test_qualified_model_flag_is_recorded_and_validates: the same
   auto-detected-host.os-vs-declared-platform-id mismatch fixed
   elsewhere in this file for #131's new class -- --platform-id
   linux-wsl2-x86_64 with host.os/architecture left on auto-detection,
   then cmd_validate()'s cross-check. Pinned --os linux --architecture
   x86_64 in the shared _record() helper (the only other test using it
   that also calls cmd_validate). Grepped every other record fixture in
   the file claiming a linux platform: none of the others call
   cmd_validate, so none needed the same fix.

2. tests.test_offhost_app_state.EvidenceGuards.
   test_command_timeout_retains_output_and_stops_owned_group: raced a
   full /usr/bin/python3 interpreter's fork/exec + bootstrap against a
   0.1s timeout. macOS process creation (especially on GitHub-hosted
   runners) is measurably slower than Linux's, so the child was
   sometimes killed before it printed anything, leaving stdout empty --
   this looked like a process-group/kill bug but was a startup-latency
   race; os.killpg/setsid semantics are POSIX and unaffected. Fixed
   deterministically, not with a longer sleep: the child is now
   /bin/sh -c 'printf ...; sleep 3', which has no interpreter to
   bootstrap and no stdio buffering to flush, so the timeout budget
   (raised slightly, to .5s, for headroom) is spent almost entirely on
   the kernel's own fork/exec rather than CPython startup. The behavior
   under test (partial output retained, process group killed) is
   unchanged and still asserted on every platform.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* Pin scripts/path_safety.py in every frozen-source list (finding C)

The shared helper added in round 2 was adopted by six checks but never
itself pinned anywhere, so a changed helper would silently run different
code than reviewed:

  - offhost-app-state/plan.json and offhost-restore/hosted-plan.json:
    added to frozen_sources, checked by Trial.verify_frozen() and
    verify_sources() respectively (run.py:262-ish in each) -- see the
    companion commit for the re-pinned hashes.
  - service-reboot/run.py: added to the repository_inputs digest set (a
    host-side-only dependency of run()'s own check, so tracked alongside
    source_paths rather than added to it, since source_paths is also
    copied into the guest payload and path_safety.py has no guest-side
    role).
  - research-efficiency/experiment.py: folded 'experiment.py' and
    'run_codex.py' plus the new 'path_safety.py' into one RUNTIME_SOURCES
    mapping, reused by both prepare()'s freeze-time copy and run()'s
    source-changed-after-freeze comparison, so all three trip the same
    'runtime source changed after freeze' check.

New tests in tests/test_active_recovery_plans.py confirm the pin is
present in both plans and that corrupting the recorded hash (simulating
a changed helper, since editing the real checked-in file would not be
reverted) trips verify_frozen()/verify_sources() with the expected
message.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* Re-pin frozen_sources hashes after the round-3 fixes

path_safety.py (findings A/B), tests/test_offhost_app_state.py (the
timeout-test fix) and scripts/path_safety.py's new pin itself all changed
bytes that offhost-app-state/plan.json and offhost-restore/hosted-plan.json
pin; re-pin them to the fixed/new bytes so
test_active_recovery_plans.py's dispatch-source guard passes again.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* Re-register changed files in the evidence manifest (round 3)

Re-hash the files touched by the round-3 fixes (host_receipts
.register_file per file, then evidence_manifest.py --write to keep
files[] sorted).

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* Make the macOS full test suite a gating step now that it passes on macos-15

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

---------

Co-authored-by: seathatflowsinourveins <[email protected]>
Co-authored-by: Claude Sonnet 5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant