Skip to content

fix: redact secrets from CLI output and stop exposing API keys (#579, #664)#780

Closed
cluster2600 wants to merge 2 commits intoNVIDIA:mainfrom
cluster2600:fix/02-secret-redaction
Closed

fix: redact secrets from CLI output and stop exposing API keys (#579, #664)#780
cluster2600 wants to merge 2 commits intoNVIDIA:mainfrom
cluster2600:fix/02-secret-redaction

Conversation

@cluster2600
Copy link
Copy Markdown
Contributor

@cluster2600 cluster2600 commented Mar 24, 2026

Summary

graph LR
    A[CLI output] --> B{redactSecrets}
    B --> C[env assignments masked]
    B --> D[nvapi- tokens masked]
    B --> E[ghp_ tokens masked]
    B --> F[Bearer tokens masked]
    style B fill:#f96,stroke:#333
Loading
  • Add redactSecrets() with 4 patterns
  • Error messages now redacted before display
  • setupSpark passes API key via environment, not command-line arguments
  • walkthrough.sh uses tmux -e for credential isolation
  • telegram-bridge.js uses SSH SendEnv
  • 9 new tests

Closes #579, #664.

Test plan

  • All tests passing (194 → 203)
  • Verify no secrets appear in --verbose output

Summary by CodeRabbit

  • New Features
    • Sensitive credentials are now automatically redacted from error messages
    • Enhanced secure credential handling via environment variables in initialization and remote operations

- uninstall.sh: check parent directory writability instead of file
  writability when deciding whether sudo is needed for removal. The
  previous check followed symlinks, so a symlink to a user-writable
  target in a root-owned directory would attempt unprivileged rm and
  fail with EACCES.

- scripts/install.sh: replace NODE_MGR-based sudo heuristic with a
  direct writability check on the npm global prefix. The old approach
  always used sudo for nodesource installs, which changes PATH and
  breaks environments where the prefix is already user-writable.
…in process args (NVIDIA#579, NVIDIA#664)

Add redactSecrets() to runner.js with patterns matching env assignments,
nvapi- prefixes, GitHub PATs, and Bearer tokens. Apply to error messages
in run() and runInteractive() so failed commands never leak credentials.

Fix setupSpark() to pass NVIDIA_API_KEY via env option instead of
interpolating into the command string (visible in ps aux).

Fix walkthrough.sh to use tmux -e for env propagation instead of
embedding the key in the shell command. Fix telegram-bridge.js to use
SSH SendEnv instead of command-line export.

9 new tests covering redaction patterns and regression guards.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

This pull request addresses a critical security vulnerability by preventing NVIDIA API keys and other sensitive credentials from being exposed in process arguments and terminal output. Changes include adding a secret redaction function, refactoring multiple scripts and JavaScript files to pass credentials via environment variables rather than command-line arguments, and updating shell scripts to avoid printing or embedding API keys in visible command strings.

Changes

Cohort / File(s) Summary
Secret Redaction Function
bin/lib/runner.js
Added redactSecrets() function that redacts environment variables (KEY=...), NVIDIA API tokens (nvapi-...), GitHub PATs (ghp_...), and Bearer tokens from strings. Updated error logging to apply redaction to command snippets before output.
Environment Variable Passing
bin/nemoclaw.js, scripts/telegram-bridge.js
Refactored credential handling to pass NVIDIA_API_KEY via process env option in run() calls and SSH forwarding (SendEnv) instead of embedding keys directly in command strings, preventing visibility in process arguments.
Shell Script Updates
scripts/walkthrough.sh
Updated non-tmux instructions to reference $NVIDIA_API_KEY with escaped syntax (\$NVIDIA_API_KEY) instead of expanding the value. Changed tmux window split to inject NVIDIA_API_KEY via tmux environment (-e flag) rather than inline bash command.
Installation & Cleanup Logic
scripts/install.sh, uninstall.sh
Replaced hardcoded node manager detection with dynamic npm config get prefix check in install.sh for determining sudo necessity. Narrowed privileged removal logic in uninstall.sh to check only directory write permissions instead of both file and directory.
Test Coverage
test/runner.test.js
Added comprehensive test suite for redactSecrets() covering environment variables, NVIDIA tokens, GitHub PATs, Bearer tokens, multiple secret types, and non-string inputs. Added regression guards ensuring API keys are not embedded in setupSpark function or printed in walkthrough.sh.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 The rabbit's whisker-twitch security song:
Our keys used to dance where prying eyes roam,
Through process arguments, exposed and alone!
Now cloaked in environments, safe from the sight,
The rabbit stands guard through the long cryptic night. ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Out of Scope Changes check ❓ Inconclusive While most changes directly address issue #579, the PR includes minor scope adjustments to uninstall.sh's permission logic and scripts/install.sh's npm prefix detection, which are fixing pre-existing issues in installer utilities unrelated to the main secret-redaction objectives. Clarify whether the uninstall.sh and install.sh changes are necessary dependencies for the security fix or should be addressed in a separate PR to maintain focused scope.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: redact secrets from CLI output and stop exposing API keys' directly and accurately describes the main objective of the PR, which is addressing the critical security issue of API keys being exposed in process arguments and terminal output.
Linked Issues check ✅ Passed The PR comprehensively addresses the requirements in issue #579 by: (1) implementing redactSecrets() for defensive masking of CLI output; (2) passing NVIDIA_API_KEY via process env instead of command strings in nemoclaw.js and walkthrough.sh; (3) using SSH SendEnv in telegram-bridge.js; (4) updating script heuristics; and (5) adding regression tests to prevent future exposures.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/02-secret-redaction

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

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.

🧹 Nitpick comments (3)
bin/lib/runner.js (1)

73-86: Consider applying redaction to runCapture errors for consistency.

run and runInteractive now redact secrets from error output, but runCapture re-throws the raw error which may contain command snippets with secrets in err.cmd or err.message. For defense-in-depth, consider redacting before re-throwing:

   } catch (err) {
     if (opts.ignoreError) return "";
+    if (err.message) err.message = redactSecrets(err.message);
     throw err;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/runner.js` around lines 73 - 86, runCapture currently re-throws the
raw error which may contain secrets in err.cmd or err.message; update runCapture
to redact secrets before re-throwing by using the same redaction utility used by
run/runInteractive (e.g., redactSecrets or redact) to sanitize err.message and
err.cmd, and then throw a new/error with the redacted message (preserving
original stack if possible) while keeping existing opts.ignoreError behavior
intact.
scripts/telegram-bridge.js (1)

145-146: Consider redacting secrets from error output.

The stderr content returned on failure could potentially contain secrets if the remote command somehow outputs them. Consider applying redactSecrets from bin/lib/runner.js here for defense-in-depth:

const { redactSecrets } = require("../bin/lib/runner");
// ...
resolve(`Agent exited with code ${code}. ${redactSecrets(stderr.trim().slice(0, 500))}`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/telegram-bridge.js` around lines 145 - 146, Require and use the
redactSecrets helper to sanitize stderr before including it in the resolved
error string: add "const { redactSecrets } = require('../bin/lib/runner');" near
the top of scripts/telegram-bridge.js and update the error branch where it
currently does resolve(`Agent exited with code ${code}. ${stderr.trim().slice(0,
500)}`) to instead call redactSecrets on the stderr snippet (e.g. resolve(`Agent
exited with code ${code}. ${redactSecrets(stderr.trim().slice(0, 500))}`)) so
any potential secrets are redacted.
test/runner.test.js (1)

255-267: Fragile regex for function extraction.

The regex async function setupSpark\b[\s\S]*?\n\} uses a non-greedy match that stops at the first \n}. This works for the current simple setupSpark function, but would capture only a partial body if the function grows to include nested blocks (e.g., if statements with braces).

Consider a more robust assertion that doesn't require extracting the full function body:

♻️ Alternative approach
-    const match = src.match(/async function setupSpark\b[\s\S]*?\n\}/);
-    assert.ok(match, "setupSpark function must exist");
-    const body = match[0];
-    assert.ok(
-      !body.includes("NVIDIA_API_KEY=") || body.includes("env:"),
-      "setupSpark must pass API key via env option, not in the command string",
-    );
+    // Find setupSpark and its run() call
+    const funcStart = src.indexOf("async function setupSpark");
+    assert.ok(funcStart !== -1, "setupSpark function must exist");
+    // Check the run() call pattern after setupSpark declaration
+    const afterFunc = src.slice(funcStart, funcStart + 500);
+    assert.ok(
+      afterFunc.includes('env: { NVIDIA_API_KEY:'),
+      "setupSpark must pass API key via env option",
+    );
+    assert.ok(
+      !afterFunc.includes('NVIDIA_API_KEY=${') && !afterFunc.includes('NVIDIA_API_KEY="'),
+      "setupSpark must not interpolate API key in command string",
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/runner.test.js` around lines 255 - 267, The current regex extracting
setupSpark is brittle; replace the brittle match with a stable AST-based check:
parse the file (e.g., with acorn/espree), locate the
FunctionDeclaration/FunctionExpression named setupSpark, obtain its body node
source, and assert that any child CallExpression that builds the command (or
calls spawn/exec) passes an options object using an env property (i.e., options
object has a property named "env" or the call uses a second argument referencing
an env object). Update the test in test/runner.test.js to use the AST lookup
instead of the regex (referencing setupSpark, match, and body in the test) so
the assertion no longer depends on fragile string matching.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@bin/lib/runner.js`:
- Around line 73-86: runCapture currently re-throws the raw error which may
contain secrets in err.cmd or err.message; update runCapture to redact secrets
before re-throwing by using the same redaction utility used by
run/runInteractive (e.g., redactSecrets or redact) to sanitize err.message and
err.cmd, and then throw a new/error with the redacted message (preserving
original stack if possible) while keeping existing opts.ignoreError behavior
intact.

In `@scripts/telegram-bridge.js`:
- Around line 145-146: Require and use the redactSecrets helper to sanitize
stderr before including it in the resolved error string: add "const {
redactSecrets } = require('../bin/lib/runner');" near the top of
scripts/telegram-bridge.js and update the error branch where it currently does
resolve(`Agent exited with code ${code}. ${stderr.trim().slice(0, 500)}`) to
instead call redactSecrets on the stderr snippet (e.g. resolve(`Agent exited
with code ${code}. ${redactSecrets(stderr.trim().slice(0, 500))}`)) so any
potential secrets are redacted.

In `@test/runner.test.js`:
- Around line 255-267: The current regex extracting setupSpark is brittle;
replace the brittle match with a stable AST-based check: parse the file (e.g.,
with acorn/espree), locate the FunctionDeclaration/FunctionExpression named
setupSpark, obtain its body node source, and assert that any child
CallExpression that builds the command (or calls spawn/exec) passes an options
object using an env property (i.e., options object has a property named "env" or
the call uses a second argument referencing an env object). Update the test in
test/runner.test.js to use the AST lookup instead of the regex (referencing
setupSpark, match, and body in the test) so the assertion no longer depends on
fragile string matching.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 775a71e7-a4a5-4992-8416-9b124a3ea015

📥 Commits

Reviewing files that changed from the base of the PR and between 6e1208c and b164b7e.

⛔ Files ignored due to path filters (1)
  • research/results.tsv is excluded by !**/*.tsv
📒 Files selected for processing (7)
  • bin/lib/runner.js
  • bin/nemoclaw.js
  • scripts/install.sh
  • scripts/telegram-bridge.js
  • scripts/walkthrough.sh
  • test/runner.test.js
  • uninstall.sh

@cluster2600
Copy link
Copy Markdown
Contributor Author

Closing — overlaps with existing PRs already addressing the same issues. Cheers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CRITICAL: NVIDIA API Key Exposed in Process Arguments and Terminal Output

1 participant