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
17 changes: 12 additions & 5 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ async function cmdInstall(force: boolean, nonInteractive: boolean): Promise<void
}

if (envApiKey) {
console.warn(`⚠ Using WANDB_API_KEY from environment: ${envApiKey.slice(0, 4)}…`);
console.warn(`⚠ Using WANDB_API_KEY from environment: ${maskSecret(envApiKey)}`);
} else if (!effectiveApiKey) {
console.warn('- WANDB_API_KEY not set. Run: weave-claude-code config set wandb_api_key <your-api-key>');
}
Expand Down Expand Up @@ -164,7 +164,7 @@ async function cmdInstall(force: boolean, nonInteractive: boolean): Promise<void
settings = loadSettings();
settings.wandb_api_key = value;
saveSettings(settings);
console.log(`✓ Set wandb_api_key = ${value.slice(0, 4)}…`);
console.log(`✓ Set wandb_api_key = ${maskSecret(value)}`);
} else {
console.log('- Skipped wandb_api_key (set later: weave-claude-code config set wandb_api_key <key>)');
}
Expand All @@ -186,6 +186,10 @@ async function cmdInstall(force: boolean, nonInteractive: boolean): Promise<void
// config
// ---------------------------------------------------------------------------

function maskSecret(value: string): string {
return `${value.slice(0, 4)}…`;
Comment thread
rgao-coreweave marked this conversation as resolved.
}

async function cmdConfig(args: string[]): Promise<void> {
const action = args[0];

Expand All @@ -211,7 +215,7 @@ async function cmdConfig(args: string[]): Promise<void> {
: settings.wandb_api_key
? 'settings.json'
: 'not set';
const apiKeyDisplay = effectiveApiKey ? `${effectiveApiKey.slice(0, 4)}… [${apiKeySource}]` : `(not set)`;
const apiKeyDisplay = effectiveApiKey ? `${maskSecret(effectiveApiKey)} [${apiKeySource}]` : `(not set)`;

console.log('Current configuration:');
console.log(` log_file: ${settings.log_file}`);
Expand Down Expand Up @@ -289,7 +293,10 @@ async function cmdConfig(args: string[]): Promise<void> {
const coerced = key === 'debug' ? value === 'true' : value;
(settings as unknown as Record<string, unknown>)[key] = coerced;
saveSettings(settings);
console.log(`✓ Set ${key} = ${value}`);
const displayValue = key === 'wandb_api_key' && typeof coerced === 'string'
? maskSecret(coerced)
: coerced;
console.log(`✓ Set ${key} = ${displayValue}`);
return;
}

Expand Down Expand Up @@ -345,7 +352,7 @@ async function cmdStatus(): Promise<void> {
const effectiveApiKey = process.env['WANDB_API_KEY'] ?? settings.wandb_api_key ?? null;
if (effectiveApiKey) {
const apiKeySource = process.env['WANDB_API_KEY'] ? 'WANDB_API_KEY env var' : 'settings.json';
console.log(`✓ W&B API key: ${effectiveApiKey.slice(0, 4)}… (from ${apiKeySource})`);
console.log(`✓ W&B API key: ${maskSecret(effectiveApiKey)} (from ${apiKeySource})`);
} else {
console.log('✗ W&B API key: not configured');
console.log(' Run: weave-claude-code config set wandb_api_key <your-api-key>');
Expand Down
65 changes: 65 additions & 0 deletions tests/config-set-masks-secrets.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: 2026 CoreWeave, Inc.
// SPDX-License-Identifier: MIT
// SPDX-PackageName: weave-claude-code

// `config set` must mask wandb_api_key in stdout (it didn't — see #66) but
// must still echo non-sensitive keys in full and persist the full secret.

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';

const HERE = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(HERE, '..');
const CLI = path.join(REPO_ROOT, 'src', 'cli.ts');
const SECRET = 'wandb_v1_SUPERSECRETvalueDoNotLeak0123456789';

function newHome(label: string): { home: string; settingsFile: string } {
const home = fs.mkdtempSync(`/tmp/wcp-cfgset-${label}-`);
const dir = path.join(home, '.weave-claude-code');
fs.mkdirSync(path.join(dir, 'logs'), { recursive: true });
const settingsFile = path.join(dir, 'settings.json');
fs.writeFileSync(settingsFile, JSON.stringify({
log_file: path.join(dir, 'logs', 'daemon.log'),
daemon_socket: path.join(dir, 'daemon.sock'),
weave_project: null,
wandb_api_key: null,
debug: false,
installed_at: '2026-01-01T00:00:00Z',
version: '0.0.0-test',
}));
return { home, settingsFile };
}

function runCli(home: string, args: string[]): Promise<{ stdout: string; code: number | null }> {
return new Promise((resolve, reject) => {
const env = { ...process.env, HOME: home };
delete env.WANDB_API_KEY;
delete env.WEAVE_PROJECT;
const child = spawn(process.execPath, ['--import', 'tsx', CLI, ...args], { cwd: REPO_ROOT, env });
let stdout = '';
child.stdout.on('data', (b) => { stdout += b.toString(); });
child.on('error', reject);
child.on('exit', (code) => resolve({ stdout, code }));
});
}

test('config set: masks wandb_api_key, echoes weave_project in full', async () => {
const { home, settingsFile } = newHome('mask');
try {
const apiKey = await runCli(home, ['config', 'set', 'wandb_api_key', SECRET]);
assert.equal(apiKey.code, 0);
assert.equal(apiKey.stdout.includes(SECRET), false, `stdout leaked the secret:\n${apiKey.stdout}`);
assert.match(apiKey.stdout, /wand…/);
assert.equal(JSON.parse(fs.readFileSync(settingsFile, 'utf8')).wandb_api_key, SECRET);

const project = await runCli(home, ['config', 'set', 'weave_project', 'my-entity/my-project']);
assert.equal(project.code, 0);
assert.match(project.stdout, /my-entity\/my-project/);
} finally {
fs.rmSync(home, { recursive: true, force: true });
}
});
Loading