Skip to content

fix(security): redact gateway auth token from console output - #1954

Closed
ColinM-sys wants to merge 6 commits into
NVIDIA:mainfrom
ColinM-sys:fix/redact-gateway-token-from-console
Closed

fix(security): redact gateway auth token from console output#1954
ColinM-sys wants to merge 6 commits into
NVIDIA:mainfrom
ColinM-sys:fix/redact-gateway-token-from-console

Conversation

@ColinM-sys

@ColinM-sys ColinM-sys commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Redact the gateway auth token in dashboard URLs printed to stdout during onboard. Only the first 4 characters are shown; the rest is replaced with asterisks.
  • Add forDisplay parameter to buildControlUiUrls()true redacts the token (safe for logs), false preserves full token (backward compatible).

Why

The gateway auth token controls full access to the OpenClaw dashboard — agent commands, workspace read/write, and action execution. During onboard, the full 64-character hex token is printed to stdout as part of the dashboard URL:

http://127.0.0.1:18789/#token=a1b2c3d4e5f6...

In CI/CD environments (--non-interactive), this token persists in build logs that are often world-readable (public GitHub Actions repos, shared GitLab CI). Terminal scrollback and piped output also retain the plaintext token. CWE-532 (Insertion of Sensitive Information into Log File).

What changed

  • src/lib/dashboard.tsbuildControlUiUrls() gains optional forDisplay parameter; new redactToken() helper
  • src/lib/agent-onboard.tsprintDashboardUi() calls buildControlUiUrls(token, port, true) and prints a hint for retrieving the full token via jq

Test plan

  • npm run build:cli — compiles cleanly
  • Manual: nemoclaw onboard shows redacted token in dashboard URL (e.g. #token=a1b2********************)
  • Manual: full token still retrievable inside sandbox via jq '.gateway.auth.token' /sandbox/.openclaw/openclaw.json

Signed-off-by: ColinM-sys cmcdonough@50words.com

Summary by CodeRabbit

  • New Features

    • Dashboard URLs now show redacted gateway tokens (first 4 characters visible) for safer display.
    • Console output includes a concise hint to retrieve the full gateway token when needed.
  • Bug Fixes / UX

    • Simplified token-present console label to reduce noise and confusion.
  • Tests

    • Updated tests to expect redacted URLs and the new retrieval hint.

The gateway auth token was printed in plaintext as part of the
dashboard URL (#token=<64-char-hex>) during onboard. This token
controls full access to the OpenClaw dashboard — agent commands,
workspace read/write, and action execution.

In CI/CD environments (GitHub Actions, GitLab CI), where
--non-interactive onboard runs, this token persists in build logs
that are often readable by all project members or publicly visible
on public repos. Terminal scrollback and piped output also retain
the plaintext token.

Redact the token in console output by showing only the first 4
characters. Users who need the full token can retrieve it via:
  nemoclaw <name> connect
  jq '.gateway.auth.token' /sandbox/.openclaw/openclaw.json

The buildControlUiUrls function gains a forDisplay parameter:
- forDisplay=true: token redacted (safe for stdout/logs)
- forDisplay=false (default): full token (backward compatible)

Signed-off-by: ColinM-sys <cmcdonough@50words.com>
@coderabbitai

coderabbitai Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

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

printDashboardUi now uses the real sandboxName, requests display-safe URLs when a gateway token exists (via a new forDisplay flag), prints token-redacted UI links, and emits a jq command to retrieve the full gateway token; buildControlUiUrls can now return redacted tokens for display.

Changes

Cohort / File(s) Summary
Dashboard URL logic
src/lib/dashboard.ts
Added token redaction helper and extended buildControlUiUrls(token, port, forDisplay = false) to return URLs with a masked token fragment when forDisplay is true.
Onboard CLI output
src/lib/agent-onboard.ts
printDashboardUi now uses sandboxName param, calls deps.buildControlUiUrls(token, info.port, true) when a token exists, simplifies console label, and prints a jq command to fetch the full gateway token.
Tests / Helpers
src/lib/agent-onboard.test.ts, src/lib/dashboard.test.ts
Updated test helper buildUrlsLoopback to accept forDisplay; adjusted assertions to expect redacted #token=... fragments, absence of prior token-warning text, and added tests for masked-token behavior (including fully masked short tokens).

Sequence Diagram

sequenceDiagram
    actor User
    participant Onboard as agent-onboard
    participant Dashboard as dashboard
    participant Shell as CLI

    User->>Onboard: request onboarding / UI URLs
    Onboard->>Dashboard: buildControlUiUrls(token, port, forDisplay=true)
    Dashboard->>Dashboard: redact token -> maskedToken
    Dashboard-->>Onboard: return URLs containing maskedToken
    Onboard->>Shell: print redacted UI URLs
    Onboard->>Shell: print jq command to retrieve full token
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I tuck the secret, show four peeks and hide the rest away,
I print the UI with gentle hush and tell you how to say:
"Run jq to fetch the full key" — a hop, a hint, a nudge so small,
Masked in logs, the token waits; unveil it only when you call.

🚥 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 accurately describes the main security fix: redacting the gateway auth token from console output. It is specific, concise, and directly reflects the primary change in the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
src/lib/agent-onboard.ts (1)

220-220: Make the token retrieval command directly runnable.

Use the actual sandbox name instead of <sandbox> so users can copy/paste the command without manual edits.

✏️ Suggested tweak
 export function printDashboardUi(
-  _sandboxName: string,
+  sandboxName: string,
   token: string | null,
   agent: AgentDefinition,
   deps: {
@@
-    console.log(`  To get the full token: nemoclaw <sandbox> connect → jq '.gateway.auth.token' /sandbox/.openclaw/openclaw.json`);
+    console.log(`  To get the full token: nemoclaw ${sandboxName} connect → jq '.gateway.auth.token' /sandbox/.openclaw/openclaw.json`);

Also applies to: 239-239

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

In `@src/lib/agent-onboard.ts` at line 220, The token-retrieval shell command
currently embeds the literal placeholder "<sandbox>" making it non-runnable;
replace that placeholder with the actual sandbox name variable (e.g.,
_sandboxName) using string interpolation or template literals wherever the
command is constructed (references around _sandboxName at the spots flagged,
including the second occurrence near line 239) so the emitted command contains
the real sandbox name and is copy/paste runnable.
🤖 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/dashboard.ts`:
- Around line 45-47: The redactToken helper currently returns short tokens
unredacted and only masks up to 20 characters of longer tokens; update
redactToken(token: string, forDisplay: boolean) so that when forDisplay is true
it always masks all characters except the first four (if token.length > 4) and
masks the entire token when token.length <= 4, i.e., replace the
Math.min(...,20) logic with token.length - 4 (or full token length for short
tokens) so no token data can leak; touch the redactToken function and its
parameters (token, forDisplay) to implement this behavior.

---

Nitpick comments:
In `@src/lib/agent-onboard.ts`:
- Line 220: The token-retrieval shell command currently embeds the literal
placeholder "<sandbox>" making it non-runnable; replace that placeholder with
the actual sandbox name variable (e.g., _sandboxName) using string interpolation
or template literals wherever the command is constructed (references around
_sandboxName at the spots flagged, including the second occurrence near line
239) so the emitted command contains the real sandbox name and is copy/paste
runnable.
🪄 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: 45d36868-22ac-480b-8f64-dedca3a2b6f3

📥 Commits

Reviewing files that changed from the base of the PR and between 56256a2 and 7d70feb.

📒 Files selected for processing (2)
  • src/lib/agent-onboard.ts
  • src/lib/dashboard.ts

Comment thread src/lib/dashboard.ts Outdated
Signed-off-by: ColinM-sys <cmcdonough@50words.com>
@wscurran

Copy link
Copy Markdown
Contributor

✨ Thanks for submitting this PR that proposes a fix to redact the gateway auth token from console output, which could help improve the security of NemoClaw and prevent sensitive information from being exposed.

@cjagwani cjagwani self-assigned this Apr 16, 2026
@cjagwani
cjagwani requested a review from cv April 16, 2026 21:35
@jyaunches jyaunches self-assigned this Apr 23, 2026
Use dashboard label from main while keeping token-redaction behavior
from this branch. The 'save it now' hint is no longer needed since
the gateway token is now redacted from console output.
@copy-pr-bot

copy-pr-bot Bot commented Apr 23, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

The test from main expected the 'save-now' warning for tokenized URLs.
Since this PR redacts the gateway token from console output, update the
test to verify:
- Token is redacted in displayed URLs
- Retrieval hint is printed instead of save-now warning
- buildUrlsLoopback mock respects the forDisplay parameter

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/lib/agent-onboard.test.ts (1)

93-93: Consider asserting the full token-retrieval command text.

At Line 93, the assertion only checks the nemoclaw sandbox-y connect prefix. You may want to assert the full jq hint string too, so regressions in the retrieval instructions are caught.

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

In `@src/lib/agent-onboard.test.ts` at line 93, The test currently uses
expect(output).toContain with only the "To get the full token: nemoclaw
sandbox-y connect" prefix which is too weak; update the assertion on the output
variable (the expect(output).toContain call) to assert the entire
token-retrieval hint string including the pipe to jq and the '.token' field
(i.e., the full command users should run) so the test fails if the retrieval
instructions change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/lib/agent-onboard.test.ts`:
- Line 93: The test currently uses expect(output).toContain with only the "To
get the full token: nemoclaw sandbox-y connect" prefix which is too weak; update
the assertion on the output variable (the expect(output).toContain call) to
assert the entire token-retrieval hint string including the pipe to jq and the
'.token' field (i.e., the full command users should run) so the test fails if
the retrieval instructions change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 0e61c52c-a886-43e5-884a-1e98dac1a4d3

📥 Commits

Reviewing files that changed from the base of the PR and between 30fa7aa and e400721.

📒 Files selected for processing (1)
  • src/lib/agent-onboard.test.ts

Address CodeRabbit review: tokens <=4 chars were returned unredacted,
and longer tokens only masked up to 20 trailing characters. Now short
tokens are fully masked and longer tokens mask all characters after the
first four. Added test coverage for forDisplay redaction behavior.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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/dashboard.ts`:
- Around line 65-68: getDashboardAccessInfo is calling buildControlUiUrls(token,
dashboardPort) which leaves forDisplay=false and embeds the raw token in URLs;
update the call in getDashboardAccessInfo to pass forDisplay=true (i.e., call
buildControlUiUrls(token, dashboardPort, true)) so the token is redacted before
those URLs are mapped/printed, ensuring you reference the same token and
dashboardPort variables used in the existing call.
🪄 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: 499ae399-f242-47ff-9cad-5d4efddc7dd2

📥 Commits

Reviewing files that changed from the base of the PR and between e400721 and 63d663a.

📒 Files selected for processing (2)
  • src/lib/dashboard.test.ts
  • src/lib/dashboard.ts
✅ Files skipped from review due to trivial changes (1)
  • src/lib/dashboard.test.ts

Comment thread src/lib/dashboard.ts
Comment on lines +65 to +68
forDisplay: boolean = false,
): string[] {
const hash = token ? `#token=${token}` : "";
const displayToken = token ? redactToken(token, forDisplay) : "";
const hash = token ? `#token=${displayToken}` : "";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find call sites still using the 2-arg form (defaults to forDisplay=false)
ast-grep --lang ts --pattern 'buildControlUiUrls($TOKEN, $PORT)'

# Inspect display/logging flows around dashboard guidance and URL printing
rg -n -C4 'getDashboardAccessInfo|getDashboardGuidanceLines|console\.log|buildControlUiUrls\(' src/lib/onboard.ts src/lib/agent-onboard.ts

Repository: NVIDIA/NemoClaw

Length of output: 50371


🏁 Script executed:

# Get context around line 6193 in onboard.ts
head -n 6210 src/lib/onboard.ts | tail -n 30

Repository: NVIDIA/NemoClaw

Length of output: 1096


🏁 Script executed:

# Get context around line 6263 in onboard.ts
head -n 6280 src/lib/onboard.ts | tail -n 30

Repository: NVIDIA/NemoClaw

Length of output: 1208


🏁 Script executed:

# Check the buildControlUiUrls function signature
rg -A 10 'function buildControlUiUrls|export.*buildControlUiUrls' src/lib/dashboard.ts

Repository: NVIDIA/NemoClaw

Length of output: 574


🏁 Script executed:

# Find all calls to buildControlUiUrls and check if results are used in console.log/print contexts
rg -B 3 -A 3 'buildControlUiUrls' src/lib/onboard.ts | head -100

Repository: NVIDIA/NemoClaw

Length of output: 1298


🏁 Script executed:

# Find where getDashboardAccessInfo is called and used
rg -B 5 -A 5 'getDashboardAccessInfo' src/lib/onboard.ts src/lib/agent-onboard.ts

Repository: NVIDIA/NemoClaw

Length of output: 1957


🏁 Script executed:

# Check what buildAuthenticatedDashboardUrl does - is it redacting tokens?
rg -A 15 'function buildAuthenticatedDashboardUrl|export.*buildAuthenticatedDashboardUrl' src/lib/dashboard.ts

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# Search for any other 2-arg calls to buildControlUiUrls in display contexts
rg -B 5 'buildControlUiUrls\([^,]+,[^,)]+\)' src/lib/onboard.ts | grep -A 5 -B 5 'console\|log\|print'

Repository: NVIDIA/NemoClaw

Length of output: 548


🏁 Script executed:

# Check buildAuthenticatedDashboardUrl implementation
rg -A 20 'buildAuthenticatedDashboardUrl' src/lib/dashboard.ts | head -50

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# Check getDashboardGuidanceLines - does it print the URLs?
rg -A 15 'getDashboardGuidanceLines' src/lib/dashboard.ts

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# See where getDashboardGuidanceLines output is used
rg -B 3 -A 3 'getDashboardGuidanceLines' src/lib/onboard.ts

Repository: NVIDIA/NemoClaw

Length of output: 760


🏁 Script executed:

# Search for buildAuthenticatedDashboardUrl in dashboard.ts
cat src/lib/dashboard.ts | grep -A 20 'buildAuthenticatedDashboardUrl'

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# Get the full getDashboardGuidanceLines implementation
sed -n '/^function getDashboardGuidanceLines/,/^}/p' src/lib/dashboard.ts | head -100

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# Trace: show what's in guidanceLines that gets printed
rg -A 10 'const guidanceLines = getDashboardGuidanceLines' src/lib/onboard.ts

Repository: NVIDIA/NemoClaw

Length of output: 606


🏁 Script executed:

# Find where guidanceLines is printed
rg -A 20 'guidanceLines' src/lib/onboard.ts | grep -A 20 'console.log'

Repository: NVIDIA/NemoClaw

Length of output: 2112


🏁 Script executed:

# More thorough search for guidanceLines usage
rg 'guidanceLines' src/lib/onboard.ts

Repository: NVIDIA/NemoClaw

Length of output: 206


🏁 Script executed:

# Get the full buildAuthenticatedDashboardUrl function
rg -B 2 'export.*buildAuthenticatedDashboardUrl|function buildAuthenticatedDashboardUrl' src/lib/dashboard.ts -A 15

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# Also check if it's in a different file
find src -name "*.ts" | xargs grep -l "buildAuthenticatedDashboardUrl"

Repository: NVIDIA/NemoClaw

Length of output: 77


🏁 Script executed:

# Let's look at what buildAuthenticatedDashboardUrl actually does with the token
rg 'buildAuthenticatedDashboardUrl' -B 5 -A 20 src/lib/dashboard.ts

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# Search all TypeScript files for buildAuthenticatedDashboardUrl definition
rg 'buildAuthenticatedDashboardUrl' src/lib/ -B 3 -A 20

Repository: NVIDIA/NemoClaw

Length of output: 5054


🏁 Script executed:

# Check dashboard.ts file for all exports
grep -E 'export (function|const)' src/lib/dashboard.ts

Repository: NVIDIA/NemoClaw

Length of output: 141


🏁 Script executed:

# Look for the actual implementation by searching patterns
ast-grep --pattern 'function buildAuthenticatedDashboardUrl' src/lib/

Repository: NVIDIA/NemoClaw

Length of output: 309


🏁 Script executed:

# Verify: check what redactToken does
rg -A 10 'function redactToken|export.*redactToken' src/lib/dashboard.ts

Repository: NVIDIA/NemoClaw

Length of output: 411


🏁 Script executed:

# Double-check the flow: getDashboardAccessInfo returns what?
sed -n '6189,6215p' src/lib/onboard.ts

Repository: NVIDIA/NemoClaw

Length of output: 1088


🏁 Script executed:

# Confirm that dashboardAccess entries are indeed printed with full tokens
sed -n '6270,6280p' src/lib/onboard.ts

Repository: NVIDIA/NemoClaw

Length of output: 344


Pass forDisplay=true to buildControlUiUrls() in getDashboardAccessInfo to prevent token leakage.

At line 6193, getDashboardAccessInfo calls buildControlUiUrls(token, dashboardPort) without the forDisplay argument, which defaults to false. This causes unredacted tokens to be embedded in URLs that are subsequently printed to console at lines 6278–6280. The full token then appears in terminal scrollback and CI/CD logs.

Change line 6193 to:

const dashboardAccess = buildControlUiUrls(token, dashboardPort, true).map((url, index) => ({

This aligns with the pattern already correctly implemented in agent-onboard.ts:261 where dashboard URLs are printed with forDisplay=true.

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

In `@src/lib/dashboard.ts` around lines 65 - 68, getDashboardAccessInfo is calling
buildControlUiUrls(token, dashboardPort) which leaves forDisplay=false and
embeds the raw token in URLs; update the call in getDashboardAccessInfo to pass
forDisplay=true (i.e., call buildControlUiUrls(token, dashboardPort, true)) so
the token is redacted before those URLs are mapped/printed, ensuring you
reference the same token and dashboardPort variables used in the existing call.

@jyaunches

Copy link
Copy Markdown
Contributor

Closing this PR — thank you @ColinM-sys for identifying the token leakage problem. The security concern is real and appreciated.

Since this was opened, three major refactors have landed that restructure the code this PR targets:

The unified redact() function already handles #token=<hex> via its CONTEXT_PATTERNS — the only remaining gap is that the two dashboard print paths (printDashboard in onboard.ts and printDashboardUi in agent-onboard.ts) use raw console.log() instead of routing through redact(). That's now a ~3-line fix against current main.

Filed #2467 to track the remaining work. Thanks again for flagging this.

@jyaunches jyaunches closed this Apr 25, 2026
@wscurran wscurran added bug-fix PR fixes a bug or regression and removed priority: high labels Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug-fix PR fixes a bug or regression

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants