Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions bin/lib/debug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

module.exports = require("../../dist/lib/debug");
51 changes: 42 additions & 9 deletions bin/nemoclaw.js
Original file line number Diff line number Diff line change
Expand Up @@ -789,15 +789,48 @@ function stop() {
}

function debug(args) {
const result = spawnSync("bash", [path.join(SCRIPTS, "debug.sh"), ...args], {
stdio: "inherit",
cwd: ROOT,
env: {
...process.env,
SANDBOX_NAME: registry.listSandboxes().defaultSandbox || "",
},
});
exitWithSpawnResult(result);
const { runDebug } = require("./lib/debug");
const opts = {};
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case "--help":
case "-h":
console.log("Collect NemoClaw diagnostic information\n");
console.log("Usage: nemoclaw debug [--quick] [--output FILE] [--sandbox NAME]\n");
console.log("Options:");
console.log(" --quick, -q Only collect minimal diagnostics");
console.log(" --output, -o FILE Write a tarball to FILE");
console.log(" --sandbox NAME Target sandbox name");
process.exit(0);
break;
case "--quick":
case "-q":
opts.quick = true;
break;
case "--output":
case "-o":
if (!args[i + 1] || args[i + 1].startsWith("-")) {
console.error("Error: --output requires a file path argument");
process.exit(1);
}
opts.output = args[++i];
break;
case "--sandbox":
if (!args[i + 1] || args[i + 1].startsWith("-")) {
console.error("Error: --sandbox requires a name argument");
process.exit(1);
}
opts.sandboxName = args[++i];
break;
default:
console.error(`Unknown option: ${args[i]}`);
process.exit(1);
}
}
if (!opts.sandboxName) {
opts.sandboxName = registry.listSandboxes().defaultSandbox || undefined;
}
runDebug(opts);
}

function uninstall(args) {
Expand Down
52 changes: 52 additions & 0 deletions src/lib/debug.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, it, expect } from "vitest";
// Import from compiled dist/ so coverage is attributed correctly.
import { redact } from "../../dist/lib/debug";

describe("redact", () => {
it("redacts NVIDIA_API_KEY=value patterns", () => {
const key = ["NVIDIA", "API", "KEY"].join("_");
expect(redact(`${key}=some-value`)).toBe(`${key}=<REDACTED>`);
});

it("redacts generic KEY/TOKEN/SECRET/PASSWORD env vars", () => {
expect(redact("API_KEY=secret123")).toBe("API_KEY=<REDACTED>");
expect(redact("MY_TOKEN=tok_abc")).toBe("MY_TOKEN=<REDACTED>");
expect(redact("DB_PASSWORD=hunter2")).toBe("DB_PASSWORD=<REDACTED>");
expect(redact("MY_SECRET=s3cret")).toBe("MY_SECRET=<REDACTED>");
expect(redact("CREDENTIAL=cred")).toBe("CREDENTIAL=<REDACTED>");
});

it("redacts nvapi- prefixed keys", () => {
expect(redact("using key nvapi-AbCdEfGhIj1234")).toBe("using key <REDACTED>");
});

it("redacts classic GitHub personal access tokens (ghp_)", () => {
expect(redact("token: ghp_" + "a".repeat(36))).toBe("token: <REDACTED>");
});

it("redacts fine-grained GitHub personal access tokens (github_pat_)", () => {
expect(redact("token: github_pat_" + "A".repeat(40))).toBe("token: <REDACTED>");
});

it("redacts Bearer tokens", () => {
expect(redact("Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.payload.sig")).toBe(
"Authorization: Bearer <REDACTED>",
);
});

it("handles multiple patterns in one string", () => {
const input = "API_KEY=secret nvapi-abcdefghijk Bearer tok123";
const result = redact(input);
expect(result).not.toContain("secret");
expect(result).not.toContain("nvapi-abcdefghijk");
expect(result).not.toContain("tok123");
});

it("leaves clean text unchanged", () => {
const clean = "Hello world, no secrets here";
expect(redact(clean)).toBe(clean);
});
});
Loading
Loading