Investigation Summary
shieldsDown builds the merged permissive policy into a 0700 permissive runtime temp directory, but only the policy-apply try/finally removes it.
- Two early exits sit between the build and that apply, and both skip cleanup: the auto-restore timer failure and the
saveShieldsState failure.
- The merge runs on effectively every real
shields down, because buildRuntimePermissivePolicy only returns the static base path when the live policy has no filesystem_policy.read_only and no read_write entries. A real openshell policy get --base always has both.
- Reproduced with a test against
main at 37f6a8a65: after a failed transition, nemoclaw-permissive-runtime-76WB4a is still in the system temp directory.
- The leak is a directory, not a single file, because
secureTempFile creates the directory with fs.mkdtempSync.
Description
nemoclaw <sandbox> shields down leaks the permissive runtime temp directory into the system temp directory when the transition fails partway through.
In src/lib/shields/index.ts, policyFile is created by buildRuntimePermissivePolicy(...) at line 2665 and policyFileIsTemp is set at line 2669. The only cleanup is the finally that wraps the apply:
try {
run(buildPolicySetCommand(policyFile, sandboxName));
} finally {
if (policyFileIsTemp) {
cleanupTempDir(policyFile, "nemoclaw-permissive-runtime");
}
}
Between the creation at line 2665 and that try there are two early exits that never reach the finally:
- The auto-restore timer block returns on a fork or authorize failure (around line 2768):
return failShieldsCommand("Cannot start auto-restore timer: ...", opts.throwOnError)
- The
saveShieldsState catch rethrows (around line 2786): throw error
Expected: a failed shields down leaves nothing behind in the system temp directory.
Actual: a 0700 nemoclaw-permissive-runtime-* directory holding the merged policy YAML stays there. It accumulates one directory per failed attempt.
The contents are a permissive policy body, not credentials, so the impact is temp-directory growth and a world-readable-parent directory holding a relaxed policy document rather than a secret leak.
Reproduction Steps
Source-level reproduction, no sandbox required. Against main at 37f6a8a65:
- In
src/lib/shields/flow.test.ts, make createHarness return a live policy that carries filesystem paths, so the merge produces the permissive runtime temp directory instead of returning the static base path. The current harness returns version: 1\nnetwork_policies:\n test: {}\n, which has no filesystem_policy, so the merge path never runs in tests today.
- Add a test that drives the timer early exit with a
fork override returning pid: 0, then asserts the system temp directory is empty.
- Run
npx vitest run --project cli src/lib/shields/flow.test.ts.
The assertion fails with a leftover directory:
AssertionError: expected [ Array(1) ] to deeply equal []
- []
+ [
+ "nemoclaw-permissive-runtime-76WB4a",
+ ]
The same leak is reachable through the saveShieldsState failure path.
Environment
- OS: macOS 26.5.2 (Darwin 25.5.0)
- Hardware: arm64 Mac
- Node.js: v22.22.3
- Docker: not required for this reproduction
- NemoClaw:
nemoclaw v0.0.98-32-gdfe71a06b, source checkout at main 37f6a8a65
The defect is in CLI source and is not platform-specific.
Debug Output
Not applicable. This is a source-level defect in the shields down transition, found by reading src/lib/shields/index.ts and reproduced with a repository test rather than a live sandbox run. nemoclaw debug output would not show the leaked directory, since the leak is in the host system temp directory and is not collected by the debug bundle.
Logs
The failing assertion above is the whole signal. The command itself prints its normal failure message, for example Cannot start auto-restore timer: auto-restore timer did not report a process id, and exits without removing the permissive runtime temp directory.
Investigation Summary
shieldsDownbuilds the merged permissive policy into a 0700 permissive runtime temp directory, but only the policy-applytry/finallyremoves it.saveShieldsStatefailure.shields down, becausebuildRuntimePermissivePolicyonly returns the static base path when the live policy has nofilesystem_policy.read_onlyand noread_writeentries. A realopenshell policy get --basealways has both.mainat37f6a8a65: after a failed transition,nemoclaw-permissive-runtime-76WB4ais still in the system temp directory.secureTempFilecreates the directory withfs.mkdtempSync.Description
nemoclaw <sandbox> shields downleaks the permissive runtime temp directory into the system temp directory when the transition fails partway through.In
src/lib/shields/index.ts,policyFileis created bybuildRuntimePermissivePolicy(...)at line 2665 andpolicyFileIsTempis set at line 2669. The only cleanup is thefinallythat wraps the apply:Between the creation at line 2665 and that
trythere are two early exits that never reach thefinally:return failShieldsCommand("Cannot start auto-restore timer: ...", opts.throwOnError)saveShieldsStatecatch rethrows (around line 2786):throw errorExpected: a failed
shields downleaves nothing behind in the system temp directory.Actual: a 0700
nemoclaw-permissive-runtime-*directory holding the merged policy YAML stays there. It accumulates one directory per failed attempt.The contents are a permissive policy body, not credentials, so the impact is temp-directory growth and a world-readable-parent directory holding a relaxed policy document rather than a secret leak.
Reproduction Steps
Source-level reproduction, no sandbox required. Against
mainat37f6a8a65:src/lib/shields/flow.test.ts, makecreateHarnessreturn a live policy that carries filesystem paths, so the merge produces the permissive runtime temp directory instead of returning the static base path. The current harness returnsversion: 1\nnetwork_policies:\n test: {}\n, which has nofilesystem_policy, so the merge path never runs in tests today.forkoverride returningpid: 0, then asserts the system temp directory is empty.npx vitest run --project cli src/lib/shields/flow.test.ts.The assertion fails with a leftover directory:
The same leak is reachable through the
saveShieldsStatefailure path.Environment
nemoclaw v0.0.98-32-gdfe71a06b, source checkout atmain37f6a8a65The defect is in CLI source and is not platform-specific.
Debug Output
Not applicable. This is a source-level defect in the
shields downtransition, found by readingsrc/lib/shields/index.tsand reproduced with a repository test rather than a live sandbox run.nemoclaw debugoutput would not show the leaked directory, since the leak is in the host system temp directory and is not collected by the debug bundle.Logs
The failing assertion above is the whole signal. The command itself prints its normal failure message, for example
Cannot start auto-restore timer: auto-restore timer did not report a process id, and exits without removing the permissive runtime temp directory.