Skip to content

fix(shields): union live policy into permissive before applying#3976

Open
laitingsheng wants to merge 3 commits into
mainfrom
fix/shields-down-runtime-superset-3942
Open

fix(shields): union live policy into permissive before applying#3976
laitingsheng wants to merge 3 commits into
mainfrom
fix/shields-down-runtime-superset-3942

Conversation

@laitingsheng
Copy link
Copy Markdown
Contributor

@laitingsheng laitingsheng commented May 21, 2026

Summary

nemoclaw <name> shields down fails on GPU sandboxes with:

status: InvalidArgument, message: "filesystem read_write path '/proc' cannot be removed on a live sandbox"

OpenShell refuses to remove a filesystem_policy.read_only or filesystem_policy.read_write entry on a live sandbox. The static openclaw-sandbox-permissive.yaml baseline never sees the runtime enrichment that NemoClaw adds at create time, so applying it on top of a live sandbox triggers the rejection whenever a runtime-injected path is missing from the static YAML.

This has reappeared with each new runtime-injected path:

Close the loop with a generic helper. Before shields down applies the permissive policy, fetch the live sandbox's policy via the snapshot we already capture and union its filesystem_policy.read_write and filesystem_policy.read_only into the base permissive YAML. The result has filesystem path lists that are a superset of the live filesystem section, so OpenShell never sees a removal on the shields down transition. Future runtime-injected paths are absorbed automatically on the shields down path. Other permissive-apply surfaces (applyPermissivePolicy in src/lib/policy/index.ts — currently unused by any in-source caller — and the E2E scripts that read the static YAML directly) are unchanged and continue to rely on the static file being correct; the earlier whack-a-mole fixes for #3168 and #3957 are kept for those paths.

Related Issue

Partially addresses #3942 - Part 2
Closes #3957

Changes

  • src/lib/shields/permissive-runtime.ts: new helper buildRuntimePermissivePolicy(basePath, deps). Takes the already-parsed live policy YAML body (from the caller's parseCurrentPolicy(rawPolicy)) and a lazy base-policy reader as injected deps. Unions filesystem_policy.read_only + filesystem_policy.read_write into the static base. Other filesystem_policy fields (e.g. include_workdir) are preserved verbatim. RW wins on path overlap. Returns the static base path verbatim when (a) the live YAML has no filesystem path lists, (b) the base YAML cannot be parsed, or (c) base-read / temp-file I/O throws — so an I/O fault degrades to the existing static apply path rather than aborting shields-down. If secureTempFile already created its mkdtemp directory before the subsequent writeFileSync throws, the directory is cleaned up via cleanupTempDir so the failure path never leaks 0700 dirs under /tmp. A writeTempPolicy dep is exposed so tests can drive the write-failure branch without monkey-patching node:fs.
  • src/lib/shields/index.ts: wire buildRuntimePermissivePolicy in. Reuse the already-captured policyYaml (parsed via parseCurrentPolicy(rawPolicy)) so there is no duplicate parser. Wrap the openshell policy set call in try / finally so the temp file's mkdtemp directory is cleaned up via cleanupTempDir.
  • test/permissive-runtime.test.ts: 9 regression tests covering /proc preservation on GPU sandboxes, include_workdir passthrough, dedup across lists, the RW-wins rule on conflicts, and five degraded paths (empty live YAML, no filesystem section, readBasePolicy throws, base YAML unparseable, writeTempPolicy throws) returning the static base path. Cleanup helper rejects any output that is not under os.tmpdir() to avoid an accidental rm -rf on the user's checkout when the helper degrades to the static base path.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Verification

  • npx prek run --all-files passes
  • npm test passes
  • Tests added or updated for new or changed behavior
  • No secrets, API keys, or credentials committed
  • Docs updated for user-facing behavior changes
  • make docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Tinson Lai tinsonl@nvidia.com

Summary by CodeRabbit

  • New Features
    • Dynamic runtime generation of permissive network policies based on live system configurations, enabling more responsive policy enforcement aligned with current system state
    • Automatic cleanup of temporary policy artifacts to improve system resource management and hygiene

Review Change Stack

OpenShell refuses to remove a `filesystem_policy.read_only` or
`filesystem_policy.read_write` entry on a live sandbox. The static
`openclaw-sandbox-permissive.yaml` baseline never sees the runtime
enrichment that NemoClaw adds at create time, so applying it on top of
a live sandbox can hit "filesystem read_write path '<X>' cannot be
removed on a live sandbox".

This has reappeared with each new runtime-injected path:

  - #3168 — Hermes `/opt/hermes` (fixed by per-agent permissive YAML)
  - #3957 — OpenClaw `/home/linuxbrew` (fixed by patching the YAML)
  - #3942 — GPU sandboxes `/proc` (the current bug)

Close the loop with a generic helper. Before `shields down` applies the
permissive policy, fetch the live sandbox's policy via the snapshot we
already capture and union its `filesystem_policy.read_write` and
`filesystem_policy.read_only` into the base permissive YAML. The result
is a strict superset of the live filesystem section, so OpenShell never
sees a removal on transition.

Resolution rules on overlap: a path that is `read_write` on the live
side wins and is removed from `read_only` in the output — we never emit
the same path in both lists, and we never downgrade a writable path to
read-only as part of an unrelated transition.

The helper takes its live-policy fetcher and base-policy reader as
injected deps so it stays a pure function with no runtime cycle into
the policy module, and so the regression test can drive it without
mocking node_modules.

When the live policy is empty, parses as an error, or omits the
filesystem section, the helper degrades to the existing static path —
matches prior behaviour rather than failing closed.

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: ac25dfe3-0562-4d92-aba9-45205921801a

📥 Commits

Reviewing files that changed from the base of the PR and between 73a4fe5 and ac6b035.

📒 Files selected for processing (2)
  • src/lib/shields/permissive-runtime.ts
  • test/permissive-runtime.test.ts

📝 Walkthrough

Walkthrough

Adds a runtime permissive policy builder that merges live sandbox filesystem_policy lists into a base permissive YAML, uses it during shieldsDown when policyName is "permissive", and ensures any temporary merged policy file is cleaned up after applying the policy.

Changes

Runtime permissive policy merging

Layer / File(s) Summary
Runtime permissive policy builder and parsing helpers
src/lib/shields/permissive-runtime.ts
buildRuntimePermissivePolicy parses live and base YAML, merges filesystem_policy.read_write and read_only with read-write precedence and deduplication, writes the merged policy to a secured temp file (mode 0o600) or uses injected writeTempPolicy, and returns the temp path or the original base path on fallback. Includes PermissiveRuntimeDeps, safeYamlObject, and readStringList helpers.
Shields-down integration and temporary cleanup
src/lib/shields/index.ts
When policyName === "permissive", shieldsDown calls buildRuntimePermissivePolicy with the captured live policy YAML and a base-reader callback, tracks if the returned policy file is temporary, applies the policy inside a try/finally, and calls cleanupTempDir(...) when cleanup is required.
Merge semantics validation
test/permissive-runtime.test.ts
Vitest suite validates RW precedence, preservation of non-list fields, correct merging of live read_only, deduplication across lists, and fallback cases (empty live policy, missing filesystem_policy, readBasePolicy error, unparseable base, and temp-write failure).

Sequence Diagram

sequenceDiagram
  participant shieldsDown
  participant buildRuntimePermissivePolicy
  participant readBasePolicy
  participant secureTempFile
  participant run_apply
  participant cleanupTempDir

  shieldsDown->>buildRuntimePermissivePolicy: call(basePermissivePath, deps)
  buildRuntimePermissivePolicy->>readBasePolicy: read base YAML
  readBasePolicy-->>buildRuntimePermissivePolicy: base YAML
  buildRuntimePermissivePolicy->>secureTempFile: write merged YAML -> temp path
  secureTempFile-->>buildRuntimePermissivePolicy: temp path
  buildRuntimePermissivePolicy-->>shieldsDown: return policyFile
  shieldsDown->>run_apply: run(buildPolicySetCommand(policyFile, sandboxName))
  run_apply-->>shieldsDown: apply result
  shieldsDown->>cleanupTempDir: (finally) cleanupTempDir(policyFile) if temp
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • NVIDIA/NemoClaw#3169: Also modifies permissive-policy resolution in shieldsDown and interacts with this runtime merge flow.

Suggested labels

Sandbox

Suggested reviewers

  • ericksoa

Poem

🐰 In burrows where the policies play,
I stitched the live paths into day.
No /proc removed, no sandbox mourns,
Temp file tidied at the morn—
Hooray, the shields go down with cheer!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(shields): union live policy into permissive before applying' directly and concisely describes the main change: unioning live policy into permissive policy before applying it, which is the core fix addressing the shields-down issue.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/shields-down-runtime-superset-3942

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

E2E Advisor Recommendation

Required E2E: shields-config-e2e
Optional E2E: network-policy-e2e, gpu-e2e

Dispatch hint: shields-config-e2e

Auto-dispatched E2E: shields-config-e2e via nightly-e2e.yaml at ac6b0355861b8424777d73d0f657b3200108de04nightly run

Workflow run

Full advisor summary

E2E Recommendation Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E

  • shields-config-e2e (medium): This is the closest existing live coverage for the changed path: it installs/onboards a sandbox, runs nemoclaw shields up, then nemoclaw shields down --timeout ..., and verifies config/workspace mutability restoration, shields status, audit entries, auto-restore, and double down/up rejection. The PR changes the policy file used inside shields down, so this should be merge-blocking.

Optional E2E

  • network-policy-e2e (medium): Useful adjacent confidence for permissive/restricted policy behavior and live policy hot-reload. It does not directly exercise the new shields down runtime-union helper because its permissive step applies the static policy via openshell policy set, so it is optional rather than required.
  • gpu-e2e (high): The code comments call out GPU runtime-injected /proc filesystem policy entries. Existing GPU E2E validates GPU onboarding and /proc GPU proof behavior, but it does not run shields up/down, so it is only an optional signal for the affected runtime environment.

New E2E recommendations

  • runtime-injected filesystem policy during shields down (high): Existing shields-config-e2e exercises shields down on a normal OpenClaw sandbox, but there is no dedicated live test that first ensures runtime-injected filesystem entries such as GPU /proc, Hermes /opt/hermes, or /home/linuxbrew are present in the live policy and then verifies nemoclaw shields down applies a generated permissive policy without OpenShell rejecting path removal.
    • Suggested test: Add a live shields-down runtime-policy union E2E that captures the current policy, confirms an injected filesystem path is present, runs nemoclaw <sandbox> shields down, and asserts policy apply succeeds and the path remains in the resulting live filesystem policy with read_write taking precedence over read_only.
  • Hermes shields policy transition (medium): The comments identify Hermes /opt/hermes as a runtime-injected path, but existing Hermes E2E jobs focus on onboarding/inference/messaging and do not validate shields up/down policy transition behavior.
    • Suggested test: Add or extend a Hermes E2E to run shields up/down on a Hermes sandbox and verify the live filesystem policy retains /opt/hermes across the permissive transition.

Dispatch hint

  • Workflow: nightly-e2e.yaml
  • jobs input: shields-config-e2e

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

PR Review Advisor

Recommendation: blocked
Confidence: high
Analyzed HEAD: ac6b0355861b8424777d73d0f657b3200108de04
Findings: 3 blocker(s), 3 warning(s), 0 suggestion(s)

This is an automated advisory review. A human maintainer must make the final merge decision.

Limitations: Review used provided deterministic GitHub context and the supplied diff; tests, builds, package-manager commands, PR scripts, and live sandboxes were not executed by this advisory review.; CI was still pending for the current head SHA at review time, so final check outcomes are unknown.; Literal #3942 issue body/comments were not included in the trusted linkedIssues payload; #3942 acceptance mapping is limited to the PR body's untrusted statement.; The diff in the prompt is truncated if large, though all three declared changed files are represented.; Open PR overlaps on src/lib/shields/index.ts were identified from provided metadata but not resolved here.

Workflow run

Full advisor summary

PR Review Advisor

Base: origin/main
Head: HEAD
Analyzed SHA: ac6b0355861b8424777d73d0f657b3200108de04
Recommendation: blocked
Confidence: high

Runtime permissive-policy union looks directionally correct and the required shields-config E2E passed for this head SHA, but merge remains blocked by pending CI, GitHub mergeStateStatus=BLOCKED/review required, and the shields monolith growth blocker.

Gate status

  • CI: pending — GraphQL statusCheckRollup for ac6b035 still shows 12 pending/in-progress/queued contexts, including cli-parity, E2E recommendation, wsl-e2e, PR review advisor, CodeQL, unit-vitest-linux, checks, ShellCheck SARIF, build-sandbox-images, build-sandbox-images-arm64, and CodeRabbit.
  • Mergeability: fail — GitHub reports mergeStateStatus=BLOCKED and reviewDecision=REVIEW_REQUIRED for PR fix(shields): union live policy into permissive before applying #3976 at head ac6b035.
  • Review threads: pass — GraphQL reports 2 review thread(s), both resolved; the CodeRabbit I/O fallback and test cleanup concerns are marked addressed/resolved.
  • Risky code tested: pass — Path heuristic rollup reports no risky code areas detected; additionally, the E2E Advisor required shields-config-e2e for this runtime shields path and selective E2E evidence shows shields-config-e2e passed for ac6b035.

🔴 Blockers

  • Required CI is not complete for the current head SHA: The PR still has pending/in-progress/queued checks for the latest head SHA, including unit, static analysis, advisory, E2E recommendation, and image build contexts. This is a hard gate independent of local diff quality.
    • Recommendation: Wait for all required CI and analysis checks to complete successfully for ac6b035 before merge consideration.
    • Evidence: statusCheckRollup includes IN_PROGRESS/QUEUED/PENDING contexts: cli-parity, E2E recommendation, wsl-e2e, PR review advisor, CodeQL javascript-typescript, CodeQL python, unit-vitest-linux, checks, ShellCheck SARIF, build-sandbox-images, build-sandbox-images-arm64, and CodeRabbit.
  • PR is currently merge-blocked and still requires review: GitHub branch protection/mergeability state is not satisfied for this pull request.
  • Large shields monolith grew past configured budget (src/lib/shields/index.ts:1): The active large-file hotspot src/lib/shields/index.ts grew by 23 lines, exceeding the configured monolith growth threshold of 20 or more lines.
    • Recommendation: Extract more orchestration into helper modules or offset the growth in src/lib/shields/index.ts before merge.
    • Evidence: monolithDeltas reports src/lib/shields/index.ts baseLines=1273, headLines=1296, delta=23, severity=blocker, rationale='Current monolith grew by 20 or more lines; extract or offset the growth before merge.'

🟡 Warnings

🔵 Suggestions

  • None.

Acceptance coverage

  • unknown — Partially addresses [Nemoclaw][All Platforms] [Policy&Network]/nemoclaw shields unavailable in sandbox and shields down fails with /proc policy error #3942 - Part 2: This clause appears in the PR body, but the trusted linked issue payload does not include [Nemoclaw][All Platforms] [Policy&Network]/nemoclaw shields unavailable in sandbox and shields down fails with /proc policy error #3942 body or comments. The diff changes host shields-down permissive policy application, but literal [Nemoclaw][All Platforms] [Policy&Network]/nemoclaw shields unavailable in sandbox and shields down fails with /proc policy error #3942 acceptance cannot be mapped from trusted issue text here.
  • partial — Closes nightly-e2e: shields-config-e2e + network-policy-e2e fail — permissive policy missing /home/linuxbrew #3957: The helper generically unions live filesystem_policy.read_write/read_only entries into the permissive baseline before shields down applies it, addressing the class of nightly-e2e: shields-config-e2e + network-policy-e2e fail — permissive policy missing /home/linuxbrew #3957 live-policy removal errors. However, nightly-e2e: shields-config-e2e + network-policy-e2e fail — permissive policy missing /home/linuxbrew #3957 also states the direct static YAML fix already landed on main, and CI is still pending for this PR head.
  • partial — The shields-config-e2e (12 sub-test failures) and network-policy-e2e (1 sub-test failure — TC-NET-06) jobs in nightly run 26197765497 both failed with:: E2E Advisor required shields-config-e2e and that job passed for ac6b035. network-policy-e2e passed only for an earlier commit and is optional per the current E2E Advisor comment.
  • partial — Error: × status: InvalidArgument, message: "filesystem read_write path '/home/linuxbrew' cannot be removed on a live sandbox": buildRuntimePermissivePolicy preserves live read_write entries by adding them to the generated permissive policy. Unit coverage includes /home/linuxbrew in the GPU /proc regression test; shields-config-e2e passed for the head SHA, but no current-head network-policy-e2e evidence is required or present.
  • partial — This occurs whenever shields down or the permissive-policy test applies openclaw-sandbox-permissive.yaml to a live sandbox.: src/lib/shields/index.ts now calls buildRuntimePermissivePolicy when policyName is permissive before buildPolicySetCommand(policyFile, sandboxName), covering shields down. Other permissive-apply surfaces that read the static YAML directly are not changed in this PR.
  • met — The default policy (openclaw-sandbox.yaml) added /home/linuxbrew to its read_write section in PR [Sandbox] brew policy preset cannot bootstrap Homebrew: sandbox lacks filesystem write to /home/linuxbrew #3913/feat(sandbox): bake Homebrew core into the sandbox base image (#3913) #3916 (commit 4135038), but the permissive policy was not updated in the same change.: The PR does not hard-code only /home/linuxbrew; it reads the captured live policy and unions any live read_write/read_only paths into the permissive YAML at runtime, so /home/linuxbrew is preserved if present in the live policy.
  • partial — OpenShell rejects the transition because removing a read_write filesystem path on a running sandbox is forbidden.: The generated policy is intended to be a superset of live read_write/read_only lists, avoiding removal. shields-config-e2e passed for the current head SHA, but targeted live coverage for synthetic runtime-injected entries remains only recommended follow-up.
  • partial — Add - /home/linuxbrew to the read_write section of nemoclaw-blueprint/policies/openclaw-sandbox-permissive.yaml (and agents/openclaw/policy-permissive.yaml) so it mirrors the default policy.: This PR does not modify those YAML files. It implements a more general runtime union instead, while the issue body says the static YAML fix already landed on main.
  • unknownThis fix has already landed on main as commit 60d3e5e — "fix(policy): preserve Homebrew in permissive OpenClaw policies". No additional PR is needed; this issue is filed for nightly-failure tracking only. The next scheduled nightly on main should pass these jobs.: The provided context does not include the next scheduled main nightly result. This PR adds a broader runtime-superset helper beyond the already-landed static main fix.
  • partial — None — the permissive policy must be a strict superset of the default policy's filesystem section for live transitions to work.: The helper makes the generated permissive policy a superset of the live sandbox filesystem path lists, not necessarily a full structural mirror of the default policy. This is aligned with the live transition failure mode for read_only/read_write removals.
  • partial — 1. Re-run shields-config-e2e or network-policy-e2e on commit f19061e via gh workflow run nightly-e2e.yaml --repo NVIDIA/NemoClaw --ref f19061ec9b79d019afdb274786ff0211182dc8df -f jobs=shields-config-e2e,network-policy-e2e.: Selective E2E evidence shows shields-config-e2e passed for PR head ac6b035, and both shields-config-e2e/network-policy-e2e passed for earlier PR commit 7afa33f. The exact f19061e rerun is not shown.
  • unknown — OS: Ubuntu 24.04.4 LTS (GitHub-hosted runner ubuntu-latest): Environment information from nightly-e2e: shields-config-e2e + network-policy-e2e fail — permissive policy missing /home/linuxbrew #3957 is historical context; the provided current selective E2E comments do not restate runner OS details.
  • unknown — Node.js: v22.22.3: Environment information from nightly-e2e: shields-config-e2e + network-policy-e2e fail — permissive policy missing /home/linuxbrew #3957 is historical context; current CI details in the trusted context do not confirm this exact Node.js version.
  • unknown — Docker: Docker Engine on GitHub Actions: Environment information from nightly-e2e: shields-config-e2e + network-policy-e2e fail — permissive policy missing /home/linuxbrew #3957 is historical context; current CI details in the trusted context do not confirm Docker runtime details.
  • unknown — NemoClaw: f19061ec9b79d019afdb274786ff0211182dc8df (main): This PR head is ac6b035, not f19061e.
  • unknown — Other: Workflow run 26197765497: The current review context includes newer workflow runs 26210119841 and 26211195274, but does not include detailed logs for the historical run 26197765497.
  • partial — All failures cascade from the same InvalidArgument on openshell policy set with the permissive policy.: The code path now builds a runtime permissive policy before openshell policy set for shields down. This should prevent the same class of path-removal InvalidArgument in that path, with shields-config-e2e passing for the current head.
  • partial — Phases affected: shields down (Phase 6), shields status (Phase 7), audit trail (Phase 8), auto-restore timer (Phase 9), double shields-down (Phase 11), and TC-NET-06 (permissive mode).: shields-config-e2e passed for the current head and covers shields lifecycle behavior. The current E2E Advisor did not require network-policy-e2e/TC-NET-06 for this head, and no current-head TC-NET-06 evidence is present.
  • unknown — - [x] I confirmed this bug is reproducible (required): This is an issue-report checklist assertion; the current review did not reproduce the bug or execute commands.
  • unknown — - [x] I searched existing issues and this is not a duplicate (required): This is an issue-report checklist assertion; duplicate triage is outside the provided diff evidence.

Security review

  • pass — 1. Secrets and Credentials: No hardcoded secrets, API keys, passwords, PEM files, .env files, credential JSON, or connection strings are introduced. Policy snapshots/temp files are written with restrictive modes such as 0o600.
  • pass — 2. Input Validation and Data Sanitization: sandboxName remains validated with validateName before shieldsDown operations. Policy commands are built as argv arrays through buildPolicySetCommand/buildPolicyGetCommand rather than shell-string concatenation. The helper parses YAML safely with YAML.parse in try/catch and only copies string entries from filesystem_policy.read_only/read_write.
  • pass — 3. Authentication and Authorization: No new endpoints, auth flows, token validation, or authorization paths are introduced. The surrounding invariant that shields changes are host-initiated remains intact.
  • pass — 4. Dependencies and Third-Party Libraries: No new third-party dependency is added; the change uses existing yaml and Node fs modules plus existing secureTempFile/cleanupTempDir infrastructure.
  • warning — 5. Error Handling and Logging: The helper now degrades safely to the static permissive path on base-read, parse, and temp-write failures and cleans up temp dirs after write failures. However, fallback is silent, which can obscure the root cause if OpenShell later rejects the static policy.
  • pass — 6. Cryptography and Data Protection: No new cryptographic operations are introduced. Existing randomBytes use for auto-restore process tokens is unchanged.
  • warning — 7. Configuration and Security Headers: The change intentionally broadens the generated permissive filesystem policy to preserve live read_only/read_write entries. This is the intended compatibility fix for OpenShell live-policy constraints, but it modifies sandbox policy configuration and should remain covered by E2E for current head; required shields-config-e2e passed, while optional GPU/Hermes targeted lifecycle coverage remains recommended.
  • warning — 8. Security Testing: Unit tests cover /proc preservation, /home/linuxbrew preservation in read_write, include_workdir passthrough, deduplication, RW-wins behavior, and degraded fallback paths. The required shields-config-e2e passed for this head, but current CI unit/static checks are still pending and targeted GPU/Hermes runtime-injected path E2Es are recommended follow-up.
  • warning — 9. Holistic Security Posture: The design improves live sandbox stability, avoids command injection patterns, reuses the already captured policy snapshot, uses secure temp files, and cleans temporary policy directories. Overall posture cannot be considered clean until pending CI completes and the merge-blocked/review-required state is cleared.

Test / E2E status

  • Test depth: e2e_required — Runtime/sandbox/infrastructure paths need real execution coverage: src/lib/shields/index.ts and src/lib/shields/permissive-runtime.ts. Unit tests cover YAML merge semantics and fallback behavior; required shields-config-e2e passed for the current head, but pending CI still blocks merge and targeted GPU/Hermes runtime-injected lifecycle coverage remains a useful follow-up.
  • E2E Advisor: ok
  • Required E2E jobs: shields-config-e2e

✅ What looks good

  • The runtime merge logic is isolated in a new src/lib/shields/permissive-runtime.ts helper instead of placing all YAML manipulation directly in the shields monolith.
  • The helper preserves non-list filesystem_policy fields from the base policy, deduplicates entries, and implements an explicit read_write-wins rule so a path is not emitted in both read_only and read_write.
  • The production path reuses the already captured policyYaml snapshot rather than issuing another live policy fetch, reducing race surface and extra OpenShell calls.
  • Temporary policy files are created through secureTempFile and written with mode 0o600; policy application uses try/finally cleanup for generated temp files.
  • The latest commits address prior review feedback: local I/O failures degrade to the static policy, temp directories are cleaned after write failures, and tests avoid deleting fallback base-path parents.
  • Regression tests cover the named GPU /proc case, /home/linuxbrew preservation, include_workdir preservation, deduplication, RW-wins conflict handling, and degraded fallback cases.

Review completeness

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/shields/permissive-runtime.ts`:
- Around line 59-60: Wrap the real filesystem boundaries in try/catch so any I/O
failure degrades to the static permissive policy instead of aborting:
specifically, guard calls to readBasePolicy(), secureTempFile(), and
writeFileSync() (and the block that uses safeYamlObject(baseYaml)) with error
handling that logs the exception and returns/uses the existing static permissive
policy path; do not catch internal helper logic—only catch around those I/O
calls and ensure the function returns the static fallback when an I/O error
occurs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 213077a6-95e7-4a3b-9dc1-ac8221c26fc8

📥 Commits

Reviewing files that changed from the base of the PR and between 18c7265 and 7afa33f.

📒 Files selected for processing (3)
  • src/lib/shields/index.ts
  • src/lib/shields/permissive-runtime.ts
  • test/permissive-runtime.test.ts

Comment thread src/lib/shields/permissive-runtime.ts Outdated
@github-actions
Copy link
Copy Markdown
Contributor

Selective E2E Results — ✅ All requested jobs passed

Run: 26210119841
Target ref: 7afa33f0c2fde7e8130a80629534700f61eaec1c
Workflow ref: main
Requested jobs: shields-config-e2e,network-policy-e2e
Summary: 2 passed, 0 failed, 0 skipped

Job Result
network-policy-e2e ✅ success
shields-config-e2e ✅ success

* Drop the local parsePolicyBlock clone: callers already invoke
  parseCurrentPolicy on the raw `openshell policy get` output, so the
  helper now takes the pre-parsed YAML body via `livePolicyYaml`
  instead of duplicating the header-stripping regex. shields/index.ts
  passes the `policyYaml` it already captured for the snapshot.

* Wrap the boundary I/O — base-policy read, temp-file create, write —
  in try/catch. On any I/O failure the helper returns the static base
  path so shields-down keeps degrading to the existing apply path
  rather than aborting before the policy set ever runs.

* Reword the doc comment: the helper unions filesystem path lists
  (read_only + read_write), not the entire filesystem_policy block.
  Other `filesystem_policy` fields such as `include_workdir` are
  preserved verbatim from the static base. Added a regression test
  to lock that behaviour in.

Also: extra regression tests for the throwing-readBasePolicy and
unparseable-base-YAML paths, and the include_workdir passthrough.

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/permissive-runtime.test.ts`:
- Around line 44-48: Guard the cleanup enqueue against fallback base-paths:
after calling buildRuntimePermissivePolicy (the function named
buildRuntimePermissivePolicy) check that the returned out value is not equal to
the sentinel base path (the literal "/unused-base.yaml" used as the unused base
or whatever basePath constant is used) before pushing it onto tempFilesToClean;
if it equals the sentinel, throw or fail the test immediately so afterEach
doesn't attempt to rmSync(path.dirname(out)) on root. Apply the same
check/early-fail to the other success-path tests that push into tempFilesToClean
(the other buildRuntimePermissivePolicy and similar calls around the other
success cases).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 705c5155-a8dd-4473-92c8-6904ab911436

📥 Commits

Reviewing files that changed from the base of the PR and between 7afa33f and 73a4fe5.

📒 Files selected for processing (3)
  • src/lib/shields/index.ts
  • src/lib/shields/permissive-runtime.ts
  • test/permissive-runtime.test.ts

Comment thread test/permissive-runtime.test.ts Outdated
@github-actions
Copy link
Copy Markdown
Contributor

Selective E2E Results — ✅ All requested jobs passed

Run: 26211195274
Target ref: 73a4fe50d7df0bb727eff592579d7985ac09842a
Workflow ref: main
Requested jobs: shields-config-e2e
Summary: 1 passed, 0 failed, 0 skipped

Job Result
shields-config-e2e ✅ success

* Track the mkdtemp directory and call cleanupTempDir in the
  writeFileSync catch branch so a partial temp-file failure no longer
  leaks a 0700 directory under /tmp.

* Expose an injectable writeTempPolicy dep so the write-failure
  fallback can be driven from tests without monkey-patching node:fs.
  Default behaviour (omit dep) keeps using secureTempFile +
  fs.writeFileSync.

* Add a regression test that drives the write-failure branch and
  asserts the helper returns the static base path.

* Tighten the test cleanup helper so it refuses to enqueue any
  output whose dirname is not under os.tmpdir(). Earlier the tests
  could have done `rm -rf path.dirname("/unused-base.yaml")` (i.e.
  "/") if the helper ever degraded to the static path on a code
  regression. The guarded helper now also asserts out !== basePath
  in the success cases for an explicit early-warning signal.

* Reword the dep doc comment: "stays a pure transform" was
  misleading because the helper does in fact do I/O (base read,
  temp file write). Say "live-policy acquisition stays outside this
  helper" instead.

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
@github-actions
Copy link
Copy Markdown
Contributor

Selective E2E Results — ✅ All requested jobs passed

Run: 26212840050
Target ref: ac6b0355861b8424777d73d0f657b3200108de04
Workflow ref: main
Requested jobs: shields-config-e2e
Summary: 1 passed, 0 failed, 0 skipped

Job Result
shields-config-e2e ✅ success

@laitingsheng laitingsheng added the v0.0.49 Release target label May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix v0.0.49 Release target

Projects

None yet

Development

Successfully merging this pull request may close these issues.

nightly-e2e: shields-config-e2e + network-policy-e2e fail — permissive policy missing /home/linuxbrew

1 participant