Skip to content

fix(resume): re-validate persisted mounts with the mount guard - #1216

Open
LuckVd wants to merge 2 commits into
usestrix:mainfrom
LuckVd:fix/1214-resume-mount-guard
Open

fix(resume): re-validate persisted mounts with the mount guard#1216
LuckVd wants to merge 2 commits into
usestrix:mainfrom
LuckVd:fix/1214-resume-mount-guard

Conversation

@LuckVd

@LuckVd LuckVd commented Sep 1, 2026

Copy link
Copy Markdown

Description

Fixes #1214

--resume rebuilt sandbox bind mounts from strix_runs/<run>/run.json but did not apply check_mountable_dir to workspace_mount or a repository target's cloned_repo_path (only local_code targets were re-checked), and workspace_files sources were not guarded at all. run.json lives on disk under the scanned tree and can be tampered with (e.g. by prompt-injected sandbox agent output during a whitebox scan of the project itself), so a modified record could mount /, $HOME or .ssh writable into the next resumed sandbox, or copy credential/system files in via workspace_files[].source_path. A comment in the resume path even documented the guard as intentionally skipped for workspace_mount.

Change

  • strix/interface/cli_args.py (_load_resume_state):
    • workspace_mount now passes through check_mountable_dir after the existence check, before attach_workspace_mount.
    • Repository targets' cloned_repo_path now passes through check_mountable_dir after the existence check (missing-directory guidance preserved).
    • workspace_files sources restored from the record are each passed through the new check_mountable_file before resolve_workspace_files; a tampered record can no longer smuggle credential/system files into the sandbox.
    • Outdated "the target mount guard does not apply" comment replaced with the new policy.
  • strix/interface/utils.py:
    • New check_mountable_file(path): applies the same system/home/credential checks as check_mountable_dir, evaluated against the file's parent directory and its own path components.
    • Shared exact-match/tree sets extracted into _mount_guard_sets(anchor) used by both guards (no behavior change for check_mountable_dir).
  • Tests:
    • tests/test_local_sources.py: 6 new check_mountable_file cases (project file accepted; missing/system/home/credential files rejected; project file under the home root accepted).
    • tests/test_cli_resume.py (new): resume integration tests — forbidden workspace_mount (home), system workspace_mount (/etc), forbidden cloned_repo_path (/etc), credential workspace_files source (~/.ssh/id_rsa) all abort; a clean record still loads.

Scope note

The issue also suggests keeping run records outside any bind-mounted tree or making them immutable from inside the sandbox. That is a broader design change (run layout / sandbox writability) and is intentionally left out of this PR; the mount guard now covers all resume mount inputs regardless of where run.json lives, which closes the demonstrated bypass.

Ruff reports one pre-existing ISC004 (implicit string concatenation) on strix/interface/utils.py:877 — untouched by this change (confirmed present on the unmodified checkout) and not part of this diff.

Type of Change

  • 🐛 Bug fix (security hardening)

Related Issue

Testing

  • uv run pytest tests/test_local_sources.py tests/test_cli_resume.py tests/test_pricing.py -q45 passed (includes 6 new file-guard unit tests and 5 new resume integration tests).
    • Full-repository suite not run from this environment (resource-constrained sandbox; only affected modules exercised).
  • ruff check on the four changed files — clean (only the pre-existing ISC004 on an untouched line remains in utils.py).
  • ruff format --check on changed files — clean.
  • git diff --check — clean.

Checklist

  • git diff --check origin/main...HEAD passes
  • Changed source files are formatted
  • Targeted tests for the changed packages/components pass
  • Diff-scoped lint passes where applicable
  • Full-repository checks were run, or any unrelated/environment-dependent failures are documented above (documented: affected-module runs only, sandbox resource limits; pre-existing ruff ISC004 on untouched line noted)
  • Self-reviewed the code
  • Added/updated tests covering the change
  • Breaking changes are clearly called out in the description above (none — valid resumes behave identically; invalid/tampered records now abort with a clear error)

root added 2 commits September 1, 2026 13:47
test_resolves_common_bare_model_names asserted against LiteLLM's mutable
model-cost registry, so it passed or failed depending on whether LiteLLM's
remote model cost map (multiple differently priced MiniMax-M3 routes) or the
local fallback (single route) was loaded at runtime.

Mock a small fixed registry, matching the pattern already used by the other
resolver tests, so the assertions never depend on external registry state.
--resume rebuilt sandbox bind mounts from run.json but skipped
check_mountable_dir for workspace_mount and repository cloned_repo_path
(local_code paths were already re-checked), and workspace_files sources
were not guarded at all. run.json lives on disk under the scanned tree and
must be treated as untrusted: a tampered record could mount '/' or $HOME
writable, or copy credential files into the sandbox.

Apply the same guard on resume:
- workspace_mount and cloned_repo_path now pass through check_mountable_dir
- workspace_files sources pass through the new check_mountable_file, which
  applies the same system/home/credential checks to a single file
- split the shared root/tree/credential sets into _mount_guard_sets used by
  both guards

Adds unit coverage for check_mountable_file and resume integration tests
for the three bypass paths.
@greptile-apps

greptile-apps Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR closes resume-time mount-validation gaps for persisted repository paths, workspace mounts, and workspace files.

  • Adds a shared mount-policy helper and a file-specific guard that resolves symlinks and rejects system, home, and credential paths.
  • Revalidates all persisted mount sources before reconstructing resumed sandbox inputs.
  • Adds focused resume and mount-policy tests, while stabilizing pricing tests against external model-registry changes.

Confidence Score: 5/5

The PR appears safe to merge, with persisted sandbox inputs consistently revalidated before resume.

The new validations cover the previously unchecked persisted mount sources, resolve symlinks before policy checks, preserve clean resume behavior, and introduce no supported changed-code failure.

Important Files Changed

Filename Overview
strix/interface/cli_args.py Resume reconstruction now validates persisted repository, workspace, and workspace-file paths before exposing them to the sandbox.
strix/interface/utils.py The mount-policy sets are shared between directory and file guards, with file checks resolving symlinks and inspecting parent and credential-path components.
tests/test_cli_resume.py New integration tests cover protected persisted mounts and successful restoration of a clean record.
tests/test_local_sources.py New unit tests exercise accepted and rejected file locations for the mount guard.
tests/test_pricing.py The bare-model resolver test now uses deterministic model metadata and restores all mutated global and cached state.

Reviews (1): Last reviewed commit: "fix(resume): re-validate persisted mount..." | Re-trigger Greptile

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.

--resume trusts workspace_mount/cloned_repo_path from run.json without check_mountable_dir — bypasses mount guard

1 participant