Pre-release Harn is pre-1.0 — the language, standard library, and CLI may change between releases. See the release notes

Toolchain config and cache reference

The package_manager_config preset admits named tool paths under ~/.config and ~/.cache. Other siblings aren't readable unless you grant them explicitly. The credential denylist overrides every grant.

The owning list is package_manager_config_read_roots_for_home in crates/harn-vm/src/stdlib/sandbox/read_roots.rs. Linux and macOS use that same list. Windows doesn't confine child processes; these roots don't provide a security boundary there.

Measured paths#

These Linux probes ran on September 30, 2026, with synthetic configuration and networking disabled. strace recorded filesystem calls inside a Bubblewrap sandbox. Each command ran with readable XDG directories, then with both directories inaccessible. A planted file succeeded in the readable control and returned EACCES in the refused control.

Paths below are home-relative. “None” means the trace recorded no config/cache access in that command, not that every command the tool offers ignores them. Cache rows name the containing directory; the trace includes both reads and writes. Refusal results describe these fixtures and installed versions.

Tool and commandConfig paths under .config/Cache paths under .cache/Result with both directories refused
Git status --porcelaingit/config, git/ignoreNoneUses defaults; succeeds
Cargo check --offline, Clippy --offlinegit/configNoneUses defaults; succeeds
rustfmt --check src/main.rsrustfmt/.rustfmt.toml, rustfmt/rustfmt.tomlNoneFails on config metadata lookup
npm run check --offlineNoneNoneSucceeds
pnpm run checkpnpm/rcNoneUses defaults; succeeds
Yarn run checkyarn/config, yarn/linkyarnFails opening config
Corepack pnpm/Yarn shimsNonenode/corepack/lastKnownGood.jsonBoth baselines need an installed package-manager distribution; refusal returns EACCES
Bun run checkNoneNoneSucceeds
pip list --disable-pip-version-checkpip/pip.confpipUses defaults and disables cache; succeeds
uv pip list --python /usr/bin/python3uv/uv.tomluvFails initializing cache
Go test ./goproject/...go/env, go/telemetry/*go-buildFails initializing cache
GCC and Clang compile a C fileNoneNoneSucceed
CMake configurecmake/api/v1/query, cmake/instrumentation-*/v1/queryNoneIgnores unavailable queries; succeeds
Ninja and Make buildNoneNoneSucceed
Java javac Main.javaNoneNoneSucceeds
Gradle --offline --no-daemon tasksNoneNoneSucceeds
Maven --offline validateNoneNoneSucceeds
sbt --server -Dsbt.offline=true aboutsbt/sbtopts, sbt/preloaded/*; Coursier probes coursier/credentials, coursier/credentials.properties, coursier/mirror.propertiessbt, coursier, JNACannot read its cached launcher; fails
.NET restore with workspace NuGet.Config, then build without restoreNone required by the workspace configNoneSucceeds after first-use setup; see limitations below
Ruby syntax check and Bundler checkNoneNoneSucceed
RubyGems list --localgem/gemrcNoneUses defaults; succeeds
Composer validate --no-check-publishcomposer/config.json, composer/composer.json, composer/auth.json, git/configcomposerUses defaults; succeeds
Swift buildswiftpm/configuration/{mirrors,registries}.json, swiftpm/security; probes swiftpm/{cache,config}, git/configclang, org.swift.swiftpm, org.swift.foundation.URLCacheFails compiling the manifest without its module cache
Julia offline Pkg.status()NoneNoneSucceeds with an empty local registry
mise ls --offlinemise/{config.toml,config.local.toml,mise.toml,mise.local.toml,miserc.toml,conf.d,trusted-configs}miseUses defaults; succeeds
Ruff check main.pyruff/{.ruff.toml,ruff.toml,pyproject.toml}, git/{config,ignore}NoneUses defaults; succeeds
Black --check main.pyblackblackUses defaults and skips cache; succeeds
Prettier --check main.js, ESLint main.jsNoneNoneSucceed

Grant boundaries#

The preset grants the measured tool config directories, plus the single coursier/mirror.properties file and swiftpm/configuration directory. Coursier credential files and SwiftPM's security directory aren't admitted. Composer's auth.json stays denied inside its admitted config directory.

Measured caches receive tool-specific read roots. go-build and harn are already writable roots owned by developer_toolchains, so the package-manager list doesn't repeat them. On macOS a later read-only rule would cancel their write grant.

Workspace toolchain environment already redirects writable caches into the workspace. A home-cache refusal in this census therefore doesn't imply that the corresponding Harn build fails. The census deliberately measures default paths before that relocation.

Reproduction and limits#

scripts/sandbox_config_census.harn contains the commands, fixtures, controls, and trace parser. It requires Linux, bwrap, and strace. Its JSON report records every attempted config path and cache directory, syscall outcomes, exit status, unavailable tools, and bounded failure diagnostics. It doesn't save successful tool output or read your home configuration. Java's user.home names the synthetic home; Rust uses the installed toolchain with a private Cargo home.

harn run --no-sandbox scripts/sandbox_config_census.harn -- /tmp/config-census.json

The census isn't a network-installation test. sbt needs preinstalled launcher and boot artifacts; Julia uses an empty registry. Temporary tool installations can be exposed with the second script argument. The third argument names a home directory containing sbt's boot, launcher, and Coursier artifacts; only those artifact directories are copied. .NET's cold first-use restore timed out in the initial probes; its subsequent refused-directory build succeeded. That timeout doesn't prove config refusal was its cause.

The paths were traced on Linux. The live canonical CLI regression also checks Linux and macOS: admitted config/cache reads succeed, unlisted siblings fail, and explicit parent grants restore only the unlisted reads. macOS tool defaults under ~/Library aren't changed by this narrowing.

The September 30 evidence records 70 terminal commands, zero pending commands, the expected refused reads, and the .NET timeout. Corepack's readable baselines lacked a cached distribution; their traces still recorded node/corepack/lastKnownGood.json reads.