fix(shields,state): keep gateway readable and runtime sessions writable under shields-up#4155
fix(shields,state): keep gateway readable and runtime sessions writable under shields-up#4155laitingsheng wants to merge 8 commits into
Conversation
…te root-owned subdirs in audit NemoClaw #4065: shields-up locked HIGH_RISK_STATE_DIRS as root:root, which stripped sandbox-group ownership from descendants and broke OpenClaw plugin discovery (`extensions/<plugin>/` became unreachable to the gateway, which is only granted sandbox-group access). It also left `agents/main/` non-writable to the sandbox user, so the OpenClaw TUI's lazy mkdir of `agents/main/sessions/` failed with EACCES on first launch under lockdown. Switch the state-dir lock to `root:sandbox` (top-level configDir is still `root:root`) so the gateway keeps `r-x` via the sandbox group on descendants stripped to 2750 by `chmod -R go-w`, and restore `agents/*/sessions` to `sandbox:sandbox 2770` after the main lock loop so the agent keeps writing session metadata under lockdown. NemoClaw #4059: pre-backup audit joined per-dir `find` invocations with `&&`, so a single permission-denied subdir made the whole chain exit 1 and the rebuild treated every state dir as failed. Join with `;` and wrap each `find` with `|| true` — the audit's real signal is its stdout (symlink / hardlink / special-file rows); exit codes from perm-denied root-owned subdirs are noise. Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughState-directory lockdown now uses an explicit lock/unlock flag; high-risk dirs are locked as root:sandbox, writable runtime subpaths (agents/*/sessions) are restored during shields-up, and the pre-backup audit tolerates permission-denied subdirs. Tests validate lock scripts, symlink preflight checks, runtime restoration, and backup semantics. ChangesSecurity Hardening and Runtime Permissions
Sequence DiagramsequenceDiagram
participant Shields
participant applyStateDirLockMode
participant RestoreRuntimeSubpaths
participant Filesystem
Shields->>applyStateDirLockMode: request lock (isLocking=true, owner=root:sandbox)
applyStateDirLockMode->>Filesystem: recursive chown/chmod (strip write bits, set owner)
applyStateDirLockMode->>RestoreRuntimeSubpaths: run restore script for agents/*/sessions
RestoreRuntimeSubpaths->>Filesystem: mkdir/chown sandbox:sandbox + chmod 2770
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Comment |
E2E Advisor RecommendationRequired E2E: Dispatch hint: Auto-dispatched E2E: Full advisor summaryE2E Recommendation AdvisorBase: Required E2E
Optional E2E
New E2E recommendations
Dispatch hint
|
E2E Scenario Advisor RecommendationRequired scenario E2E: None Full scenario advisor summaryE2E Scenario AdvisorBase: Required scenario E2E
Optional scenario E2E
Relevant changed files
|
PR Review AdvisorFindings: 2 needs attention, 3 worth checking, 0 nice ideas Review findings🛠️ Needs attention
🔎 Worth checking
🌱 Nice ideas
Since last review detailsCurrent findings:
This is an automated advisory review. A human maintainer must make the final merge decision. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/lib/state/sandbox.ts (1)
1108-1122: Run the state lifecycle E2E jobs for this audit-semantic change before merge.This changes backup audit behavior under permission-denied traversal, so it’s worth validating with
state-backup-restore-e2e,snapshot-commands-e2e, andrebuild-openclaw-e2e.As per coding guidelines: "
src/lib/state/sandbox.ts: This file manages sandbox state ... E2E test recommendation: state-backup-restore-e2e, snapshot-commands-e2e, rebuild-openclaw-e2e."🤖 Prompt for 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. In `@src/lib/state/sandbox.ts` around lines 1108 - 1122, This change alters the backup audit behavior in src/lib/state/sandbox.ts (see the auditCmd construction and the "Pre-backup audit" log) so run the full state lifecycle E2E suites before merging: execute state-backup-restore-e2e, snapshot-commands-e2e, and rebuild-openclaw-e2e against a fresh sandbox to validate permission-denied traversal is tolerated and legitimate rebuilds still succeed; if any test fails, adjust the auditCmd logic or error handling around the shell-quoted dir mapping (the code building auditCmd and the surrounding pre-backup audit flow) until the E2E suites pass.
🤖 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/snapshot.test.ts`:
- Around line 1056-1171: Update the fake ssh stub used in the two tests ("treats
audit-find exit 1 with empty stdout as a successful audit (NemoClaw `#4059`)" and
"still rejects violations from readable dirs even if a sibling find exits
non-zero") so that when the command contains "find " the script exits non-zero
(e.g., process.exit(1)) unless the command string also contains the tolerant
shape "|| true"; in other words, change the cmd.includes("find ") branch in both
writeExecutable calls to check for cmd.includes("|| true") and only return
non-zero when that tolerant token is absent, preserving existing stdout behavior
for other branches.
---
Nitpick comments:
In `@src/lib/state/sandbox.ts`:
- Around line 1108-1122: This change alters the backup audit behavior in
src/lib/state/sandbox.ts (see the auditCmd construction and the "Pre-backup
audit" log) so run the full state lifecycle E2E suites before merging: execute
state-backup-restore-e2e, snapshot-commands-e2e, and rebuild-openclaw-e2e
against a fresh sandbox to validate permission-denied traversal is tolerated and
legitimate rebuilds still succeed; if any test fails, adjust the auditCmd logic
or error handling around the shell-quoted dir mapping (the code building
auditCmd and the surrounding pre-backup audit flow) until the E2E suites pass.
🪄 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: b97cf8fe-d359-487b-8af3-17cb329e2c93
📒 Files selected for processing (5)
src/lib/shields/index.tssrc/lib/state/sandbox.tstest/repro-2681-group-writable.test.tstest/repro-4065-shields-up-runtime-perms.test.tstest/snapshot.test.ts
…file Address review: scrub `#4065` / `#4059` mentions from production code comments and test docstrings, and rename the new shields-up regression test from `repro-4065-…` to `shields-up-runtime-perms.test.ts` so the filename describes behaviour rather than an issue number. Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Selective E2E Results — ✅ All requested jobs passedRun: 26355812622
|
Selective E2E Results — ✅ All requested jobs passedRun: 26356219674
|
…d test Address review: 1. `restoreWritableRuntimeSubpaths` expanded the full pattern `agents/*/sessions` as a single glob. On a fresh sandbox where `sessions` does not exist yet, the glob has no matches and the shell leaves the literal pattern, which the `*"*"*` guard then drops — so `sessions/` was never created and the post-lockdown TUI mkdir still failed with EACCES. Split each pattern into a parent glob (expanded against the existing tree) plus a leaf to create, so the helper always mkdir's the missing leaf inside every existing parent. 2. The two pre-backup audit tests stubbed the SSH fake as always-exit-0 on `find`, so the `|| true` tolerance wrapper was not actually exercised. Make the fake exit non-zero with a Permission-denied stderr unless the audit cmd includes `|| true`, so the tests fail loudly if the wrapper is dropped. 3. New behavioural test runs the actual restore-helper script body against a real filesystem fixture and asserts that `agents/main/sessions` is created when only `agents/main` exists beforehand. Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/index.ts`:
- Around line 490-499: The loop in restoreWritableRuntimeSubpaths
(WRITABLE_RUNTIME_SUBPATHS handling) currently treats symlinked directories as
regular dirs and thus creates/mutates sessions/ on the symlink target; update
the parent existence guard to skip symlinks (e.g., require directory AND not a
symlink) or explicitly check [ -L "$parent" ] and continue when true so
mkdir/chown/chmod are not applied to symlink targets; make this change around
the loop that iterates parents (the for parent in "$@"; do ... [ -d "$parent" ]
|| continue portion) to ensure symlinked runtime parents are ignored.
In `@test/shields-up-runtime-perms.test.ts`:
- Around line 129-135: The test extracts patterns with restoreShell.slice(4)
which includes configDir, causing configDir to be passed twice into spawnSync
and altering the argv layout; change the extraction to restoreShell.slice(5) so
that patterns contains only the glob patterns, leaving the explicit "configDir"
argument in the spawnSync call (referencing restoreShell, script, patterns, and
the spawnSync invocation).
🪄 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: 1eb0107a-ac61-4d44-ab10-07c74795f156
📒 Files selected for processing (3)
src/lib/shields/index.tstest/shields-up-runtime-perms.test.tstest/snapshot.test.ts
Selective E2E Results — ✅ All requested jobs passedRun: 26356782985
|
… test argv slice Address review: 1. The privileged restore helper called `[ -d "$parent" ]` before `mkdir`/`chown`/`chmod`, but `[ -d ]` follows symlinks. A pre-lockdown agent that swapped `agents/<id>` for a symlink to an arbitrary host path could redirect the post-lock `mkdir -p ".../sessions"` and `chown -R sandbox:sandbox` through that link and rewrite ownership on any directory the privileged exec context can reach. Drop the parent (and the target leaf) when either is a symlink, before any mutation. 2. The behavioural test extracted patterns with `slice(4)`, which kept the captured `configDir` in the argv passed to bash — so the helper ran with `configDir` listed twice and the test argv diverged from the real `privilegedSandboxExec` call shape. Use `slice(5)` so only the patterns are forwarded. 3. New behavioural test asserts the symlink guard: when `agents/<id>` is a symlink to a sibling host directory, the helper must not create `sessions/` under either the link target or the link itself. Also reword one comment to avoid contested terminology. Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
test/shields-up-runtime-perms.test.ts (1)
135-139:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse
sh(notbash) when replaying the restore helperBoth restore-shell captures match
sh -c, but both behavioral replays runspawnSync("bash", ["-c", ...]), which can mask/bin/shcompatibility regressions. Switch the replay shell toshin both replay blocks (around lines 135-139 and 173-177).♻️ Proposed fix
- const result = spawnSync( - "bash", - ["-c", `${script}\n`, "sh", configDir, ...patterns], - { encoding: "utf-8", timeout: 5000 }, - ); + const result = spawnSync( + "sh", + ["-c", `${script}\n`, "sh", configDir, ...patterns], + { encoding: "utf-8", timeout: 5000 }, + );Apply the same change to the second replay block as well.
🤖 Prompt for 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. In `@test/shields-up-runtime-perms.test.ts` around lines 135 - 139, In test/shields-up-runtime-perms.test.ts the replay uses spawnSync("bash", ["-c", ...]) which hides /bin/sh compatibility issues; change both replay invocations to use spawnSync("sh", ["-c", ...]) instead (update the call sites where spawnSync is invoked with "bash" in the two replay blocks around the current spawnSync(...) usages so they call "sh" and keep the same args and options).
🤖 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.
Outside diff comments:
In `@test/shields-up-runtime-perms.test.ts`:
- Around line 135-139: In test/shields-up-runtime-perms.test.ts the replay uses
spawnSync("bash", ["-c", ...]) which hides /bin/sh compatibility issues; change
both replay invocations to use spawnSync("sh", ["-c", ...]) instead (update the
call sites where spawnSync is invoked with "bash" in the two replay blocks
around the current spawnSync(...) usages so they call "sh" and keep the same
args and options).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: ea69a11f-9462-4b87-aa13-0ece1af0192b
📒 Files selected for processing (3)
src/lib/shields/index.tstest/shields-up-runtime-perms.test.tstest/snapshot.test.ts
Selective E2E Results — ✅ All requested jobs passedRun: 26357693833
|
…ed comment
Address review:
1. The behavioural test for the runtime-subpath restore was running the
captured script body under `bash` rather than `sh`, while the
production helper invokes it via `sh -c`. A bash-only construct
slipping into the helper would pass the test but break the real call
site. Switch the replay to `spawnSync("sh", …)` in both fixtures so
the test covers exactly what the privileged exec runs.
2. Drop the NC-2227-05 tag from the updated `lockAgentConfig` comment.
The state-directory ownership story is now self-contained in
`HIGH_RISK_STATE_DIRS` doc + the helper itself; the issue tag does
not add information and conflicts with the no-issue-refs-in-comments
guidance applied across the rest of this PR.
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/lib/shields/index.ts (1)
399-458:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winReject symlinked state-dir roots before recursive
chmod -R/chown -Rlock stepGNU coreutils defaults make
chmod -Rfollow symlink command-line arguments, so if aconfigDir/<stateDir>orconfigDir/workspace-*entry is a symlink to a directory, the recursivechmod -Rcan apply outsideconfigDirduring the shields-up lock path. (chown -Rdefaults to non-dereference for symlink command-line args, but the recursivechmod -Ris enough for this to be a security/correctness blocker.)Suggested hardening
for (const dirName of HIGH_RISK_STATE_DIRS) { const dirPath = `${configDir}/${dirName}`; + try { + privilegedSandboxExec(sandboxName, [ + "sh", + "-c", + '[ -d "$1" ] && [ ! -L "$1" ]', + "sh", + dirPath, + ]); + } catch { + continue; + } try { privilegedSandboxExec(sandboxName, ["chown", "-R", owner, dirPath]); } catch { // Directory may not exist for this agent — silently skip } @@ for dir in "$config_dir"/workspace-*; do + [ -L "$dir" ] && continue [ -d "$dir" ] || continue chown -R "$owner" "$dir" 2>/dev/null || true chmod "$dir_mode" "$dir" 2>/dev/null || true [ "$clear_setgid" = "1" ] && chmod g-s "$dir" 2>/dev/null || true chmod -R "$recursive_mode" "$dir" 2>/dev/null || true doneAlso applies to: 756-760
🤖 Prompt for 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. In `@src/lib/shields/index.ts` around lines 399 - 458, The recursive chown/chmod operations in the HIGH_RISK_STATE_DIRS loop and the workspace-* shell block can follow symlinked roots and affect paths outside configDir; before calling privilegedSandboxExec for any dirPath or iterating workspace-* entries (symbols: HIGH_RISK_STATE_DIRS, privilegedSandboxExec, configDir, dirPath, workspace-* pattern, recursiveMode, dirMode, owner, clearSetgid, isLocking), reject/skips any entry that is a symlink (use a non-following lstat/test -L check) so recursive -R operations are only applied to real directories; update both the TypeScript loop (skip when lstat indicates symlink) and the embedded shell script (skip entries where [ -L "$dir" ] ) to avoid following symlink roots.
🤖 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.
Outside diff comments:
In `@src/lib/shields/index.ts`:
- Around line 399-458: The recursive chown/chmod operations in the
HIGH_RISK_STATE_DIRS loop and the workspace-* shell block can follow symlinked
roots and affect paths outside configDir; before calling privilegedSandboxExec
for any dirPath or iterating workspace-* entries (symbols: HIGH_RISK_STATE_DIRS,
privilegedSandboxExec, configDir, dirPath, workspace-* pattern, recursiveMode,
dirMode, owner, clearSetgid, isLocking), reject/skips any entry that is a
symlink (use a non-following lstat/test -L check) so recursive -R operations are
only applied to real directories; update both the TypeScript loop (skip when
lstat indicates symlink) and the embedded shell script (skip entries where [ -L
"$dir" ] ) to avoid following symlink roots.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: cf8d1c18-3301-4936-baba-55a23d2cf2e7
📒 Files selected for processing (2)
src/lib/shields/index.tstest/shields-up-runtime-perms.test.ts
Selective E2E Results — ✅ All requested jobs passedRun: 26358552843
|
…p lock
Address review:
1. The shields-up lock loop ran `chown -R`/`chmod -R`/`chmod g-s` on
each `${configDir}/${dirName}` (and on every `workspace-*` glob hit)
without rejecting symlinked roots. A pre-lockdown agent that swapped
e.g. `extensions/` or `workspace-main/` for a symlink to a host path
could redirect those recursive ownership and mode mutations at an
attacker-controlled directory. Consolidate the per-state-dir loop
into a single privileged shell exec that skips symlinks (`[ -L "$path" ]
&& continue`) before any mutation, and add the same guard to the
existing `workspace-*` shell loop.
2. Drop the `NC-2227-05` issue tag from the state-directory header
comment for consistency with the rest of this PR.
Updates the regression tests:
- `repro-2681`: assert the unlock fan-out via the new `sh -c` script
shape (workspace included as an arg, plus the workspace-* glob path
still present).
- `shields-up-runtime-perms`: assert the state-dir lock and workspace-*
scripts both contain the `[ -L … ] && continue` guard, and add a
behavioural fixture that proves a symlinked `extensions/` root is
skipped (its host target keeps its original mode and file contents).
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Selective E2E Results — ✅ All requested jobs passedRun: 26359318121
|
Address review: Previously, when shields-up encountered a symlinked high-risk state-dir root (or a symlinked `workspace-*` dir), the privileged lock script silently skipped it via `[ -L "$path" ] && continue`. That refused to follow the link — good — but left the dir as-is and reported the lock as successful. The sandbox would then sit in "shields up (lockdown active)" status while a state-dir root still pointed at a writable host path, exactly the security regression the symlink guard was meant to prevent. Have the two consolidated lock shell scripts always exit 0 but emit `symlinked-root\t<path>` on stdout for every symlinked root they refuse to touch. `applyStateDirLockMode` parses those lines and returns them as lock failures when invoked under `isLocking=true`. `lockAgentConfig` now throws "Config not locked: state dir root is a symlink: …" before any further verification, refusing to acknowledge shields-up. Unlock is unchanged: skipping symlinked roots is the correct best-effort behaviour there. New regression test exercises the end-to-end path: when the captured shell script reports a symlinked root via the mocked exec, the `lockAgentConfig` call throws with the expected diagnostic. Tests for the static script shape are updated to match the new `if [ -L … ]; then printf …; fi` form instead of the previous `[ -L … ] && continue`. Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Selective E2E Results — ✅ All requested jobs passedRun: 26360739914
|
… + doc update Address review: 1. The inline `[ -L … ] && printf 'symlinked-root\t…'` guard in the mutation script ran per-iteration. A symlinked root later in the list could still leave earlier (non-symlinked) state dirs already reowned to `root:sandbox` by the time the lock helper bailed out. shields-up would then report "config not locked" while the tree was partially mutated. Add a dedicated preflight pass that runs before any `chown`/`chmod`, scans every high-risk state-dir root *and* every `workspace-*` dir for symlinks, and returns the full list. When `isLocking=true` and the preflight finds any symlinked root, `applyStateDirLockMode` short-circuits without touching the mutation pass or the sessions-restore helper, and `lockAgentConfig` throws `Config not locked: state dir root is a symlink: …`. The inline symlink guards in the mutation scripts stay for defence-in-depth in case the preflight and the mutation observe different fs state. 2. New regression test mocks the preflight script to report a symlinked root and asserts (a) `lockAgentConfig` throws with the expected diagnostic and (b) no mutation calls (state-dir lock, workspace-* lock, or sessions-restore) were ever issued. 3. Add a second regression test that asserts a dedicated preflight script (no `chown`/`chmod`, just the `[ -L … ] && printf` checks) is present in the recorded call sequence. 4. Update `docs/security/best-practices.mdx` to document the new `root:sandbox` state-dir ownership, the `agents/<id>/sessions` runtime carve-out, and the hard fail on symlinked state-dir roots. Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
|
🌿 Preview your docs: https://nvidia-preview-pr-4155.docs.buildwithfern.com/nemoclaw |
Selective E2E Results — ✅ All requested jobs passedRun: 26361443080
|
Summary
Fix two related shields-up regressions: under lockdown the OpenClaw gateway lost sandbox-group access to plugin/agent state dirs (so
/nemoclawandopenclaw-weixinreported "plugin not found" + the TUI crashed withEACCES: mkdir agents/main/sessions), andnemoclaw rebuildaborted its pre-backup audit on a fresh sandbox becausefindexits non-zero when it walks the base-image's root-owned subdirs.Related Issue
Fixes #4065
Fixes #4059
Changes
src/lib/shields/index.ts— lock the high-risk state dirs asroot:sandbox(top-level config dir is stillroot:root) so the gateway keepsr-xaccess to descendants stripped to2750bychmod -R go-w. AddWRITABLE_RUNTIME_SUBPATHS = ["agents/*/sessions"]and arestoreWritableRuntimeSubpathshelper that runs at the end of the lock and chowns those paths back tosandbox:sandbox 2770so the agent keeps writing session metadata under lockdown. PromoteisLockingfrom a string-equality derivation to an explicit parameter so the new owner string can change without affecting locking semantics.src/lib/state/sandbox.ts— replace the&&-joined per-dirfindchain inbackupSandboxState's pre-backup audit with;-joined{ find … || true; }blocks. The audit's real signal is its stdout (the printf-emitted symlink / hardlink / special-file rows); exit codes from permission-denied root-owned subdirs are noise.test/shields-up-runtime-perms.test.ts(new) — regression coverage for thelockAgentConfigownership change and theagents/*/sessionsrestoration shell loop.test/repro-2681-group-writable.test.ts— update the existinglockAgentConfigassertion to expectroot:sandboxin the state-dir-lock shell command.test/snapshot.test.ts— add two regressions for the audit-find tolerance: (a)findexit 1 with empty stdout still produces a successful audit, and (b) violations from a readable sibling dir are still rejected even when a co-located dir is unreadable.Type of Change
Verification
npx prek run --all-filespassesnpm testpassesmake docsbuilds without warnings (doc changes only)Signed-off-by: Tinson Lai tinsonl@nvidia.com
Summary by CodeRabbit
Bug Fixes
Improvements
Tests
Documentation