Skip to content

feat(policy): support custom preset files via --from-file / --from-dir (closes #2039)#2077

Merged
jyaunches merged 6 commits intoNVIDIA:mainfrom
KimYannn:feat/2039-policy-add-from-file
Apr 27, 2026
Merged

feat(policy): support custom preset files via --from-file / --from-dir (closes #2039)#2077
jyaunches merged 6 commits intoNVIDIA:mainfrom
KimYannn:feat/2039-policy-add-from-file

Conversation

@KimYannn
Copy link
Copy Markdown
Contributor

@KimYannn KimYannn commented Apr 19, 2026

Summary

Lets users apply user-authored preset YAML to a running sandbox without editing the baseline or dropping to openshell policy set. Adds --from-file, --from-dir, --yes/-y, and NEMOCLAW_NON_INTERACTIVE=1 support to nemoclaw <name> policy-add.

Related Issue

Closes #2039

Changes

  • src/nemoclaw.ts: extend sandboxPolicyAdd with --from-file <path>, --from-dir <path>, --yes/-y, and NEMOCLAW_NON_INTERACTIVE=1 handling; new applyExternalPreset helper; updated help text.
  • src/lib/policies.ts: new loadPresetFromFile (validates path, YAML, preset.name as RFC 1123 label, presence of network_policies, rejects collisions with built-in preset names); refactor applyPreset to delegate to a new applyPresetContent that works on raw content so the external path can reuse the merge + policy-set logic.
  • test/policies.test.ts: unit tests for loadPresetFromFile (valid/invalid/missing fields/name collision) and integration tests for the --from-file / --from-dir CLI paths (success, dry-run, non-interactive env, mutually exclusive flags, missing path, abort-on-first-failure).
  • docs/network-policy/customize-network-policy.md: new Custom Preset Files section with YAML skeleton, --from-file / --from-dir usage, and a warning that external hosts bypass review.
  • .agents/skills/nemoclaw-user-manage-policy/SKILL.md: regenerated from docs via scripts/docs-to-skills.py.

Design decisions

  • Name collision: reject any custom preset whose preset.name matches a built-in preset — the user must rename it. Keeps the preset registry unambiguous.
  • --from-dir failure mode: stop at the first failure and leave earlier, successfully-applied presets in place. Rollback would require deleting already-merged entries from the running sandbox policy, which is risky; let the user fix the failing file and re-run.
  • Custom hosts: trust the user and print a warning rather than SSRF-gate. Documented in the new section of customize-network-policy.md.

Type of Change

  • Code change with doc updates

Verification

  • npx prek run --all-files passes
  • npm test passes (policies project: 95/95; install-preflight project: 57/57)
  • 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)

AI Disclosure

  • AI-assisted — tool: Claude Code

Signed-off-by: Kim Yang [email protected]

Summary by CodeRabbit

  • New Features

    • Apply user-authored preset YAMLs via --from-file and bulk via --from-dir (files processed lexicographically; stops on first failure; no rollback). Non-interactive controls: --yes/-y or NEMOCLAW_NON_INTERACTIVE=1; preview with --dry-run; preview shows endpoints and a warning for unvetted targets.
  • Documentation

    • Added guide for custom preset structure, name/validation rules, collision behavior, CLI usage, and egress/review warning.
  • Tests

    • Added coverage for file/dir workflows, validations, dry-run, prompts, and abort semantics.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds CLI support to apply user-authored preset YAMLs via nemoclaw <sandbox> policy-add --from-file/--from-dir, implements loader/validator/apply helpers in src/lib/policies.ts, updates interactive/non-interactive flows and help text, and adds docs and tests covering these flows. (≤50 words)

Changes

Cohort / File(s) Summary
Documentation
.agents/skills/nemoclaw-user-manage-policy/SKILL.md, docs/network-policy/customize-network-policy.md
New "Custom Preset Files" sections describing required YAML shape (preset.name as a lowercase RFC 1123-like label), built-in-name collision rules, CLI workflows (--from-file, --from-dir), flags (--dry-run, --yes, NEMOCLAW_NON_INTERACTIVE=1), lexicographic dir processing, stop-on-first-failure semantics, and a warning that custom preset hosts bypass review.
Policy library
src/lib/policies.ts
Refactored preset application: added exported applyPresetContent(sandboxName, presetName, presetContent, options) and loadPresetFromFile(filePath) (validates extension, YAML, preset.name, network_policies, and collisions with built-ins); applyPreset() now loads built-in YAML and delegates to applyPresetContent. Minor simplification in removePresetFromPolicy.
CLI
src/nemoclaw.ts
sandboxPolicyAdd() extended for mutually-exclusive --from-file / --from-dir and non-interactive --yes / NEMOCLAW_NON_INTERACTIVE; added applyExternalPreset(...) helper that loads/validates, previews endpoints, warns about unvetted hosts, honors --dry-run, prompts unless bypassed, and sequentially applies directory entries (lexicographic, abort on first failure). Help text updated.
Tests
test/policies.test.ts
Adjusted buildPolicySetCommand expectations; added unit tests for loadPresetFromFile() (success and multiple failure modes: missing file, bad ext, invalid YAML, missing/invalid preset.name, missing network_policies, collision with built-in); added integration-style tests for policy-add --from-file/--from-dir covering apply, dry-run, prompts, non-interactive mode, arg validation, sorted iteration, and abort-on-failure behavior.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as "nemoclaw CLI"
  participant FS as Filesystem
  participant Policies as policies.ts
  participant OpenShell as openshell

  User->>CLI: run "nemoclaw <sandbox> policy-add --from-file /path/preset.yaml [--dry-run] [--yes]"
  CLI->>FS: read /path/preset.yaml
  FS-->>CLI: file contents
  CLI->>Policies: loadPresetFromFile(filePath)
  Policies-->>CLI: { presetName, content } or error
  alt load success
    CLI->>CLI: show endpoint preview & warning
    alt dry-run
      CLI-->>User: preview only (no apply)
    else apply
      CLI->>Policies: applyPresetContent(sandbox, presetName, content, options)
      Policies->>OpenShell: "openshell policy set --wait" (merge network_policies)
      OpenShell-->>Policies: success / failure
      Policies-->>CLI: result
      CLI-->>User: success / error
    end
  else load failure
    CLI-->>User: error & exit non-zero
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A preset hopped from file to shell,
No more manual merges in the dell,
Validate, preview, then apply—
--yes skips the questions sly,
Custom hosts warned, hop safe, farewell! 🎩

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.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 clearly identifies the primary change: adding support for custom preset files via --from-file and --from-dir flags, matching the core functionality in the changeset.
Linked Issues check ✅ Passed The PR implements all primary objectives from issue #2039: --from-file flag for custom preset YAML [2039], validation of preset format and name uniqueness [2039], --dry-run support [2039], non-interactive flags (--yes/-y, NEMOCLAW_NON_INTERACTIVE) [2039], and optional --from-dir for batch application [2039].
Out of Scope Changes check ✅ Passed All changes directly support custom preset file support: refactored policies.ts to handle external content, extended nemoclaw.ts CLI with new flags, added comprehensive tests, and updated documentation. The single-line reformatting in policies.ts and test assertion adjustments are minor incidental refactoring consistent with the scope.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@KimYannn KimYannn force-pushed the feat/2039-policy-add-from-file branch from 9e08ba8 to 6ce9ecb Compare April 20, 2026 02:44
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

🧹 Nitpick comments (1)
test/policies.test.ts (1)

1120-1164: Make prompt response configurable to cover external decline paths.

runPolicyAddExternal hardcodes prompt return value to "y", so the interactive decline behavior for external presets is untestable. Consider parameterizing it and adding a "no" case to prevent regressions around confirmation safety.

Possible refactor
-    function runPolicyAddExternal(extraArgs = [], envOverrides = {}) {
+    function runPolicyAddExternal(extraArgs = [], envOverrides = {}, promptAnswer = "y") {
...
-credentials.prompt = async (message) => {
+credentials.prompt = async (message) => {
   calls.push({ type: "prompt", message });
-  return "y";
+  return ${JSON.stringify("PROMPT_SENTINEL")};
 };
...
-      fs.writeFileSync(scriptPath, script);
+      fs.writeFileSync(scriptPath, script.replace("PROMPT_SENTINEL", promptAnswer));
+    it("does not apply when external preset confirmation is declined with 'no'", () => {
+      const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-from-file-no-"));
+      const file = path.join(tmp, "custom-rule.yaml");
+      fs.writeFileSync(file, "preset:\n  name: custom-rule\nnetwork_policies: {}\n");
+      const result = runPolicyAddExternal(["--from-file", file], {}, "no");
+      expect(result.status).toBe(0);
+      const calls = JSON.parse(result.stdout.split("__CALLS__")[1].trim());
+      expect(calls.some((c) => c.type === "apply")).toBeFalsy();
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/policies.test.ts` around lines 1120 - 1164, runPolicyAddExternal
currently hardcodes the interactive prompt response by setting policies.prompt =
async (...) => "y", which prevents testing the decline path; change
runPolicyAddExternal to accept a promptResponse parameter (default "y"), pass
that into the generated script and set policies.prompt = async (message) => {
calls.push({ type: "prompt", message }); return promptResponse; }, update any
callers in tests to invoke runPolicyAddExternal(..., ..., "no") to cover the
decline path and assert behavior based on the calls array and process output;
keep existing extraArgs and envOverrides parameters intact so other tests remain
unaffected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/nemoclaw.ts`:
- Around line 1539-1542: The confirmation logic incorrectly treats only the
exact string "n" as a decline, so inputs like "no" are accepted; update both
confirmation branches (the block using askPrompt and the similar block around
the code handling the alternative at lines ~1572-1577) to normalize the response
with trim() and toLowerCase() and treat any response starting with "n" (e.g.,
response.toLowerCase().trim().startsWith("n")) as a decline; reference the
askPrompt call and the local yes variable to locate and replace the current
exact-equality checks.

---

Nitpick comments:
In `@test/policies.test.ts`:
- Around line 1120-1164: runPolicyAddExternal currently hardcodes the
interactive prompt response by setting policies.prompt = async (...) => "y",
which prevents testing the decline path; change runPolicyAddExternal to accept a
promptResponse parameter (default "y"), pass that into the generated script and
set policies.prompt = async (message) => { calls.push({ type: "prompt", message
}); return promptResponse; }, update any callers in tests to invoke
runPolicyAddExternal(..., ..., "no") to cover the decline path and assert
behavior based on the calls array and process output; keep existing extraArgs
and envOverrides parameters intact so other tests remain unaffected.
🪄 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: Pro Plus

Run ID: 4f840bb0-6819-4a75-892e-de82dbe3ff7e

📥 Commits

Reviewing files that changed from the base of the PR and between 9e08ba8 and 6ce9ecb.

📒 Files selected for processing (5)
  • .agents/skills/nemoclaw-user-manage-policy/SKILL.md
  • docs/network-policy/customize-network-policy.md
  • src/lib/policies.ts
  • src/nemoclaw.ts
  • test/policies.test.ts
✅ Files skipped from review due to trivial changes (2)
  • docs/network-policy/customize-network-policy.md
  • .agents/skills/nemoclaw-user-manage-policy/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/policies.ts

Comment thread src/nemoclaw.ts Outdated
@wscurran wscurran added NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). enhancement: feature Use this label to identify requests for new capabilities in NemoClaw. labels Apr 20, 2026
@KimYannn KimYannn force-pushed the feat/2039-policy-add-from-file branch from 6ce9ecb to 14ca974 Compare April 20, 2026 06:11
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 the current code and only fix it if needed.

Inline comments:
In `@src/lib/policies.ts`:
- Around line 519-536: In loadPresetFromFile, fs.readFileSync is called outside
the try-catch which allows a TOCTOU read error to escape; fix by declaring let
content; (and keeping parsed declared) then move the call to
fs.readFileSync(abs, "utf-8") inside the try block before calling
YAML.parse(content), catch any error and call console.error with a descriptive
message (including err.message) and return null so all read/parse failures
follow the same graceful null-return pattern.
🪄 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: Pro Plus

Run ID: cc1781f7-bdb1-42ab-8f84-88dfa7b2d333

📥 Commits

Reviewing files that changed from the base of the PR and between 6ce9ecb and 14ca974.

📒 Files selected for processing (5)
  • .agents/skills/nemoclaw-user-manage-policy/SKILL.md
  • docs/network-policy/customize-network-policy.md
  • src/lib/policies.ts
  • src/nemoclaw.ts
  • test/policies.test.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/network-policy/customize-network-policy.md

Comment thread src/lib/policies.ts Outdated
@KimYannn KimYannn force-pushed the feat/2039-policy-add-from-file branch from 14ca974 to 5ba18f5 Compare April 20, 2026 09:16
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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/nemoclaw.ts (1)

1534-1536: ⚠️ Potential issue | 🟠 Major

--dry-run never reaches the diff/merge path.

Both dry-run branches return before policies.applyPreset(...) / policies.applyPresetContent(...) runs, so the command only prints endpoint summaries and never shows the merged policy diff promised by this feature. That leaves operators unable to validate the actual policy change before applying a custom preset.

Also applies to: 1567-1569

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nemoclaw.ts` around lines 1534 - 1536, The dryRun branch returns early
and thus never invokes policies.applyPreset or policies.applyPresetContent, so
the tool only prints endpoint summaries and never computes/shows the merged
policy diff — remove the early return and instead call the same
applyPreset/applyPresetContent logic in dry-run mode but avoid persisting
changes; specifically, when dryRun is true, call policies.applyPreset(...) or
policies.applyPresetContent(...), capture the merged/updated policy result and
render the diff/merge output (same code used for the real path), but skip any
persistence/commit step; apply this change to both occurrences referencing
dryRun around policies.applyPreset and policies.applyPresetContent so dry-run
shows the merged diff without applying it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/nemoclaw.ts`:
- Around line 2394-2396: The help text currently claims `--yes /
NEMOCLAW_NON_INTERACTIVE=1 skip prompts`, which is inaccurate; update the string
in the CLI help template that contains `${D}--yes / -y or
NEMOCLAW_NON_INTERACTIVE=1 skip prompts${R}` so it clarifies that `--yes` (or
the env var) only skips the final confirmation prompt and does not bypass the
preset-selection flow — note that preset selection remains interactive unless
`--from-file` or `--from-dir` is supplied; make the wording concise like “skip
confirmation prompt; preset selection remains interactive unless --from-file or
--from-dir is used.”

In `@test/policies.test.ts`:
- Around line 276-286: The tests calling policies.buildPolicySetCommand assume
NEMOCLAW_OPENSHELL_BIN is unset and delete it in finally, making them
non-hermetic; modify the test(s) to capture the current value of
process.env.NEMOCLAW_OPENSHELL_BIN at the start (e.g., const prior =
process.env.NEMOCLAW_OPENSHELL_BIN), then set or delete the env var for the test
case as needed, and in the finally block restore it
(process.env.NEMOCLAW_OPENSHELL_BIN = prior === undefined ? undefined : prior)
so the original environment is preserved; apply the same pattern to the other
argv test(s) that manipulate NEMOCLAW_OPENSHELL_BIN and reference
policies.buildPolicySetCommand to locate the affected tests.

---

Outside diff comments:
In `@src/nemoclaw.ts`:
- Around line 1534-1536: The dryRun branch returns early and thus never invokes
policies.applyPreset or policies.applyPresetContent, so the tool only prints
endpoint summaries and never computes/shows the merged policy diff — remove the
early return and instead call the same applyPreset/applyPresetContent logic in
dry-run mode but avoid persisting changes; specifically, when dryRun is true,
call policies.applyPreset(...) or policies.applyPresetContent(...), capture the
merged/updated policy result and render the diff/merge output (same code used
for the real path), but skip any persistence/commit step; apply this change to
both occurrences referencing dryRun around policies.applyPreset and
policies.applyPresetContent so dry-run shows the merged diff without applying
it.
🪄 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: Pro Plus

Run ID: fa3bcdb8-b5ae-4425-89d6-31dc821775e3

📥 Commits

Reviewing files that changed from the base of the PR and between 14ca974 and 5ba18f5.

📒 Files selected for processing (5)
  • .agents/skills/nemoclaw-user-manage-policy/SKILL.md
  • docs/network-policy/customize-network-policy.md
  • src/lib/policies.ts
  • src/nemoclaw.ts
  • test/policies.test.ts
✅ Files skipped from review due to trivial changes (2)
  • docs/network-policy/customize-network-policy.md
  • .agents/skills/nemoclaw-user-manage-policy/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/policies.ts

Comment thread src/nemoclaw.ts Outdated
Comment thread test/policies.test.ts
@KimYannn KimYannn force-pushed the feat/2039-policy-add-from-file branch from 5ba18f5 to 1d66a89 Compare April 21, 2026 04:55
@KimYannn
Copy link
Copy Markdown
Contributor Author

Thanks for the review, @coderabbitai. Responses:

✅ Fixed: Help text wording for --yes — updated the policy-add help to clarify that --yes / NEMOCLAW_NON_INTERACTIVE=1 skips the confirmation prompt, and that preset selection remains interactive unless --from-file or --from-dir is supplied.

❌ Declining: --dry-run should show merged policy diff (outside-diff comment on src/nemoclaw.ts:1534-1536) — intentional. This PR keeps the existing --dry-run contract used by policy-remove ("preview endpoints that would be opened/removed; no changes applied"), so both commands behave consistently. Rendering a merged-policy diff would be a useful enhancement but changes the semantics of --dry-run across the policy commands and is better done as a dedicated follow-up that updates both policy-add and policy-remove together.

❌ Declining: test env-var hermeticity (test/policies.test.ts:276-286) — this test lives outside the scope of this PR. The NEMOCLAW_OPENSHELL_BIN set-then-delete pattern is pre-existing on main; I only touched the buildPolicySetCommand assertions' formatting in this area. Happy to open a separate PR if maintainers want that cleaned up.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

Tip

For best results, initiate chat on the files or code changes.

@KimBioInfoStudio — thanks for the clear write-up!

  1. ✅ Help text wording — noted and appreciated.

  2. --dry-run contract — that's a solid rationale. Keeping --dry-run semantics uniform across policy-add and policy-remove ("show what would change, don't apply") is the right call here; a merged-policy diff can land in a dedicated follow-up that touches both commands at once. I'll record this intent so future reviews don't re-raise it.

  3. Test env-var hermeticity — fair, the NEMOCLAW_OPENSHELL_BIN set-then-delete pattern predates this PR. Would you like me to open a GitHub issue to track the cleanup so it doesn't get lost?


✏️ Learnings added
Learnt from: KimBioInfoStudio
URL: https://github.com/NVIDIA/NemoClaw/pull/2077

Timestamp: 2026-04-21T04:56:00.062Z
Learning: In `src/nemoclaw.ts` and `src/lib/policies.ts` (NemoClaw, TypeScript), the `--dry-run` flag for policy commands intentionally previews only the endpoints that would be opened/removed without rendering a merged-policy diff. This contract is shared between `policy-add` and `policy-remove` for consistency. Any enhancement to show a merged-policy diff should be done in a dedicated follow-up that updates both commands together. Do not flag the absence of a merged-policy diff under `--dry-run` as an issue.

Learnt from: KimBioInfoStudio
URL: https://github.com/NVIDIA/NemoClaw/pull/2077

Timestamp: 2026-04-21T04:56:00.062Z
Learning: In `test/policies.test.ts` (NemoClaw, TypeScript), the `NEMOCLAW_OPENSHELL_BIN` environment variable set-then-`delete` pattern around `buildPolicySetCommand` tests is pre-existing on `main` and not introduced by any single PR. Do not flag missing test hermeticity (e.g., missing `afterEach`/`beforeEach` teardown) in that test block as a new issue in PRs that only reformat assertions in that area.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: laitingsheng
Repo: NVIDIA/NemoClaw PR: 2139
File: src/nemoclaw.ts:1669-1746
Timestamp: 2026-04-21T04:21:52.946Z
Learning: In `src/nemoclaw.ts` (NemoClaw, TypeScript), sandbox-scoped action handlers such as `sandboxChannelsAdd`, `sandboxChannelsRemove`, `sandboxPolicyAdd`, and `sandboxPolicyRemove` intentionally do not validate unknown flags or extra positional arguments. They follow a shared convention of using `args.find((arg) => !arg.startsWith("-"))` to extract the first positional arg, mirroring the `policy add`/`policy remove` pattern. Do not flag missing strict arg validation in these handlers as an issue.

Learnt from: CR
Repo: NVIDIA/NemoClaw PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-21T01:49:06.043Z
Learning: Applies to nemoclaw-blueprint/policies/presets/**/*.yaml : Network policy presets must be added as YAML files to `nemoclaw-blueprint/policies/presets/` following the existing preset structure (see `slack.yaml`, `discord.yaml`)

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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/nemoclaw.ts`:
- Around line 1501-1505: The current files list creation accepts directory
entries by name ending with .yml/.yaml which lets directories like
"archived.yaml/" slip through and later cause applyExternalPreset() to fail
isFile(); change the filter to only include regular files: enumerate the
directory with fs.readdirSync(absDir, { withFileTypes: true }) (or stat each
entry) and keep entries where dirent.isFile() is true and
/\.ya?ml$/i.test(dirent.name), then map to path.join(absDir, name) and sort;
update the variable named files accordingly so applyExternalPreset() only
receives real files.
- Around line 1555-1580: applyExternalPreset can throw because
policies.loadPresetFromFile and policies.applyPresetContent may throw; wrap the
load and apply calls in a try/catch so any sync/async exceptions are caught, log
a concise error (including the thrown error message) and return false instead of
letting the CLI crash. Specifically, surround the call to
policies.loadPresetFromFile(filePath) and the call to
policies.applyPresetContent(sandboxName, loaded.presetName, loaded.content)
(awaiting it if it returns a promise) with a single try/catch that handles
errors, logs them, and returns false.
🪄 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: Pro Plus

Run ID: 3f4278bd-e469-49c5-b63b-ace810872981

📥 Commits

Reviewing files that changed from the base of the PR and between 5ba18f5 and 1d66a89.

📒 Files selected for processing (5)
  • .agents/skills/nemoclaw-user-manage-policy/SKILL.md
  • docs/network-policy/customize-network-policy.md
  • src/lib/policies.ts
  • src/nemoclaw.ts
  • test/policies.test.ts
✅ Files skipped from review due to trivial changes (2)
  • docs/network-policy/customize-network-policy.md
  • .agents/skills/nemoclaw-user-manage-policy/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/policies.ts

Comment thread src/nemoclaw.ts
Comment thread src/nemoclaw.ts Outdated
@wscurran
Copy link
Copy Markdown
Contributor

Thanks for addressing the CodeRabbit feedback! Branch has conflicts with main — could you rebase and we'll get a review queued up.

@wscurran wscurran added the status: rebase PR needs to be rebased against main before review can continue label Apr 22, 2026
Lets users apply user-authored preset YAML to a running sandbox without
editing the baseline policy or dropping to openshell policy set.

- --from-file <path> applies a single custom preset
- --from-dir <path> applies every .yaml preset in a directory; stops at the
  first failure and does not roll back already-applied presets
- --yes / -y and NEMOCLAW_NON_INTERACTIVE=1 skip the confirmation prompt,
  also covering the original built-in preset path
- Custom presets must declare preset.name as an RFC 1123 label that does
  not collide with a built-in preset; the file must include a
  network_policies section
- Custom hosts are not vetted and a warning is printed before apply; the
  docs page spells out the risk

Closes NVIDIA#2039

Signed-off-by: Kim Yang <[email protected]>
@KimYannn KimYannn force-pushed the feat/2039-policy-add-from-file branch from 1d66a89 to 8c3dae8 Compare April 23, 2026 06:04
@KimYannn
Copy link
Copy Markdown
Contributor Author

Rebased onto main (resolves merge conflicts from #2081 / getGatewayPresets / --force flag addition).

Conflict resolutions:

  • src/nemoclaw.ts — kept main's skipConfirm + --force, merged in --from-file/--from-dir logic, kept robust confirm.trim().toLowerCase().startsWith("n") parsing (addresses earlier feedback about "no" handling).
  • src/lib/policies.ts — kept main's new getGatewayPresets alongside this branch's loadPresetFromFile / applyPresetContent.
  • Docs + skill file — both additive sections retained.

Force-pushed. CLI tsc --noEmit passes.

Wrap fs.readFileSync in loadPresetFromFile in a try block so a TOCTOU
read failure between existsSync and readFileSync follows the same
graceful null-return path as YAML parse errors.

Normalize the policy-remove confirmation prompt to match policy-add
(trim + startsWith("n")) so typing "no" actually declines.

Addresses CodeRabbit feedback on PR NVIDIA#2077.
@KimYannn
Copy link
Copy Markdown
Contributor Author

Pushed 5f4ef0f addressing remaining CodeRabbit feedback:

  • loadPresetFromFilefs.readFileSync now inside the try block, TOCTOU read errors return null like other failures (distinguishes ENOENT/EACCES from YAML parse errors in the logged message).
  • policy-remove confirm prompt — normalized to trim().toLowerCase().startsWith("n") to match policy-add; no now declines.

Help-text wording concern is moot — the misleading line was dropped during the rebase.

@KimYannn
Copy link
Copy Markdown
Contributor Author

wsl-e2e failure is a pre-existing flake in test/sandbox-build-context.test.ts > optimized staging is smaller than the legacy build context — 30s timeout when copying the full repo over the WSL /mnt/d NTFS mount. Test is unrelated to this PR's changes (it exercises build-context staging, not policy presets).

Can a maintainer rerun the failed job? I don't have repo admin rights.

@jyaunches jyaunches self-assigned this Apr 24, 2026
@jyaunches
Copy link
Copy Markdown
Contributor

PR Review: feat/2039-policy-add-from-file

Files Changed: 5 | Lines: +629 / -22

Nice work on this, @KimBioInfoStudio — the applyPresetapplyPresetContent refactor is clean, the validation in loadPresetFromFile is thorough, and the test coverage is excellent (103/103 passing). The JSDoc additions throughout are a great bonus. A few things to address before merge:


🔴 Blockers (must fix before merge)

  1. Custom presets can be applied but cannot be removed.

    removePreset() calls loadPreset(presetName) which only looks in PRESETS_DIR (built-in presets). When a user applies a custom preset via --from-file, it gets recorded in the registry by name (e.g., custom-rule). But policy-remove custom-rule will fail because:

    • sandboxPolicyRemove validates against allPresets = policies.listPresets() — custom presets won't appear in that list
    • Even if bypassed, removePresetloadPreset("custom-rule") returns null since there's no custom-rule.yaml in PRESETS_DIR

    This creates a one-way door: presets can be added but not removed. The user's only recourse is openshell policy set with a manually edited YAML.

    Suggested fix: At minimum, document this limitation explicitly and file a follow-up issue. Better: add a --from-file flag to policy-remove that loads the custom preset content for removal, or store the custom preset content alongside its name in the registry so removePreset can find it later.


🟡 Warnings (should fix)

  1. -y added to policy-add but not policy-removesandboxPolicyAdd now accepts -y as a shorthand for --yes, but sandboxPolicyRemove (line 2153) still only accepts --yes/--force. This creates an inconsistency in the CLI surface. Consider adding -y to policy-remove as well, or leaving it out of both.

  2. Docs missing mention of removal limitation — The doc explains how to apply custom presets but doesn't mention how (or whether) they can be removed. Since policy-remove can't currently handle custom presets, this should be documented.

  3. MyST admonition syntax — The doc uses > [!WARNING] (GitHub-flavored Markdown), but the project uses Sphinx/MyST. This should use :::{warning} syntax instead. Verify with make docs that it renders correctly.


🔵 Suggestions (nice to have)

  1. File size check in loadPresetFromFile — The function reads the entire file with fs.readFileSync. Consider adding a statSync(abs).size guard with a reasonable limit (e.g., 1MB) before reading.

  2. Temp file cleanup in tests — The writeTmp helper creates temp directories but never cleans them up. Consider afterEach cleanup or vi.onTestFinished.

  3. --from-dir dotfile filteringreaddirSync + .ya?ml filter would pick up files like .hidden-preset.yaml. Consider filtering dotfiles (.filter(f => !f.startsWith('.'))) to match typical directory-scan behavior.

  4. Symlink check in loadPresetFromFileloadPreset (built-in) has path traversal protection. loadPresetFromFile uses fs.statSync which follows symlinks. Low blast radius since the user already has filesystem access, but worth noting for defense-in-depth.


Overall this is well-structured and well-tested. The blocker around removal is the main thing to sort out — everything else is polish. 🚀

@jyaunches jyaunches self-requested a review April 24, 2026 15:44
Merge main into feat/2039-policy-add-from-file. Keep both sides of the
refactor: main's TypeScript type tightening (PresetInfo, PolicyDocument,
SelectionOptions, isPolicyDocument, typed signatures, extracted
buildSandboxLogsArgs, command-registry-driven help) plus this branch's
custom-preset support (--from-file / --from-dir flags, applyPresetContent,
loadPresetFromFile, applyExternalPreset, -y alias, JSDoc comments).

Advertise --from-file / --from-dir in command-registry.ts so the new
help generator lists them under policy-add. Type the new test helpers
(writeTmp, runPolicyAddExternal) and cast JSON.parse call-logs to
PolicyCall[] so toContainEqual type-checks.
@KimYannn KimYannn force-pushed the feat/2039-policy-add-from-file branch from e683eaa to 29d4aa1 Compare April 25, 2026 15:38
Address CodeRabbit review on NVIDIA#2077:

- --from-dir now enumerates with withFileTypes and rejects entries that
  are not regular files. Previously a sub-directory named 'archived.yaml'
  would be passed to applyExternalPreset and abort the entire batch even
  though no preset file was invalid.
- applyExternalPreset wraps both loadPresetFromFile and applyPresetContent
  in try/catch. loadPresetFromFile still has a TOCTOU window around
  fs.existsSync, and applyPresetContent throws on invalid sandbox names
  or failed openshell policy-set; previously either would crash the CLI
  instead of returning false and letting the caller abort cleanly.
- Add regression test: --from-dir skips a sub-directory whose name ends
  in .yaml so the real preset alongside it still applies.
@KimYannn
Copy link
Copy Markdown
Contributor Author

CodeRabbit review triage

Addressed the major findings and responded inline on each thread. Summary:

Fixed in this PR

# Finding Commit
Major applyExternalPreset didn't catch loader/apply exceptions (#3115137587) 59dc4d25
Minor --from-dir filter accepted sub-directories ending in .yaml/ (#3115137577) 59dc4d25
Major Confirmation prompt treated only exact "n" as decline (#3108013314) 5f4ef0fb
Minor fs.readFileSync outside try/catch TOCTOU (#3108621349) 5f4ef0fb
Minor Help text overstated --yes scope (#3109601365) resolved by main merge — help now generated from typed command-registry.ts with accurate flag list

Deferred

  • --dry-run should show merged-policy diff (Major, outside diff). Intentionally out of scope for this PR. The feature spec in [CLI] nemoclaw policy-add has no custom-preset surface; custom egress targets require dropping to openshell policy set #2039 is "apply custom preset files non-interactively"; dry-run here preserves the existing built-in-preset behavior (print endpoints, skip apply). A real policy-diff renderer needs its own design (structured YAML diff vs. text diff, colorization, how to handle tombstones on remove, etc.) and should ship as a separate feature that improves --dry-run for all policy-add paths, not just --from-file/--from-dir. Happy to open a tracking issue.
  • Non-hermetic NEMOCLAW_OPENSHELL_BIN env handling in test/policies.test.ts (Minor, #3109601399). The problematic delete process.env.NEMOCLAW_OPENSHELL_BIN idiom predates this PR and affects multiple call sites. Cleaner as a single chore(test) sweep that introduces a shared withEnv helper than a partial patch here.

Address jyaunches's blocker on NVIDIA#2077: custom presets applied via
--from-file / --from-dir were written to the registry by name only, so
policy-remove <name> failed because loadPreset() only reads
PRESETS_DIR. This was a one-way door.

Fix:
- registry: add SandboxEntry.customPolicies (array of { name, content,
  sourcePath, appliedAt }), plus getCustomPolicies / addCustomPolicy /
  removeCustomPolicyByName helpers. Built-in preset names continue to
  live in SandboxEntry.policies, so old registries keep working.
- applyPresetContent: when called with options.custom, persist the full
  YAML in customPolicies instead of appending to policies. External
  preset apply path now passes { custom: { sourcePath } }.
- removePreset: resolve preset content from built-ins first, then from
  customPolicies; prune the correct registry bucket on success.
- policy-remove: accept custom-preset names as valid targets, add -y
  alias, show custom presets in the interactive selector, preview
  endpoints from persisted content.
- policy-list: mix built-in and custom presets when listing.
- command-registry: advertise -y on both add/remove, mention custom
  presets on remove.
- docs: MyST ::: warning block (was GH > [!WARNING]) and a new
  'Remove a Custom Preset' section.
- tests: registry unit tests for the new helpers and a policy-remove
  custom-preset round-trip in policies.test.ts.
@KimYannn
Copy link
Copy Markdown
Contributor Author

Thanks @jyaunches — addressing the blocker in ec73dfbc.

🔴 Blocker (custom presets are applied but cannot be removed) — fixed

Rather than require users to keep the original preset file around, I took Option C from the reviewer's three suggestions and persisted the full preset YAML in the sandbox registry when --from-file / --from-dir is used:

  • SandboxEntry.customPolicies: { name, content, sourcePath, appliedAt }[] (new field; policies: string[] is unchanged so existing registries keep working).
  • applyPresetContent(..., { custom: { sourcePath } }) writes to customPolicies; built-in applyPreset still writes to policies.
  • removePreset(sandboxName, name) resolves the preset content from built-ins first, then from customPolicies, and prunes the correct registry bucket.
  • policy-remove <custom-name> now works by name alone — no file required. Interactive selector and policy-list mix built-in and custom presets.

Round-trip tests added in test/registry.test.ts (the new helpers) and test/policies.test.ts (policy-remove custom presets describe block).

🟡 -y alias mismatch — fixed

policy-remove now accepts -y in addition to --yes / --force. Updated command-registry.ts so --help advertises both aliases on both commands.

🟡 docs (MyST warning syntax + removal limitation) — fixed

  • Converted the GH-flavored > [!WARNING] in docs/network-policy/customize-network-policy.md to MyST :::{warning} so it renders under Sphinx.
  • Added a Remove a Custom Preset section explaining that the preset content is persisted in the registry, so policy-remove <name> works without the original file on disk.

🔵 suggestions (file size guard / tmp-dir cleanup / dotfile filter / symlink check)

These are all sensible hardening steps but outside the scope of this blocker fix. I'll file a follow-up issue to track them once this PR is in.

npm run build:cli && npm run typecheck:cli clean; npx vitest run --project cli is 2150 passed / 7 skipped.

Copy link
Copy Markdown
Contributor

@jyaunches jyaunches left a comment

Choose a reason for hiding this comment

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

All blockers and warnings from Friday's review have been addressed. Deferred hardening items tracked in #2521. CI is green — merging.

Copy link
Copy Markdown
Contributor

@jyaunches jyaunches left a comment

Choose a reason for hiding this comment

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

All blockers and warnings from Friday's review have been addressed. Deferred hardening items tracked in #2521. CI is green — merging.

@jyaunches jyaunches merged commit 7720b17 into NVIDIA:main Apr 27, 2026
13 checks passed
@miyoungc miyoungc mentioned this pull request Apr 28, 2026
13 tasks
miyoungc added a commit that referenced this pull request Apr 28, 2026
## Summary
Refreshes user-facing docs for the last 24 hours of merged NemoClaw
history and bumps the docs metadata to 0.0.29, the next version after
v0.0.28. The updates are limited to behavior supported by merged PR
descriptions and diffs.

## Changes
- `docs/reference/commands.md`: documented `nemoclaw <name> policy-add
--from-file` and `--from-dir`, including custom preset review guidance,
from #2077 / commit `7720b175`.
- `docs/deployment/deploy-to-remote-gpu.md`: clarified that non-loopback
`CHAT_UI_URL` disables OpenClaw device pairing for remote browser-only
deployments, from #2449 / commit `f5ee8a4d`.
- `docs/inference/inference-options.md`: documented provider-aware
credential retry validation and the NVIDIA-only `nvapi-` prefix check,
from #2389 / commit `6f7f0c6d`.
- `docs/inference/switch-inference-providers.md`: documented
`NEMOCLAW_INFERENCE_INPUTS` for text/image-capable model metadata baked
into `openclaw.json`, from #2441 / commit `f4391892`.
- `docs/reference/troubleshooting.md`: added the Git certificate
verification entry for proxy CA propagation through `GIT_SSL_CAINFO`,
`GIT_SSL_CAPATH`, `CURL_CA_BUNDLE`, and `REQUESTS_CA_BUNDLE`, from #2345
/ commit `fa0dc1ab`.
- `docs/versions1.json` and `docs/project.json`: promoted docs version
`0.0.29`; `docs/versions1.json` omits unpublished `0.0.26`, `0.0.27`,
and `0.0.28` entries.
- `.agents/skills/nemoclaw-user-*`: regenerated derived user skill
references from the updated docs.
- Reviewed with no extra doc changes: #2575 / `d392ec07`, #2565 /
`a3231049`, #1965 / `db1ef3ca`, #1990 / `db665834`, #2495 / `7da86fa3`,
#2496 / `3192f4f4`, #2490 / `8c209058`, #2487 / `1f615e2f`, #2483 /
`5653d33a`, #2482 / `31c782c0`, #2464 / `23bb5703`, #2472 / `a54f9a34`,
and #2437 / `6bc860d7`.
- Skipped per docs policy: #2420 / `7b76df6b` touched the experimental
sandbox config path listed in `docs/.docs-skip`; #2466 / `cc15689c`
touched a skipped term and CI-only sandbox image files.

## Type of Change
- [ ] Code change (feature, bug fix, or refactor)
- [ ] Code change with doc updates
- [ ] Doc only (prose changes, no code sample modifications)
- [x] Doc only (includes code sample changes)

## Verification
<!-- Check each item you ran and confirmed. Leave unchecked items you
skipped. -->
- [x] `npx prek run --all-files` passes
- [ ] `npm test` passes — failed locally in installer-integration tests
and one onboard helper timeout; the doc-scoped hook test projects passed
under `prek`.
- [ ] Tests added or updated for new or changed behavior
- [x] No secrets, API keys, or credentials committed
- [x] Docs updated for user-facing behavior changes
- [ ] `make docs` builds without warnings (doc changes only) — build
succeeded, but local Sphinx emitted the existing version-switcher file
read message.
- [x] Doc pages follow the [style
guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
(doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

## AI Disclosure
<!-- If an AI agent authored or co-authored this PR, check the box and
name the tool. Remove this section for fully human-authored PRs. -->
- [x] AI-assisted — tool: Codex

---
<!-- DCO sign-off required by CI. Run: git config user.name && git
config user.email -->
Signed-off-by: Miyoung Choi <[email protected]>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Support for custom YAML presets in policy configuration via
--from-file and --from-dir.
* New build-time inference input option to declare accepted modalities
(text or text,image).

* **Improvements**
* Credential validation now offers interactive recovery: re-enter key,
retry, choose another provider, or exit.
* Clarified provider-specific API key prefix handling (nvapi- only
applies to NVIDIA keys).

* **Documentation**
  * TLS certificate troubleshooting for inspected networks.
* Clarified remote dashboard security/device-pairing behavior; command
docs updated; docs version bumped.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Miyoung Choi <[email protected]>
DemianHeyGen pushed a commit to DemianHeyGen/NemoClaw that referenced this pull request Apr 30, 2026
closes NVIDIA#2039) (NVIDIA#2077)

feat(policy): support custom preset files via --from-file / --from-dir (closes NVIDIA#2039)

Adds --from-file, --from-dir, --yes/-y, and NEMOCLAW_NON_INTERACTIVE=1 support to nemoclaw policy-add. Custom preset YAML is persisted in the sandbox registry so policy-remove works by name without the original file.

Follow-up hardening: NVIDIA#2521
DemianHeyGen pushed a commit to DemianHeyGen/NemoClaw that referenced this pull request Apr 30, 2026
## Summary
Refreshes user-facing docs for the last 24 hours of merged NemoClaw
history and bumps the docs metadata to 0.0.29, the next version after
v0.0.28. The updates are limited to behavior supported by merged PR
descriptions and diffs.

## Changes
- `docs/reference/commands.md`: documented `nemoclaw <name> policy-add
--from-file` and `--from-dir`, including custom preset review guidance,
from NVIDIA#2077 / commit `7720b175`.
- `docs/deployment/deploy-to-remote-gpu.md`: clarified that non-loopback
`CHAT_UI_URL` disables OpenClaw device pairing for remote browser-only
deployments, from NVIDIA#2449 / commit `f5ee8a4d`.
- `docs/inference/inference-options.md`: documented provider-aware
credential retry validation and the NVIDIA-only `nvapi-` prefix check,
from NVIDIA#2389 / commit `6f7f0c6d`.
- `docs/inference/switch-inference-providers.md`: documented
`NEMOCLAW_INFERENCE_INPUTS` for text/image-capable model metadata baked
into `openclaw.json`, from NVIDIA#2441 / commit `f4391892`.
- `docs/reference/troubleshooting.md`: added the Git certificate
verification entry for proxy CA propagation through `GIT_SSL_CAINFO`,
`GIT_SSL_CAPATH`, `CURL_CA_BUNDLE`, and `REQUESTS_CA_BUNDLE`, from NVIDIA#2345
/ commit `fa0dc1ab`.
- `docs/versions1.json` and `docs/project.json`: promoted docs version
`0.0.29`; `docs/versions1.json` omits unpublished `0.0.26`, `0.0.27`,
and `0.0.28` entries.
- `.agents/skills/nemoclaw-user-*`: regenerated derived user skill
references from the updated docs.
- Reviewed with no extra doc changes: NVIDIA#2575 / `d392ec07`, NVIDIA#2565 /
`a3231049`, NVIDIA#1965 / `db1ef3ca`, NVIDIA#1990 / `db665834`, NVIDIA#2495 / `7da86fa3`,
NVIDIA#2496 / `3192f4f4`, NVIDIA#2490 / `8c209058`, NVIDIA#2487 / `1f615e2f`, NVIDIA#2483 /
`5653d33a`, NVIDIA#2482 / `31c782c0`, NVIDIA#2464 / `23bb5703`, NVIDIA#2472 / `a54f9a34`,
and NVIDIA#2437 / `6bc860d7`.
- Skipped per docs policy: NVIDIA#2420 / `7b76df6b` touched the experimental
sandbox config path listed in `docs/.docs-skip`; NVIDIA#2466 / `cc15689c`
touched a skipped term and CI-only sandbox image files.

## Type of Change
- [ ] Code change (feature, bug fix, or refactor)
- [ ] Code change with doc updates
- [ ] Doc only (prose changes, no code sample modifications)
- [x] Doc only (includes code sample changes)

## Verification
<!-- Check each item you ran and confirmed. Leave unchecked items you
skipped. -->
- [x] `npx prek run --all-files` passes
- [ ] `npm test` passes — failed locally in installer-integration tests
and one onboard helper timeout; the doc-scoped hook test projects passed
under `prek`.
- [ ] Tests added or updated for new or changed behavior
- [x] No secrets, API keys, or credentials committed
- [x] Docs updated for user-facing behavior changes
- [ ] `make docs` builds without warnings (doc changes only) — build
succeeded, but local Sphinx emitted the existing version-switcher file
read message.
- [x] Doc pages follow the [style
guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
(doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

## AI Disclosure
<!-- If an AI agent authored or co-authored this PR, check the box and
name the tool. Remove this section for fully human-authored PRs. -->
- [x] AI-assisted — tool: Codex

---
<!-- DCO sign-off required by CI. Run: git config user.name && git
config user.email -->
Signed-off-by: Miyoung Choi <[email protected]>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Support for custom YAML presets in policy configuration via
--from-file and --from-dir.
* New build-time inference input option to declare accepted modalities
(text or text,image).

* **Improvements**
* Credential validation now offers interactive recovery: re-enter key,
retry, choose another provider, or exit.
* Clarified provider-specific API key prefix handling (nvapi- only
applies to NVIDIA keys).

* **Documentation**
  * TLS certificate troubleshooting for inspected networks.
* Clarified remote dashboard security/device-pairing behavior; command
docs updated; docs version bumped.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Miyoung Choi <[email protected]>
ericksoa pushed a commit that referenced this pull request May 5, 2026
…ed inputs (#3020)

## Summary

Three follow-up hardening items from PR #2077 review against
`loadPresetFromFile` in `src/lib/policies.ts`: a 10 MB file-size guard,
symlink rejection via `O_NOFOLLOW`, and tmp-dir cleanup in the
corresponding test block. The size-and-symlink checks now use an atomic
`openSync(O_RDONLY | O_NOFOLLOW) → fstatSync → readFileSync(fd)` pattern
(per CodeRabbit review), so there is no TOCTOU window between the stat
and the read and the kernel rejects symbolic links at open time.

## Related Issue

Closes #2521

## Changes

- Add `MAX_PRESET_FILE_BYTES = 10_000_000` and reject files larger than
the limit before reading their contents.
- Replace the prior `existsSync` + `statSync` pair with an atomic
`openSync(O_RDONLY | O_NOFOLLOW)` followed by `fstatSync` on the
descriptor and `readFileSync(fd, "utf-8")`, with a `try/finally`
`closeSync` on every early-return path. Symbolic links are rejected via
`ELOOP` from `openSync` and surfaced with a clear error pointing the
user at `realpath`.
- Track and clean up `mkdtempSync` directories created by the `writeTmp`
helper inside the `loadPresetFromFile` describe block via `afterEach`.
- Add tests covering the new oversize-file (>10 MB) and
symlink-rejection paths.

## Type of Change

- [x] 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

- [x] `npx prek run --all-files` passes
- [x] `npm test` passes
- [x] Tests added or updated for new or changed behavior
- [x] 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](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
(doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

---
Signed-off-by: Tinson Lai <[email protected]>

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Enhanced preset file validation with strict extension checking and a
10MB file size limit.
* Improved security by rejecting symbolic links and enforcing stricter
file access and permission checks, with clearer error messages for
invalid files.

* **Tests**
* Added tests covering preset file size rejection and symbolic link
rejection scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Tinson Lai <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement: feature Use this label to identify requests for new capabilities in NemoClaw. NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). status: rebase PR needs to be rebased against main before review can continue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CLI] nemoclaw policy-add has no custom-preset surface; custom egress targets require dropping to openshell policy set

3 participants