Repository navigation
host: make the export planner and its host rechecks private to the CLI (SL6.5a2) - #1544
Merged
Pedro Henrique Penna (ppenna) merged 2 commits intoNov 12, 2026
Conversation
Contributor
This pull request changes existing testsIt modifies or removes 46 existing test asset(s), listed below. Review each with care: a change to a test can hide a regression that the test caught. See doc/contribute.md#changes-to-existing-tests. Test code
Test policy
Relocated unchanged: 0; added: 12. Compared the merge base |
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues block approval of the changes making export planning and pre-launch host rechecks private to the CLI.
No open issues 🎉
What changed in this PR
This PR advances #1125’s library-slimming work by making export planning and pre-launch host rechecks private to the CLI. It preserves CLI policy behavior, identity pins, and the public export-description, structural-validation, and lowering APIs.
Changes:
- Makes the planner, its input types, and host rechecks crate-private and gated by
cli. - Moves export security tests beside their owner and replaces the CLI fixture’s planner dependency with an independently constructed export.
- Updates documentation, acceptance-test references, and Linux/Windows public API snapshots.
| File | Description |
|---|---|
nvx-rs/tests/security_checklist.rs |
Removes export checks relocated to filesystem unit tests. |
nvx-rs/tests/cli/openvmm.rs |
Builds expected fixture exports without calling the private planner. |
nvx-rs/src/host/mod.rs |
Documents the distinction between public export operations and CLI planning. |
nvx-rs/src/host/filesystem/verify.rs |
Privatizes and gates host rechecks while retaining public structural validation. |
nvx-rs/src/host/filesystem/tests.rs |
Gates planner tests and receives the relocated security checks. |
nvx-rs/src/host/filesystem/policy.rs |
Gates policy-resolution and checking helpers used by the CLI. |
nvx-rs/src/host/filesystem/mod.rs |
Narrows planner visibility and documents the remaining public contract. |
nvx-rs/src/host/filesystem/limits.rs |
Gates host-inspection helpers while retaining shared limit checks. |
nvx-rs/src/host/filesystem/host_path.rs |
Gates identity lookup and lenient canonicalization helpers. |
nvx-rs/README.md |
Describes the public export API and private CLI path rules. |
nvx-rs/public-api/windows.txt |
Removes privatized planner and verifier entries from the Windows snapshot. |
nvx-rs/public-api/linux.txt |
Removes privatized planner and verifier entries from the Linux snapshot. |
doc/acceptance-case-matrix.md |
Points coverage references to the relocated filesystem tests. |
Review effort: Balanced
This was referenced Nov 12, 2026
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/slim-sl6-6d-planner-test-only
branch
from
November 12, 2026 21:01
3e29656 to
c6d9d60
Compare
Base automatically changed from
agents/slim-sl6-6d-planner-test-only
to
feature/nvx-refactor
November 12, 2026 21:19
SL6.5a2 (#1125) makes the export planner and its host rechecks private to the CLI. Two integration tests reached them through their public paths, which they cannot once those are private, so each case moves beside its owner or drives the nvx-rs binary alone, with the same assertions: - The security checklist's four checks of an export, which only the CLI plans and checks again before a start, an_export_refuses_guest_targets_that_are_not_canonical_or_that_a_root_reserves, an_export_refuses_a_read_only_path_inside_a_writable_directory, the_caller_owner_is_refused_on_windows and an_exported_object_that_a_workload_could_move_is_checked_again_before_a_start, move unchanged to the unit tests of host::filesystem, where plan_blockless plans their mappings into the blockless root, as the checklist's plan did. - cold_run_expected planned the shares of a cold-run fixture through filesystem::plan before it lowered them through the public VM contract. fixture_export now builds the fixture's ExportPlan as host::filesystem documents an export, independently of the CLI's planner, as of its parser and token renderer, so the dry-run and one-shot tests compare the CLI's command line with an export that they derive on their own. A denied path that a workload could move carries a zero identity there: ExportPlan::check requires one, and no command line carries it. The case matrix names the moved checklist tests where they now are. Part of #1125 (SL6.5a2). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The export planner was public API of nvx, although path resolution, the protected and reserved targets, the allow, deny and write rules, aliases and the host rechecks before a start are the CLI's policy: only nvx-rs run and the sandbox's shares plan exports, and only the CLI checks a recorded export again before it starts a VM. They become crate-private where they are, gated by cli, with no change of behavior: - host::filesystem's plan, ExportRequest, Mapping, Target and PathList become pub(crate) and take cli, with the helpers that only they use: resolve_mapping, check_targets, shows, Mapped and unsupported_inspection in the module, identity and canonicalize_lenient in host_path, check_root and check_policy_paths in limits, and the resolution and checks of policy paths in policy. - ExportPlan::verify, the host recheck before each start, becomes pub(crate) and takes cli, with bound, changed_object, uninspected, loops, check_caller_roots and CALLER_OWNER_ON_WINDOWS, which only it and the planner use. The CLI keeps the identity pins, and calls verify before each launch as before. - What stays public doesn't change: ExportPlan with its children, binds, owner and identities, check, openvmm_arguments, kernel_token and guest_table, ReservedTargets, Owner, FileIdentity, the constants, and guest_path, at nvx::host::filesystem:: and nvx::. host::paths is already crate-private; resolve, which only the CLI calls, took cli in SL6.7b, and the rest serves host::vm, host::snapshot and the VM directory too, so it stays ungated. host::platform's file_identity, crate-private since SL6.14, then has no caller outside the CLI and the tests: the planner, its limits and verify were the others. Like process_start_time, it and its re-exports take cfg(any(test, feature = "cli")), since the unit tests of host::vm and host::snapshot compare the files and volumes that paths name with it. On Windows, handle_identity and the five imports that only the two use take it too, and on Linux the identity module, whose other items already take cli. The test-layout allowlist records the eleven hooks as seams: two more in linux/mod.rs, one more in windows/mod.rs, seven in windows/identity.rs and one in other.rs. The 27 unit tests of the planner and of verify in host::filesystem take cli with their 11 helpers, so the all-features tests run them as before; the six tests of guest_path, within, outermost, the whole-volume refusal, the reserved targets and check run without it. The public API snapshots lose 20 lines on each platform, the planner's items with their fields and ExportPlan::verify, and gain none. The docs of host and host::filesystem, and those of ExportPlan and check, name the planner and the recheck as the CLI's, and the README's filesystem section and module row follow. Part of #1125 (SL6.5a2). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/slim-sl6-5a2-path-rules-private
branch
from
November 12, 2026 21:20
c765ea3 to
43ba688
Compare
Pedro Henrique Penna (ppenna)
marked this pull request as ready for review
November 12, 2026 21:24
Pedro Henrique Penna (ppenna)
merged commit Nov 12, 2026
19b5404
into
feature/nvx-refactor
87 checks passed
Pedro Henrique Penna (ppenna)
deleted the
agents/slim-sl6-5a2-path-rules-private
branch
November 12, 2026 21:54
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #1125 (SL6.5a2, "Path rules are private to the CLI"). Parent #1088, umbrella #1076.
It was stacked on #1543 (SL6.6d), which merged, and its base is now
feature/nvx-refactor. SL6.6d deleted the five planner items that this PR would otherwise have left without a caller,resolve_guest_pathamong them (note on #1125), so that this one stays visibility-only. It needed SL6.3 (#1530), SL6.4 (#1533) and SL6.7b (#1539), which merged too.What and why
Path resolution, the protected and reserved targets, the allow, deny and write rules, aliases and the host rechecks before a start are the CLI's policy, but the export planner and
ExportPlan::verifywere public API ofnvx. This PR does SL6.5a2's Scope: it makes them private to the CLI where they are, without changing what NVX's programs plan, refuse or pin. Two commits:tests: check the export planner through its unit tests and the CLI(d46ca5a4): the two integration tests that reached the planner through its public paths move beside its owner or drive the binary, with the same assertions (Test changes, below). It changes no production code, so it passes on its own, before the visibility change.host: make the export planner and its host rechecks private to the CLI(43ba6882):plan,ExportRequest,Mapping,TargetandPathListbecomepub(crate)and takecli, where they are inhost::filesystem, with the helpers that only they use:resolve_mapping,check_targets,shows,Mappedandunsupported_inspectionin the module,identityandcanonicalize_lenientinhost_path,check_rootandcheck_policy_pathsinlimits, and the resolution and checks of policy paths inpolicy.verify's host rechecks:ExportPlan::verifybecomespub(crate)and takescli, withbound,changed_object,uninspected,loops,check_caller_rootsandCALLER_OWNER_ON_WINDOWS, which only it and the planner use. The CLI keeps the identity pins and callsverifybefore every launch from a recorded export (check_imagesinhost::cli::sandbox::state), as before.host::pathsis already crate-private. Itsresolve, which only the CLI calls, tookcliin SL6.7b; the rest of it serveshost::vm,host::snapshotand the VM directory too, so it stays ungated and unchanged.host::platform::file_identity, crate-private since SL6.14 (nvx-rs slimming 4.3.4: NVXC, the host's platform layer and the guest's space are private #1127), has no caller outside the CLI and the tests once the planner, itslimitsandverifytakecli, so the warning-denied build withoutclifinds it dead. As SL6.14 did forprocess_start_time, it and its re-exports takecfg(any(test, feature = "cli")), since the unit tests ofhost::vmandhost::snapshotcompare the files and volumes that paths name with it. On Windows,handle_identityand the five imports that only the two use take it too, and on Linux theidentitymodule, whose other items already takecli. Theclibuild keeps all of them, so the binary doesn't change.scripts/test-layout-allowlist.jsonrecords the 11 hooks as seams:linux/mod.rs1 → 3,windows/mod.rs1 → 2, and new entries forwindows/identity.rs(7) andother.rs(1).ExportPlanwith its children, binds, owner and identities,check,openvmm_arguments,kernel_tokenandguest_table,ReservedTargets,Owner,FileIdentity,GUEST_EXPORT,MAX_CHILDREN,RESERVED_TARGETS, andguest_path, atnvx::host::filesystem::and atnvx::. The crate root re-exportsguest_pathalone; SL6.6d deletedresolve_guest_path.host's andhost::filesystem's module docs, and those ofExportPlanandcheck, describe the export that a caller records, checks and lowers, and name the planner and the recheck as the CLI's, without linking to them. The README'snvx::host::filesystemrow and section say the same,PathList::nameleaves the lowering's naming note, and Sandbox input contract says that the sandbox plans its shares through the private planner.plan,ExportPlan::verify, andExportRequest,Mapping,PathListandTargetwith their fields and variant. Nothing is added.guest_pathstays listed inhost::filesystemand at the root.Canonicalization, the protected and reserved targets, the allow, deny and write-path rules, aliases, OpenVMM's limits, the identity pins, error texts and accepted spellings don't change, and neither do
BOOT_CONTRACT_VERSIONandCONTROL_CONTRACT_REVISION.Size
Measured against
d67a2d5ab57(feature/nvx-refactorwith SL6.6d), by #1076's rules, with #1125's note that privatizing counts as a move. This PR is visibility-only: 0 core lines and no counted files, as estimated.nvx-rs/src: 0 core lines, no counted file. The measuring script counts 101 added code lines in 10 production files, and every one of them makes an item crate-private (pub(crate)), gates it bycli, or, forfile_identity, byany(test, feature = "cli"), or is an import that the gates force, with its continuation lines. The rest of the production diff is docs.Old tests of the area
tests/security_checklist.rstohost::filesystem's unit tests, the owner ofplanandverify, each checking the requirement that it checked:an_export_refuses_guest_targets_that_are_not_canonical_or_that_a_root_reserves: a mapping's guest target must be an absolute path other than the root, without empty,.or..components or NUL characters (Target::Guest's docs), and free in the root'sReservedTargets(plan's); a plan of a free target passescheckandverify.an_export_refuses_a_read_only_path_inside_a_writable_directory: a read-only path inside a part that the guest may write is refused on every host, since the host could not keep it read-only (host::filesystem's docs andplan's).the_caller_owner_is_refused_on_windows: the caller owner needs a Linux host (plan's docs andOwner::Caller's).an_exported_object_that_a_workload_could_move_is_checked_again_before_a_start: the plan pins the objects that a workload could move, inside a writable part, and the check before each start refuses a decoy at a pinned path withErrorKind::Otherand leaves the moved object as it is (host::filesystem's docs andverify's).host::filesystem, with the same assertions. The 23 of the planner and ofverify, and the four moved ones, now build withcli, as the planner does, and CI'scargo test --all-featuresruns them; the six ofguest_path,within,outermost, the whole-volume refusal, the reserved targets andcheckstill run without it. The CLI's tests that plan shares,host::sandbox's andhost::cli::run::guest_tokens's, already build withcli.cold_run_expectedoftests/cli/openvmm.rs(Test changes).Test changes
python scripts/check_test_changes.py --base d67a2d5ab57lists 46 changes, 42 to existing tests and 4 loosened allowlist entries, and 12 additions, 2 helpers and 10 test hooks, hencetests-modified:cligates, bodies unchanged (35), innvx-rs/src/host/filesystem/tests.rs: the 23 tests of the planner and ofverify, fromunrelated_directories_are_exported_with_their_own_accesstomapped_paths_resolve_to_utf8_paths, their helpersFilesystemPolicy,convention,plain_request,root,canonical,sources,option_values,second_volumeandat, and the imports ofdisplay_pathandplatform, which only they use (33); two tests that already had a platformcfgmerge it withcli. The helperplantakesclitoo, but the script pairs it with the checklist's removedplanand lists that pair as "moved", and this file's oldplanas "removed" (2); in fact this file'splanonly takescli, and the checklist'splanandrequesthelpers go with the tests that called them.The four moved tests (4), listed as moved from
nvx-rs/tests/security_checklist.rstonvx-rs/src/host/filesystem/tests.rs(Old tests, above), with their bodies as they were, but forplanbecoming the new helperplan_blockless, which plans into the blockless root throughplain_request, as the checklist'splandid through itsrequest, and the caller-owner test's request built withplain_request. They takecliwith the other planner tests.nvx-rs/tests/security_checklist.rs(2): its imports drop the planner's types, and its helpersplanandrequestgo; its module docs say that the checks of an export, which only the CLI plans and checks again, are unit tests ofhost::filesystem, as those of a staged image and of a scratch image's claim are of theirs.nvx-rs/tests/cli/openvmm.rs(1, and 1 added):cold_run_expectedplanned a cold-run fixture's shares throughfilesystem::planbefore it lowered them through the public VM contract. The new helperfixture_exportbuilds the fixture'sExportPlanashost::filesystemdocuments an export, independently of the CLI's planner, as the harness already is of its parser and token renderer: each share is a child of its own at its resolved host path, in their order; the policy paths are resolved, deduplicated and sorted inside the child that holds them, and a read-write share is writable whole or only below its write paths; each denied path that a workload could move carries a zero identity, sinceExportPlan::checkrequires one there but no command line carries it, andnvx::host::platform::file_identity, which gave the real one, has been crate-private since SL6.14; and each share binds its child's root at its target, the shallower targets first. Thedry_runandone_shottests that call it pass unchanged, so they compare the CLI's command line with an export that they derive on their own.scripts/test-layout-allowlist.json(4 loosened entries, and 10 added hooks): theany(test, feature = "cli")gates offile_identity(What and why, above), which keep it for the unit tests ofhost::vmandhost::snapshotwithoutcli. No test changes for them.doc/acceptance-case-matrix.mdnames three of the moved tests as coverage in 7 rows; those references now namenvx-rs/src/host/filesystem/tests.rs, and the counts don't change.check_matrix.pypasses.Validation
local_gates.py --files(fromdev) selectstest-changes,markdown-links,test-layout, thecheck-nvx-rssteps,nvx-cliand the microVM tests; I ran them from this branch's action definitions, with Rust 1.95.0, locally on Windows and onnvx-mshv-test(Azure Linux 3, kernel 6.18, linux-mshv, virtual machine), in temporary worktrees. Everything below ran first on1f2c4f61996and again onc765ea3687d, on SL6.6d's earlier heads, and passed, with the microVM scenarios below. Then the guest and NVXC chains, SL6.6c and SL6.7b landed under it, and both commits were rebased onto SL6.6d'sc6c3a9b66fb, with two changes:tests/security_checklist.rsconflicted with the base's move of the VM-liveness checks tohost::vm's unit tests: its module docs keep both moves, and its imports drop the planner's types and those that the base dropped.fixture_exportread the identity of each pinned denied path throughnvx::host::platform::file_identity, which SL6.14 made crate-private; it now gives a zero identity, whichExportPlan::checkrequires and no command line carries.Clippy without
clithen foundfile_identitydead, and itsany(test, feature = "cli")gates followed (What and why). SL6.6d merged asd67a2d5ab57, with the tree ofc6c3a9b66fb, and the commits moved onto it asd46ca5a4232and43ba68825f0, with the tree of87cd9e8d593, which the checks below ran on (git range-diff:=for both). On that tree:cargo fmt --all --check-D warnings:--all-targets --all-features,--lib,--lib --no-default-features, andserde,cliandtestingalone--target x86_64-unknown-linux-gnu, and--all-targetswith default features, natively and forx86_64-unknown-linux-gnu, which builds the tests withoutcli--all-targets --no-default-features --features guest--target x86_64-unknown-linux-musl --no-default-features --features guest -D warningscheck-guest-binaries)cargo check --target aarch64-apple-darwin:--all-targets --all-features, and--all-targetsand--libwith default featureshost/snapshot/tests.rs, is the base's--all-targets --all-featurespassed, the same warningcargo test --locked --all-featurescli113 passed, 2 ignored;security_checklist3 passed; every other integration and doc testcli123 passed;security_checklist3 passed; every other integration and doc testcargo test --locked,cargo build --locked --lib --no-default-features,cargo test --locked --lib --no-default-featuresclitests; 191 passed)RUSTDOCFLAGS=-D warnings cargo doc --locked --no-deps --all-featurescargo +1.89 check --locked --all-targets --all-featurespython scripts/public_api.pycheck_test_layout.py,check_matrix.py <worktree> 99d2022085fpython3 scripts/nvx.py test-microvm --backend mshvonc765ea3687d, withNVX_AUTO_DOWNLOAD=0, its releasenvx-rs, and the artifacts of CI run 43238436289, the push run of8ae0617b1eb, whose guest code and OpenVMM pin (14dc4116ec7) are this base's: the kernel, the Alpine initramfs, the Ubuntu distro layer and scratch template, and OpenVMM, each checked by SHA-256 against the run's artifacts and OpenVMM's provenance. All 14 passed:managed-exec-config,sandbox-gpt-layer,sandbox-lifecycle,sandbox-proxy-only,sandbox-ram-upper,snapshot-core,snapshot-tiers,untiered-network-policyandvm-lifecycle;--mountand filesystem-policy scenarios:filesystem-comma-paths, which shares directories throughnvx-rs run --mountand so plans them through the now-private planner, alone and as an aggregate with denied paths, andfilesystem-shares,filesystem-policy,filesystem-owneranddenied-filesystem-paths, which check the shares, policy paths and owner that OpenVMM enforces for such exports, and their refusals.swapped_objects_fail_verification,objects_that_cannot_be_inspected_fail_verification_by_what_the_failure_shows,narrowed_writes_pin_only_what_lies_in_writable_pathsandan_exported_object_that_a_workload_could_move_is_checked_again_before_a_startdo, and pass.1f2c4f61996, on the earlier base, with CI run 43203015701's artifacts, which SL6.7b used.file_identitygates keep everything in theclibuild, andfixture_exportis test code. CI's microVM jobs run them on KVM, MSHV and WHP with artifacts built from this head (below).d46ca5a4232: Clippy with-D warningsfor--all-targetswith every feature and with default features,cli, and no default features, and the tests ofhost::filesystem(32 passed),cli(113 passed, 2 ignored) andsecurity_checklist(3 passed).file_identitygates again, and found no issue.CI run 43309903121 on
43ba68825f0passed: 33 jobs succeeded, among them the microVM scenarios on KVM, MSHV and WHP, withfilesystem-shares,filesystem-policy,filesystem-owner,filesystem-comma-paths,denied-filesystem-pathsand the public sandbox scenarios, and 9 were skipped as for every pull request (OpenVMM CI of the pinned revision, for the unchanged pin, the debug kernels, publishing and performance records). Copilot's review of43ba68825f0recommends approval, with no comments.