Skip to content

Commit e24e499

Browse files
authored
Merge pull request #209 from InsForge/feedback/348ab693
fix(current): redact privileged api_key from --json output
2 parents d75240d + 1fb2b94 commit e24e499

2 files changed

Lines changed: 76 additions & 1 deletion

File tree

src/commands/info.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
2+
import { Command } from 'commander';
3+
import { registerContextCommand } from './info.js';
4+
5+
const PROJECT_CONFIG = {
6+
project_id: 'p1',
7+
project_name: 'demo',
8+
org_id: 'o1',
9+
appkey: 'k',
10+
region: 'us-east',
11+
api_key: 'uak_secret_key',
12+
oss_host: 'http://localhost',
13+
};
14+
15+
vi.mock('../lib/config.js', () => ({
16+
getCredentials: vi.fn(() => null),
17+
getGlobalConfig: vi.fn(() => ({})),
18+
getProjectConfig: vi.fn(),
19+
}));
20+
vi.mock('../lib/command-telemetry.js', () => ({
21+
trackTopLevelUsage: vi.fn(async () => {}),
22+
}));
23+
24+
function makeProgram() {
25+
const program = new Command().exitOverride();
26+
program.option('--json').option('--api-url <url>');
27+
registerContextCommand(program);
28+
return program;
29+
}
30+
31+
async function runJson(argv: string[]): Promise<string> {
32+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
33+
try {
34+
await makeProgram().parseAsync(argv, { from: 'user' });
35+
return logSpy.mock.calls.flat().join('\n');
36+
} finally {
37+
logSpy.mockRestore();
38+
}
39+
}
40+
41+
describe('current command', () => {
42+
beforeEach(async () => {
43+
vi.clearAllMocks();
44+
const { getProjectConfig } = await import('../lib/config.js');
45+
(getProjectConfig as Mock).mockReturnValue(PROJECT_CONFIG);
46+
});
47+
48+
it('redacts the privileged api_key from --json output', async () => {
49+
const output = await runJson(['current', '--json']);
50+
51+
const payload = JSON.parse(output);
52+
expect(payload.project).toMatchObject({
53+
project_id: 'p1',
54+
project_name: 'demo',
55+
appkey: 'k',
56+
region: 'us-east',
57+
oss_host: 'http://localhost',
58+
});
59+
expect(payload.project).not.toHaveProperty('api_key');
60+
expect(output).not.toContain('uak_secret_key');
61+
});
62+
63+
it('reports a null project in --json output when no project is linked', async () => {
64+
const { getProjectConfig } = await import('../lib/config.js');
65+
(getProjectConfig as Mock).mockReturnValue(null);
66+
67+
const payload = JSON.parse(await runJson(['current', '--json']));
68+
expect(payload.project).toBeNull();
69+
});
70+
});

src/commands/info.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,16 @@ export function registerContextCommand(program: Command): void {
1818
await trackTopLevelUsage('current', true);
1919

2020
if (json) {
21+
// Strip the privileged api_key — identity output must not leak
22+
// credentials (the human-readable branch already omits it).
23+
const project = projectConfig
24+
? (({ api_key: _api_key, ...rest }) => rest)(projectConfig)
25+
: null;
2126
outputJson({
2227
authenticated: !!creds,
2328
user: creds?.user ?? null,
2429
default_org_id: globalConfig.default_org_id ?? null,
25-
project: projectConfig,
30+
project,
2631
});
2732
return;
2833
}

0 commit comments

Comments
 (0)