Skip to content

fix(security): strip gateway token from descendants - #8872

Merged
cv merged 1 commit into
mainfrom
codex/fix-8693-dashboard-token
Aug 12, 2026
Merged

fix(security): strip gateway token from descendants#8872
cv merged 1 commit into
mainfrom
codex/fix-8693-dashboard-token

Conversation

@apurvvkumaria

@apurvvkumaria apurvvkumaria commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Managed OpenClaw gateway launches and automatic respawns no longer inherit OPENCLAW_GATEWAY_TOKEN, so descendants of those gateway processes do not receive that ambient environment value.

This is a bounded defense-in-depth improvement for #8693. It does not remove the token from OpenClaw configuration, the generated runtime environment file, or other same-UID sandbox-readable surfaces. The issue's host-side/injection-only credential design remains blocked on the OpenShell/OpenClaw runtime architecture and threat-model decision.

Related Issue

Refs #8693

Changes

  • Centralize root, non-root, and automatic-respawn gateway process launches behind one helper.
  • Remove OPENCLAW_GATEWAY_TOKEN with the absolute /usr/bin/env boundary before privilege step-down or OpenClaw execution.
  • Preserve the existing truncate-on-initial-launch and append-on-respawn gateway log behavior.
  • Reject unknown log modes before starting a process.
  • Add real-shell behavioral coverage for both log modes, token removal, and invalid-mode failure.

Type of Change

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

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Docs updated for user-facing behavior changes
  • Docs not applicable — justification: This changes an internal child-process environment boundary without changing a command, option, configuration surface, token location, or documented lifecycle. Existing documentation already treats the gateway token as a password.
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: Codex Desktop reviewed the completed diff against the nine-category security rubric. /usr/bin/env is absolute to avoid sandbox-user PATH substitution; "$@" preserves argv boundaries; only the ambient token is removed; launch PID identity, privilege step-down, redirection modes, restart accounting, and fail-closed invalid-mode handling are preserved. Real-shell tests prove the credential is absent in both launch modes and no process starts for an invalid mode. No new secret, auth bypass, dependency, configuration weakening, or cryptographic behavior is introduced.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Documentation Writer Review

  • Documentation writer subagent reviewed the completed changes
  • Result: no-docs-needed
  • Evidence: The diff changes only the managed OpenClaw gateway launch/respawn environment and focused tests. It does not change a user command, option, configuration surface, token location, or token lifecycle. Documenting sandbox-wide isolation would exceed the evidence because the token remains present in same-UID configuration/runtime surfaces.
  • Agent: Codex Desktop

DGX Station Hardware Evidence

  • Tested on DGX Station
  • Tested commit:
  • Station profile/scenario:
  • Result:
  • Supporting evidence:

Verification

  • PR description includes a valid Signed-off-by: line and the commit appears as Verified in GitHub
  • Normal pre-commit, commit-message, and pre-push hooks passed
  • Targeted behavior tests pass: 3 focused integration tests passed; CLI type checking passed
  • Applicable broad gate passed — command/result:
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Apurv Kumaria akumaria@nvidia.com

Summary by CodeRabbit

  • Bug Fixes

    • Improved gateway startup reliability across initial launches and automatic restarts.
    • Prevented gateway authentication tokens from being exposed to launched processes.
    • Added validation to stop startup when an unsupported logging mode is requested.
    • Improved gateway log handling for both fresh launches and respawns.
  • Tests

    • Added coverage for token protection, logging modes, and invalid startup configuration.

Refs #8693

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The gateway launch paths now use a shared helper. The helper controls log mode, removes OPENCLAW_GATEWAY_TOKEN, validates input, and records the child PID. Tests cover both log modes and invalid modes.

Changes

Gateway launch behavior

Layer / File(s) Summary
Shared launcher and gateway integrations
scripts/nemoclaw-start.sh, test/nemoclaw-start-gateway-token-env.test.ts
The shared launcher supports truncate and append logging, removes OPENCLAW_GATEWAY_TOKEN, captures the child PID, and rejects unknown modes. Root and non-root launches use truncate mode. Respawns use append mode. Tests cover token removal and invalid modes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested labels: area: sandbox, bug-fix

Suggested reviewers: cv

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the security fix that prevents gateway descendants from inheriting the gateway token.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-8693-dashboard-token

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

@github-code-quality

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit a19af89 in the codex/fix-8693-dashb... branch remains at 96%, unchanged from commit 9b4c285 in the main branch.

@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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/nemoclaw-start-gateway-token-env.test.ts`:
- Around line 16-42: Update the test around launch_openclaw_gateway_process to
seed gatewayLog with existing content before launching, then assert
mode-specific final contents: truncate must replace the seed with the command
output, while append must preserve the seed and add the output. Keep the
existing token-removal assertion and cleanup behavior unchanged.
🪄 Autofix

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: 73d44a78-11a0-4509-ab71-d6814e827448

📥 Commits

Reviewing files that changed from the base of the PR and between 5aaf22b and a19af89.

📒 Files selected for processing (2)
  • scripts/nemoclaw-start.sh
  • test/nemoclaw-start-gateway-token-env.test.ts

Comment on lines +16 to +42
it.each([
"truncate",
"append",
])("removes the dashboard token from a %s gateway launch (#8693)", (logMode) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-gateway-token-env-"));
const gatewayLog = path.join(tmpDir, "gateway.log");
const source = fs.readFileSync(START_SCRIPT, "utf8");
const launch = extractShellFunctionFromSource(
source,
"launch_openclaw_gateway_process",
).replaceAll("/tmp/gateway.log", gatewayLog);
const script = [
"set -euo pipefail",
launch,
"export OPENCLAW_GATEWAY_TOKEN=dashboard-secret",
`launch_openclaw_gateway_process ${logMode} sh -c 'printf "%s\\n" "\${OPENCLAW_GATEWAY_TOKEN-unset}"'`,
'wait "$GATEWAY_PID"',
].join("\n");

try {
const result = spawnSync("bash", ["-c", script], { encoding: "utf8", timeout: 5000 });
expect(result.status, result.stderr).toBe(0);
expect(fs.readFileSync(gatewayLog, "utf8")).toBe("unset\n");
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Test the log-mode effect.

The test uses an empty gateway log for both modes. It passes if append truncates the log or if truncate appends to it. Seed the log and assert the distinct final contents.

Proposed test update
     const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-gateway-token-env-"));
     const gatewayLog = path.join(tmpDir, "gateway.log");
+    fs.writeFileSync(gatewayLog, "existing\n");
     const source = fs.readFileSync(START_SCRIPT, "utf8");
@@
-      expect(fs.readFileSync(gatewayLog, "utf8")).toBe("unset\n");
+      expect(fs.readFileSync(gatewayLog, "utf8")).toBe(
+        logMode === "append" ? "existing\nunset\n" : "unset\n",
+      );

As per path instructions, “Review tests for behavioral confidence rather than implementation lock-in.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it.each([
"truncate",
"append",
])("removes the dashboard token from a %s gateway launch (#8693)", (logMode) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-gateway-token-env-"));
const gatewayLog = path.join(tmpDir, "gateway.log");
const source = fs.readFileSync(START_SCRIPT, "utf8");
const launch = extractShellFunctionFromSource(
source,
"launch_openclaw_gateway_process",
).replaceAll("/tmp/gateway.log", gatewayLog);
const script = [
"set -euo pipefail",
launch,
"export OPENCLAW_GATEWAY_TOKEN=dashboard-secret",
`launch_openclaw_gateway_process ${logMode} sh -c 'printf "%s\\n" "\${OPENCLAW_GATEWAY_TOKEN-unset}"'`,
'wait "$GATEWAY_PID"',
].join("\n");
try {
const result = spawnSync("bash", ["-c", script], { encoding: "utf8", timeout: 5000 });
expect(result.status, result.stderr).toBe(0);
expect(fs.readFileSync(gatewayLog, "utf8")).toBe("unset\n");
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
it.each([
"truncate",
"append",
])("removes the dashboard token from a %s gateway launch (#8693)", (logMode) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-gateway-token-env-"));
const gatewayLog = path.join(tmpDir, "gateway.log");
fs.writeFileSync(gatewayLog, "existing\n");
const source = fs.readFileSync(START_SCRIPT, "utf8");
const launch = extractShellFunctionFromSource(
source,
"launch_openclaw_gateway_process",
).replaceAll("/tmp/gateway.log", gatewayLog);
const script = [
"set -euo pipefail",
launch,
"export OPENCLAW_GATEWAY_TOKEN=dashboard-secret",
`launch_openclaw_gateway_process ${logMode} sh -c 'printf "%s\\n" "\${OPENCLAW_GATEWAY_TOKEN-unset}"'`,
'wait "$GATEWAY_PID"',
].join("\n");
try {
const result = spawnSync("bash", ["-c", script], { encoding: "utf8", timeout: 5000 });
expect(result.status, result.stderr).toBe(0);
expect(fs.readFileSync(gatewayLog, "utf8")).toBe(
logMode === "append" ? "existing\nunset\n" : "unset\n",
);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
🧰 Tools
🪛 ast-grep (0.45.1)

[warning] 21-21: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(START_SCRIPT, "utf8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)


[warning] 37-37: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(gatewayLog, "utf8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

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

In `@test/nemoclaw-start-gateway-token-env.test.ts` around lines 16 - 42, Update
the test around launch_openclaw_gateway_process to seed gatewayLog with existing
content before launching, then assert mode-specific final contents: truncate
must replace the seed with the command output, while append must preserve the
seed and add the output. Keep the existing token-removal assertion and cleanup
behavior unchanged.

Source: Path instructions

@cv
cv merged commit 09a4b22 into main Aug 12, 2026
71 of 75 checks passed
@cv
cv deleted the codex/fix-8693-dashboard-token branch August 12, 2026 07:54
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — No blocking findings reported

Advisor assessment: No blocking advisor findings reported
Next action: No advisor follow-up needed.
Findings: 0 blockers · 0 warnings · 0 suggestions

Model lanes

  • GPT-5.6 Terra (primary): Completed · high confidence · 0 blockers · 0 warnings · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Failed after a partial review · low confidence · 0 blockers · 0 warnings · 0 suggestions

Second-opinion terminology and E2E selections are advisory. Live E2E does not run automatically for pull requests.

1 semantic terminology decision

Terminology decisions are advisory. They affect the assessment only when a separate finding identifies concrete semantic impact.

  • established — dashboard token at test/nemoclaw-start-gateway-token-env.test.ts:19: Retain `dashboard token`; repository source and tests already use this term for the same credential.

E2E guidance

Advisory only. A maintainer can dispatch the default E2E suite for the commit under review.

Recommended E2E: None

Manual-only E2E: managed-image-multiarch-startup
The manual PR workflow does not run these selectors for the commit under review. Run them from reviewed code on main.

Workflow run details

This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge.

cv pushed a commit that referenced this pull request Aug 12, 2026
<!-- markdownlint-disable MD041 -->
## Summary

OpenClaw managed-image activation stopped before binding the gateway
port after #8872 correctly removed `OPENCLAW_GATEWAY_TOKEN` from the
gateway process environment.
The direct cause was OpenClaw 2026.7 classifying NemoClaw's freshly
authenticated config as clobbered because the atomic token rotation did
not stamp OpenClaw's `meta.lastTouchedAt` field.
OpenClaw then restored its tokenless build-time backup before gateway
authentication resolved.

This change stamps that metadata in the existing atomic config write, so
OpenClaw keeps the rotated authenticated config while the bearer remains
absent from both gateway argv and the gateway/descendant environment.
Gateway logging now also opens the fixed log path through a
descriptor-pinned, no-follow boundary under the final gateway identity.

## Related Issue

Refs #8693

## Changes

- Stamp `meta.lastTouchedAt` when rotating the gateway token through the
existing pinned-directory, no-symlink, owner-only temporary file,
`fsync`, and atomic-rename path.
- Keep the gateway launch command credential-free and continue removing
`OPENCLAW_GATEWAY_TOKEN` at the process boundary for initial launches
and automatic respawns.
- Open `/tmp/gateway.log` with `O_NOFOLLOW`, pin and verify the opened
inode before redirecting output, prevent gateway launch when safe
initial log replacement fails, and fail closed if a respawn encounters a
symlink or replaced path.
- In root mode, step down to the `gateway` identity before opening that
log descriptor while preserving `HOME=/sandbox` and umask `0007`.
- Add Linux process-level coverage that reads `/proc/<pid>/cmdline` and
`/proc/<pid>/environ` for both launch paths and rejects token exposure.
- Preserve and test truncate-on-initial-launch and append-on-respawn log
behavior, including replacement of a planted symlink at initial launch
and refusal of one at respawn.

## Type of Change

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

## Quality Gates

- [x] Tests added or updated for changed behavior
- [ ] Existing tests cover changed behavior — justification:
- [ ] Tests not applicable — justification:
- [x] Docs updated for user-facing behavior changes
- [ ] Docs not applicable — justification:
- [x] Sensitive paths changed (security, policy, credentials, preflight,
onboarding, inference, runner, sandbox, or messaging)
- [x] Sensitive-path review completed or maintainer-approved waiver
recorded — reviewer/approval link/justification: The final diff removes
the interim argv credential path entirely. The token remains in the
existing OpenClaw config and is rotated through the existing
symlink-rejecting, exclusive temporary file, `fsync`, atomic rename, and
hash-refresh flow. The gateway launcher removes
`OPENCLAW_GATEWAY_TOKEN`, steps down before the descriptor-safe opener
in root mode, opens the log with `O_NOFOLLOW`, verifies the opened
inode, and only then redirects and execs the gateway. Safe initial-log
replacement failure prevents launch. Linux process tests inspect both
argv and environment for initial launch and respawn; filesystem
regressions prove initial launch replaces a planted gateway-log symlink
without changing its target, refuses to launch on safe-replacement
failure, and refuses a respawn symlink. No input parsing, shell
interpolation, network bind, dependency, or cryptographic boundary is
expanded.
- [ ] Non-success, skipped, or missing CI check accepted by maintainer —
check name, approval link, and follow-up issue:

## Documentation Writer Review

- [x] Documentation writer subagent reviewed the completed changes
- Result: `docs-updated`
- Evidence: `docs/security/filesystem-controls.mdx` accurately documents
that the entrypoint retains the token path for interactive sandbox
shells while the OpenClaw launch boundary removes
`OPENCLAW_GATEWAY_TOKEN` from the gateway/descendant environment and
does not add the bearer to process arguments; the gateway reads it from
`openclaw.json`. The privilege-drop change moves the descriptor-safe
gateway-log opener under the `gateway` identity in root mode and
requires no additional public documentation.
- Agent: Codex Desktop
<!-- docs-review-head-sha: 495a433 -->
<!-- docs-review-agents-blob-sha:
c4923a3 -->

## DGX Station Hardware Evidence

- [ ] Tested on DGX Station
- Tested commit: Not applicable
- Station profile/scenario: Not applicable;
`scripts/prepare-dgx-station-host.sh` is unchanged.
- Result: Not applicable
- Supporting evidence: Not applicable

## Verification

- [x] PR description includes a `Signed-off-by:` line and every commit
appears as `Verified` in GitHub
- [x] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed
- [x] Targeted behavior tests pass for the current change set — the
focused changed-file review passed 149 tests with 3 platform skips; the
fail-closed safe-log suite passes 4/4, including refusal to launch after
initial log replacement failure. The exact published OpenClaw 2026.7.1
managed image passed all 32 affected gateway lifecycle, credential, and
log-safety tests, including live initial-launch and respawn `/proc`
assertions. The earlier exact-image activation reached HTTP 200,
preserved the rotated config token, and exposed the token in neither
`/proc/<pid>/cmdline` nor `/proc/<pid>/environ`.
- [x] Applicable broad gate passed — `npm run typecheck`, repository
checks, ShellCheck, secret scanning, source-shape budget, and
test-file-size budget passed through local validation and normal hooks.
- [x] Quality Gates section completed with required justifications or
waivers
- [x] No secrets, API keys, or credentials committed
- [x] `npm run docs` builds without warnings (doc changes only)
- [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)

`npm run docs` passes with 0 errors and the repository's 2 known
pre-existing Fern warnings.

---
Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>

---------

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Senthil Ravichandran <senthilr@nvidia.com>
Co-authored-by: Aaron Erickson <aerickson@nvidia.com>
Co-authored-by: Senthil Ravichandran <senthilr@nvidia.com>
@wscurran wscurran added the bug-fix PR fixes a bug or regression label Aug 25, 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.

3 participants