Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ navigation:
- page: "Credential Storage"
path: _build/agent-variants/security/credential-storage.openclaw.generated.mdx
slug: credential-storage
- page: "Credential Rotation"
path: _build/agent-variants/security/credential-rotation.openclaw.generated.mdx
slug: credential-rotation
- page: "Trusted Computing Base"
path: _build/agent-variants/security/tcb-boundary.openclaw.generated.mdx
slug: trusted-computing-base
Expand Down Expand Up @@ -427,6 +430,9 @@ navigation:
- page: "Credential Storage"
path: _build/agent-variants/security/credential-storage.hermes.generated.mdx
slug: credential-storage
- page: "Credential Rotation"
path: _build/agent-variants/security/credential-rotation.hermes.generated.mdx
slug: credential-rotation
- page: "Trusted Computing Base"
path: _build/agent-variants/security/tcb-boundary.hermes.generated.mdx
slug: trusted-computing-base
Expand Down
17 changes: 13 additions & 4 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1454,10 +1454,19 @@ Do not log it, share it, or commit it to version control.
</AgentOnly>
<AgentOnly variant="hermes">

`gateway-token` is not applicable to Hermes sandboxes.
Hermes API access uses bearer-token authentication configured through the Hermes runtime, not the OpenClaw gateway token.
For browser access to the dashboard, use `nemohermes my-assistant dashboard-url`; Hermes dashboard auth is read from the in-sandbox config (`/sandbox/.hermes/config.yaml`), not a gateway token.
If you need the endpoint for an OpenAI-compatible client, use `nemohermes my-assistant status` and the API URL it reports.
Print the Hermes API bearer token for a running sandbox to stdout.
NemoClaw retrieves the sandbox's `API_SERVER_KEY`, which authenticates OpenAI-compatible clients on the forwarded API port.
Capture the token and pass it in the `Authorization` header:

```bash
TOKEN=$(nemohermes my-assistant gateway-token --quiet)
curl -fsS -H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:8642/v1/models
```

Treat the token like a password.
Do not log it, share it, or commit it to version control.
For browser access to the dashboard, use `nemohermes my-assistant dashboard-url`.

</AgentOnly>
<AgentOnly variant="deepagents">
Expand Down
239 changes: 239 additions & 0 deletions docs/security/credential-rotation.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
---
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
title: "Credential Rotation"
sidebar-title: "Credential Rotation"
description: "Rotate inference, messaging, and web search credentials through the supported NemoClaw workflows."
description-agent: "Step-by-step guide for rotating inference API keys, messaging tokens, and web search credentials in NemoClaw. Use when a key expires, is compromised, or must be replaced."
keywords: ["nemoclaw credential rotation", "rotate api key", "update inference key", "messaging token", "nemoclaw credentials reset"]
content:
type: "how_to"
---
import { AgentOnly } from "../_components/AgentGuide";

NemoClaw uses different rotation paths for inference, messaging, and web search credentials.
Inference credentials can normally be updated while reusing the existing sandbox.
Messaging tokens and web search settings require a rebuild or recreation because their configuration is applied when the sandbox image starts.

## Before You Start

List the provider names registered with the OpenShell gateway.

```bash
$$nemoclaw credentials list
```

The output is authoritative for commands that accept an OpenShell provider name.
An inference provider is commonly named `nvidia-prod`, while an onboarded web search provider is commonly named `<sandbox>-brave-search` or `<sandbox>-tavily-search`.
Provider names can differ with your selected inference route and sandbox configuration.

Per-sandbox messaging bridge names are not resettable credentials.
Use `channels add`, `channels remove`, or `channels stop` for messaging integrations instead of passing a bridge name to `credentials reset`.

| Credential | Supported rotation path | Sandbox impact |
|---|---|---|
| Inference API key | Rerun onboarding with the replacement value | The existing sandbox can normally be reused unless onboarding detects unrelated configuration drift |
| Slack, Telegram, or Discord token | Re-add the channel, then rebuild | Rebuild required |
| Brave or Tavily web search key | Rerun onboarding with the selected web search provider | Sandbox recreation required |

## Rotate an Inference API Key

Supply the replacement key and rerun onboarding for the existing sandbox.
Read replacement credentials silently on a trusted host so their values do not enter shell history or terminal scrollback.
Unset each variable after the command finishes.

```bash
printf 'New NVIDIA inference API key: ' >&2
IFS= read -r -s NVIDIA_INFERENCE_API_KEY
printf '\n' >&2
export NVIDIA_INFERENCE_API_KEY
$$nemoclaw onboard --name <sandbox> \
--non-interactive --yes --yes-i-accept-third-party-software
unset NVIDIA_INFERENCE_API_KEY
```

Onboarding updates the selected OpenShell inference provider and reuses the sandbox when its recorded configuration is still compatible.
If onboarding detects other configuration drift, review the requested rebuild or recreation before continuing.

For an interactive rotation, export the replacement key and run `$$nemoclaw onboard --name <sandbox>` without the non-interactive flags.

## Rotate a Messaging Token

Re-adding an existing channel overwrites its stored credentials.
The replacement takes effect only after a rebuild because messaging providers are resolved when the sandbox starts.

### Slack

Slack requires both replacement tokens.

```bash
printf 'New Slack bot token: ' >&2
IFS= read -r -s SLACK_BOT_TOKEN
printf '\n' >&2
printf 'New Slack app token: ' >&2
IFS= read -r -s SLACK_APP_TOKEN
printf '\n' >&2
export SLACK_BOT_TOKEN SLACK_APP_TOKEN
NEMOCLAW_NON_INTERACTIVE=1 $$nemoclaw <sandbox> channels add slack
unset SLACK_BOT_TOKEN SLACK_APP_TOKEN
$$nemoclaw <sandbox> rebuild --yes
```

### Telegram

```bash
printf 'New Telegram bot token: ' >&2
IFS= read -r -s TELEGRAM_BOT_TOKEN
printf '\n' >&2
export TELEGRAM_BOT_TOKEN
NEMOCLAW_NON_INTERACTIVE=1 $$nemoclaw <sandbox> channels add telegram
unset TELEGRAM_BOT_TOKEN
$$nemoclaw <sandbox> rebuild --yes
```

### Discord

```bash
printf 'New Discord bot token: ' >&2
IFS= read -r -s DISCORD_BOT_TOKEN
printf '\n' >&2
export DISCORD_BOT_TOKEN
NEMOCLAW_NON_INTERACTIVE=1 $$nemoclaw <sandbox> channels add discord
unset DISCORD_BOT_TOKEN
$$nemoclaw <sandbox> rebuild --yes
```

Omit `NEMOCLAW_NON_INTERACTIVE=1` and the token variables if you want `channels add` to prompt for replacement values and offer the rebuild interactively.

## Rotate a Web Search Key

Web search provider configuration and credential attachment are baked into the sandbox image.
Select the provider again and recreate the sandbox so the replacement becomes active.

<AgentOnly variant="openclaw">

OpenClaw supports Brave or Tavily through NemoClaw onboarding.

```bash
printf 'New Brave API key: ' >&2
IFS= read -r -s BRAVE_API_KEY
printf '\n' >&2
export BRAVE_API_KEY
NEMOCLAW_WEB_SEARCH_PROVIDER=brave \
$$nemoclaw onboard --fresh --name <sandbox> --recreate-sandbox \
--non-interactive --yes --yes-i-accept-third-party-software
unset BRAVE_API_KEY
```

To rotate a Tavily key instead, read and export `TAVILY_API_KEY` silently and select `NEMOCLAW_WEB_SEARCH_PROVIDER=tavily`.

</AgentOnly>
<AgentOnly variant="hermes">

Hermes uses Tavily for NemoClaw-managed web search.
Brave is not a supported Hermes web search backend.

```bash
printf 'New Tavily API key: ' >&2
IFS= read -r -s TAVILY_API_KEY
printf '\n' >&2
export TAVILY_API_KEY
NEMOCLAW_WEB_SEARCH_PROVIDER=tavily \
$$nemoclaw onboard --fresh --name <sandbox> --recreate-sandbox \
--non-interactive --yes --yes-i-accept-third-party-software
unset TAVILY_API_KEY
```

</AgentOnly>

## Remove and Re-register a Provider Credential

Use `credentials reset` only when you need to remove an inference or web search provider before its replacement is available.
Run `$$nemoclaw credentials list` first, then pass the exact provider name from that output.

```bash
$$nemoclaw credentials reset nvidia-prod --yes
```

Removing a provider interrupts requests that depend on it.
Re-register an inference provider with the inference rotation command above.
For web search, repeat the matching web search onboarding flow and recreate the sandbox.

Do not pass a messaging bridge name to `credentials reset`.
Use the messaging rotation flow above to replace a token, or `$$nemoclaw <sandbox> channels remove <channel>` to retire the integration.

## Emergency Rotation After Key Compromise

If a credential was exposed, act in this order:

1. Revoke the exposed credential at the upstream provider before relying on any local cleanup.
2. For inference or web search, remove the exact provider shown by `$$nemoclaw credentials list` if a replacement is not immediately available.
3. For messaging, use `channels remove` to retire the integration or re-add the channel with a replacement token and rebuild.
4. Issue a replacement credential at the upstream provider.
5. Follow the matching rotation procedure on this page.
6. Complete a real request through the affected inference, search, or messaging integration.
7. Review recent sandbox logs for unexpected authentication failures or use of the retired credential:

```bash
$$nemoclaw <sandbox> logs --since 24h | grep -i "auth\|401\|403\|forbidden"
```

## Rotate an Inference Key in CI/CD

Supply credentials through the CI system's secret store and inject them as environment variables.
Non-interactive onboarding must include the third-party software acceptance flag.

```yaml
- name: Rotate inference key
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: >-
$$nemoclaw onboard --name <sandbox>
--non-interactive --yes --yes-i-accept-third-party-software
```

Do not commit credential values or host-side NemoClaw state to the repository.
Plan for rebuild downtime when automating messaging rotation and recreation downtime when automating web search rotation.

## Verify the Replacement

`$$nemoclaw credentials list` confirms that a provider exists, but it does not reveal or validate the stored value.
`$$nemoclaw status` performs reachability checks without sending cloud API keys, so even an HTTP `401` or `403` can count as reachable.
`$$nemoclaw inference get` reports the active route and does not authenticate a model request.

Complete a real request through the rotated integration before declaring the rotation successful.

<AgentOnly variant="openclaw">

Verify an inference key by running an isolated OpenClaw turn and confirming that it returns the requested content.

```bash
$$nemoclaw <sandbox> agent --session-id credential-check \
-m "Reply with only: credential-check-ok"
```

</AgentOnly>
<AgentOnly variant="hermes">

Verify an inference key by forwarding the Hermes API and making a chat-completions request with the onboarded model.

```bash
TOKEN=$($$nemoclaw <sandbox> gateway-token --quiet)
openshell forward start --background 8642 <sandbox>
curl -sN http://127.0.0.1:8642/v1/chat/completions \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{"model":"<onboarded-model>","messages":[{"role":"user","content":"Reply with credential-check-ok"}],"stream":false}'
```

</AgentOnly>

To verify messaging, send a test message from an allowed account and confirm the sandbox receives it and responds.
To verify web search, ask the agent to perform a search and confirm that the tool returns current results without an authentication error.

## Related Pages

- [Credential Storage](credential-storage): how NemoClaw stores and protects credentials.
- [Runtime Controls](../manage-sandboxes/runtime-controls): which changes take effect at runtime and which require recreation.
- [Security Best Practices](best-practices): recommended key scopes and operational hygiene.
- [Switch Inference Providers](../inference/switch-inference-providers): change the inference provider rather than rotate its key.
95 changes: 95 additions & 0 deletions test/credential-rotation-docs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { readFileSync } from "node:fs";
import path from "node:path";

import { describe, expect, it } from "vitest";

const DOC_PATH = "docs/security/credential-rotation.mdx";

function readGuide(): string {
return readFileSync(path.join(process.cwd(), DOC_PATH), "utf8");
}

function fencedBlocks(text: string, language: string): string[] {
const pattern = new RegExp("```" + language + "\\n([\\s\\S]*?)```", "g");
return [...text.matchAll(pattern)].map((match) => match[1] ?? "");
}

describe("credential rotation documentation", () => {
it("keeps every non-interactive onboard example executable", () => {
const examples = [
...fencedBlocks(readGuide(), "bash"),
...fencedBlocks(readGuide(), "yaml"),
].filter((block) => block.includes("onboard") && block.includes("--non-interactive"));

expect(examples.length).toBeGreaterThan(0);
for (const example of examples) {
expect(example).toContain("--name <sandbox>");
expect(example).toContain("--yes-i-accept-third-party-software");
}
});

it("uses normal onboarding instead of interrupted-session resume", () => {
expect(readGuide()).not.toContain("--resume");
});

it("keeps replacement credentials out of command text (#6266)", () => {
const guide = readGuide();
const credentialVariables = [
"NVIDIA_INFERENCE_API_KEY",
"SLACK_BOT_TOKEN",
"SLACK_APP_TOKEN",
"TELEGRAM_BOT_TOKEN",
"DISCORD_BOT_TOKEN",
"BRAVE_API_KEY",
"TAVILY_API_KEY",
];

for (const variable of credentialVariables) {
expect(guide).toMatch(new RegExp(`IFS= read -r -s ${variable}`));
expect(guide).toMatch(new RegExp(`unset [^\\n]*\\b${variable}\\b`));
expect(guide).not.toMatch(new RegExp(`${variable}=[^\\s$]`));
}
});

it("documents messaging rebuilds and web search recreation", () => {
const guide = readGuide();
const bash = fencedBlocks(guide, "bash");

for (const channel of ["slack", "telegram", "discord"]) {
const example = bash.find((block) => block.includes(`channels add ${channel}`));
expect(example, channel).toBeDefined();
expect(example, channel).toContain("rebuild --yes");
}

const searchExamples = bash.filter((block) => block.includes("NEMOCLAW_WEB_SEARCH_PROVIDER"));
expect(searchExamples.length).toBeGreaterThan(0);
for (const example of searchExamples) {
expect(example).toContain("--fresh");
expect(example).toContain("--recreate-sandbox");
}
});

it("uses real provider names and separates configuration checks from live proof", () => {
const guide = readGuide();

expect(guide).toContain("credentials reset nvidia-prod --yes");
expect(guide).toContain("Per-sandbox messaging bridge names are not resettable credentials");
expect(guide).toContain("Complete a real request through the rotated integration");
expect(guide).not.toContain("alpha-nvidia-inference");
expect(guide).not.toContain("alpha-slack");
expect(guide).not.toContain("PROVIDER_KEY=new-value");
});

it("authenticates the Hermes verification request", () => {
const example = fencedBlocks(readGuide(), "bash").find((block) =>
block.includes("/v1/chat/completions"),
);

expect(example).toBeDefined();
expect(example).toContain("gateway-token --quiet");
expect(example).toContain("Authorization: Bearer $TOKEN");
});
});
Loading