feat(policy): discover default-location dev-tool caches in available_tools_policy - #673
feat(policy): discover default-location dev-tool caches in available_tools_policy#673caarlos0 wants to merge 3 commits into
Conversation
…tools_policy Add an opt-in second discovery source to `available_tools_policy` covering language-toolchain caches/registries/toolchains in their default home locations (cargo, go, npm, Maven, .NET, Ruby, Conan, Node version managers, …), so sandboxed builds work without the user exporting CARGO_HOME/GOPATH/RUSTUP_HOME/… The new grants are gated behind an `allow_dev_tool_caches` flag (default off); the existing env-var/PATH discovery stays always-on. - Credential-safe scoping: only build-input subdirectories are granted, never tool-home roots, so credential/key material stays unreadable (~/.cargo/credentials.toml, ~/.npmrc, ~/.m2/settings.xml, ~/.gradle/ gradle.properties, the .NET CurrentUser X509 store, …). The env-var half now scopes credential-bearing homes (CARGO_HOME) to safe subdirs too, rather than granting the whole root. - Read-only by default; only scratch caches a build writes on every run are read-write (go-build, ccache, sccache, ~/.gradle/caches + wrapper/dists, and Cargo's root-level .package-cache/.global-cache lock files). These are provisioned via the new `materialize_tool_cache_writes` so a first build's grant survives a downstream existence filter; it uses create_new to avoid truncating a concurrently-populated lock file. - The home is always the trusted process home (std::env::home_dir), never a caller- or command-env-controlled value, closing a symlink-redirect vector. - Unix-only; Windows layouts are covered by the existing %LOCALAPPDATA% env-var discovery. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 521575da-2087-4e55-87d3-28436b60ff4a Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR extends the Rust mxc_engine policy-discovery helpers to optionally include a curated allow-list of default-location developer tool caches under the user’s home directory, while tightening CARGO_HOME grants to credential-safe subdirectories and adding an explicit materialization step for write-cache candidates.
Changes:
- Updated
available_tools_policyto (a) scope credential-bearing tool homes likeCARGO_HOMEto safe subdirectories and (b) optionally merge in default-location home cache paths via a newallow_dev_tool_cachesflag. - Added
materialize_tool_cache_writesto provision candidate write-cache paths (dirs + Cargo lock/tracker files) before a deny-by-default sandbox run. - Updated
mxc-sdkre-exports/docs and adjusted SDK helper tests for the newavailable_tools_policysignature.
Show a summary per file
| File | Description |
|---|---|
| src/core/mxc-sdk/tests/sdk_helpers.rs | Updates tests for the new available_tools_policy(env, allow_dev_tool_caches) API. |
| src/core/mxc-sdk/src/lib.rs | Re-exports the new materialize_tool_cache_writes helper from mxc_engine. |
| src/core/mxc-sdk/README.md | Documents the new dev-tool-cache opt-in and the need to materialize write-cache grants. |
| src/core/mxc_engine/src/policy.rs | Core implementation: credential-safe scoping for CARGO_HOME, opt-in home cache discovery, write-candidate materialization, and extensive tests. |
| src/core/mxc_engine/src/lib.rs | Re-exports materialize_tool_cache_writes from the engine crate. |
Review details
Comments suppressed due to low confidence (1)
src/core/mxc_engine/src/policy.rs:775
materialize_tool_cache_writesdetects Cargo lock/tracker files viapath.ends_with(...)againstCARGO_RW_FILE_SUFFIXES(which are written with/). If a Windows-style path (e.g. fromCARGO_HOME) uses\, this check won’t match and the code will fall through tocreate_dir_all, creating a directory at.package-cache/.global-cacheinstead of touching a file. Consider detecting byPath::file_name()so it’s separator-agnostic and always treats these as files.
if CARGO_RW_FILE_SUFFIXES
.iter()
.any(|suffix| path.ends_with(suffix))
{
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Low
Condense the doc comments and inline notes added for the dev-tool-cache discovery to keep the essential "why" (trusted-home, credential-safe scoping, create_new non-truncation) without the padding. Comment-only; no behavior change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 521575da-2087-4e55-87d3-28436b60ff4a Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
|
PS: I kind of wish these would be optional... thoughts? |
Match the read-write Cargo lock/tracker files (`.package-cache`/`.global-cache`) on the final path component via `Path::file_name` instead of a `/`-prefixed string suffix. A relocated `CARGO_HOME` on Windows yields `\`-separated paths, so the old suffix match missed them and `create_dir_all`'d the lock file as a directory. Rename `CARGO_RW_FILE_SUFFIXES` -> `CARGO_RW_FILE_NAMES` and drop the misleading "Windows never matches" note. Adds a platform-native-separator regression test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 521575da-2087-4e55-87d3-28436b60ff4a Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
| let env = env_pairs(&[("PATH", &path_val), ("CARGO_HOME", &cwd)]); | ||
| let env = env_pairs(&[("PATH", &path_val), ("GOROOT", &cwd)]); | ||
|
|
||
| let result = available_tools_policy(Some(&env)); | ||
| let result = available_tools_policy(Some(&env), false); |
There was a problem hiding this comment.
question: any reason to change the env variable name in the test?
| /// (the compiler scratch caches, `~/.gradle/caches` + `wrapper/dists`, and Cargo's | ||
| /// root-level lock files). Cold dependency fetches stay a per-command bypass. | ||
| #[cfg(not(target_os = "windows"))] | ||
| fn home_tool_cache_policy_for(home: &str, macos: bool) -> HomeToolCachePolicy { |
There was a problem hiding this comment.
question: What issue in GitHub copilot made you want to spin up this PR for the home tool stuff? Was it not being able to build rust projects? Just trying to understand what this all now enables is all.
| let join = |rel: &str| format!("{home}/{rel}"); | ||
|
|
||
| // Read-only build inputs common to macOS and Linux. | ||
| const COMMON_READONLY: &[&str] = &[ |
There was a problem hiding this comment.
suggestion: would probably prefer these constants to be in their own file outside of the function itself for readability and maintainability of the function itself
| pub fn materialize_tool_cache_writes(readwrite_paths: &[String]) -> Vec<String> { | ||
| for path in readwrite_paths { | ||
| let is_cargo_lock_file = Path::new(path) | ||
| .file_name() | ||
| .and_then(|name| name.to_str()) | ||
| .is_some_and(|name| CARGO_RW_FILE_NAMES.contains(&name)); | ||
| if is_cargo_lock_file { | ||
| if let Some(parent) = Path::new(path).parent() { | ||
| let _ = std::fs::create_dir_all(parent); | ||
| } | ||
| // `create_new` never truncates, so a `.package-cache`/`.global-cache` | ||
| // populated concurrently (or by Cargo) is not clobbered as | ||
| // `File::create` would; `AlreadyExists`/errors are ignored. | ||
| let _ = std::fs::OpenOptions::new() | ||
| .write(true) | ||
| .create_new(true) | ||
| .open(path); | ||
| } else { | ||
| let _ = std::fs::create_dir_all(path); | ||
| } | ||
| } | ||
| readwrite_paths | ||
| .iter() | ||
| .filter(|path| Path::new(path).exists()) | ||
| .cloned() | ||
| .collect() | ||
| } |
There was a problem hiding this comment.
Question: In this function, are we preemptively creating read-write paths that do not already exist because the sandboxed AI workload may need to create those directories or files during execution?
For example, if the workload were building a Rust project with Cargo, the current behavior could result in an access-denied error when Cargo tries to create a required directory. With this change, are we creating those directories ahead of time so the workload can write to them?
| // drop non-existent ones (matching the env-var discovery). The write | ||
| // buckets stay unfiltered so `materialize_tool_cache_writes` can create | ||
| // them before a first build needs them. | ||
| policy.readonly.retain(|dir| directory_exists(dir)); |
There was a problem hiding this comment.
This retain uses directory_exists, and ".rustup/settings.toml" on line 542 is a file, not a directory, so it is always filtered out and never reaches a real policy.
The test home_tool_cache_policy_for_grants_shim_libexec_and_narrows_dotnet asserts on the pure table rather than the filtered output, so it passes and hides this.
| /// created before a deny-by-default sandbox can use them; call | ||
| /// [`materialize_tool_cache_writes`] on the returned | ||
| /// [`readwrite_paths`](FilesystemPolicyResult::readwrite_paths) to do so. | ||
| pub fn available_tools_policy( |
There was a problem hiding this comment.
Since this is a breaking public API change, an options struct or a enum, instead of a bool, which is descriptive would be better.
| /// [`readwrite_paths`](FilesystemPolicyResult::readwrite_paths). This is the one | ||
| /// side-effecting step of discovery, kept separate so discovery stays pure; | ||
| /// idempotent and best-effort. Returns the subset that now exists. | ||
| pub fn materialize_tool_cache_writes(readwrite_paths: &[String]) -> Vec<String> { |
There was a problem hiding this comment.
This is pub, takes arbitrary caller-supplied paths, create_dir_all's them, follows symlinks, and discards every error with let _ = and no Logger. Expected?
📖 Description
Adds default-location developer-tool cache discovery to
mxc_engine'savailable_tools_policy(re-exported frommxc-sdk), so sandboxed builds canread the toolchain caches/registries they need even when the user has not
exported
CARGO_HOME/GOPATH/RUSTUP_HOME/ … for the existing env-vardiscovery to find.
available_tools_policynow merges two sources into oneFilesystemPolicyResult:PATHdiscovery (always on, as before). Now additionally scopescredential-bearing tool homes — currently
CARGO_HOME— to their safebuild-input subdirectories instead of granting the whole root, so a token
stored beside the caches (
~/.cargo/credentials.toml) is no longer readable.allow_dev_tool_cachesparameter, default off). Covers cargo/rustup, go, npm/pnpm/yarn/bun/deno +
Node version managers (nvm/fnm/volta/asdf/mise), pyenv/pipx/virtualenv,
Maven/Gradle/sdkman, .NET/NuGet, Ruby (gem/rbenv/rvm), Conan, and node-gyp.
Key properties:
never tool-home roots, so
~/.cargo/credentials.toml,~/.npmrc,~/.m2/settings.xml,~/.gradle/gradle.properties,~/.gem/credentials,~/.nuget/NuGet.Config,~/.config/gh,~/.ssh,~/.gnupg, and the .NETCurrentUser X509 store all stay unreadable.
every run are read-write (
go-build,ccache,sccache,~/.gradle/caches+wrapper/dists, and Cargo's root-level.package-cache/.global-cachelockfiles). These are provisioned by the new
materialize_tool_cache_writesso afirst build's grant survives a downstream existence filter; it uses
create_newto avoid truncating a concurrently-populated lock file.(
std::env::home_dir), never a caller- or command-env-controlled value,closing a symlink-redirect vector.
%LOCALAPPDATA%env-var discovery;available_tools_policyreturns no homecaches there.
The discovery function stays pure (no filesystem writes) so callers can
inspect/serialize the policy without side effects; materialization is a separate,
explicit step.
🔗 References
No linked issue. The default-location path list is a curated allow-list of common
toolchain cache/registry locations; the read/write split and credential-safe
scoping are documented inline in
mxc_engine::policy.🔍 Validation
cargo fmt --all -- --check— clean.cargo clippy -p mxc_engine -p mxc-sdk --all-targets -- -D warnings— clean.cargo test -p mxc_engine -p mxc-sdk— all pass, including new tests for theplatform path shapes (macOS
Library/Cachesvs Linux XDG), the read/writeclassification (gradle caches +
wrapper/dists,go-build), the credentialexclusion /
~/.dotnetnarrowing /CARGO_HOMEscoping, shimlibexecgrants, the trusted-home containment guard, the
allow_dev_tool_cacheson/off cases, and the
materialize_tool_cache_writesdir-vs-file creation.✅ Checklist
Cargo.lock, thedependency-feed-checkcheck passes (no dependency changes)📋 Issue Type
Microsoft Reviewers: Open in CodeFlow