diff --git a/src/commands/cliproxy/__tests__/proxy-lifecycle-control-panel.test.ts b/src/commands/cliproxy/__tests__/proxy-lifecycle-control-panel.test.ts index 48aa735b..c799c30b 100644 --- a/src/commands/cliproxy/__tests__/proxy-lifecycle-control-panel.test.ts +++ b/src/commands/cliproxy/__tests__/proxy-lifecycle-control-panel.test.ts @@ -1,12 +1,12 @@ /** * Tests for printControlPanelAccess: the Control Panel (API Management Center) - * URL + login-key surface shown by `ccs cliproxy status` / `start`. + * URL + masked login-key hint shown by `ccs cliproxy status` / `start`. * * Why this matters: - * - The Control Panel login screen only asks for a "Management Key" with no - * hint. Users who don't know the default (`ccs`) cannot get in. This surface - * is the fix, so it must print the panel URL on the active port and the - * EFFECTIVE key (default `ccs`, or the user's custom management_secret). + * - Routine lifecycle output is commonly pasted into logs and support tickets, + * so it must not disclose the raw management key. The helper should print + * the panel URL, a masked key for orientation, and the explicit command users + * can run when they intentionally need the full key. */ import * as fs from 'fs'; import * as os from 'os'; @@ -56,12 +56,14 @@ describe('printControlPanelAccess', () => { } }); - it('prints the panel URL on the given port and the default key (ccs)', () => { + it('prints the panel URL on the given port and masks the default key', () => { printControlPanelAccess(8317); const out = lines.join('\n'); expect(out).toContain('http://127.0.0.1:8317/management.html'); expect(out).toContain('Panel login key:'); - expect(out).toContain('ccs'); + expect(out).toContain('Panel login key: ****'); + expect(out).toContain('ccs tokens --show'); + expect(out).not.toContain('Panel login key: ccs'); }); it('uses the active port in the URL', () => { @@ -69,7 +71,7 @@ describe('printControlPanelAccess', () => { expect(lines.join('\n')).toContain('http://127.0.0.1:9000/management.html'); }); - it('prints a custom management_secret when configured', () => { + it('masks a custom management_secret when configured', () => { mutateConfig((config) => { if (!config.cliproxy) { config.cliproxy = {}; @@ -84,6 +86,8 @@ describe('printControlPanelAccess', () => { printControlPanelAccess(8317); const out = lines.join('\n'); expect(out).toContain('Panel login key:'); - expect(out).toContain('my-custom-key'); + expect(out).toContain('my-c...-key'); + expect(out).toContain('ccs tokens --show'); + expect(out).not.toContain('my-custom-key'); }); }); diff --git a/src/commands/cliproxy/help-subcommand.ts b/src/commands/cliproxy/help-subcommand.ts index 9fb96480..87296a0c 100644 --- a/src/commands/cliproxy/help-subcommand.ts +++ b/src/commands/cliproxy/help-subcommand.ts @@ -91,7 +91,7 @@ export async function showHelp(): Promise { ['restart', 'Restart CLIProxy instance'], [ 'status [--verbose]', - 'Show CLIProxy status + Control Panel URL and login key (--verbose adds uptime)', + 'Show CLIProxy status + Control Panel URL and masked login key (--verbose adds uptime)', ], ['stop', 'Stop running CLIProxy instance'], ['doctor | diag', 'Quota diagnostics and shared project detection'], diff --git a/src/commands/cliproxy/proxy-lifecycle-subcommand.ts b/src/commands/cliproxy/proxy-lifecycle-subcommand.ts index 6794260d..92133e0a 100644 --- a/src/commands/cliproxy/proxy-lifecycle-subcommand.ts +++ b/src/commands/cliproxy/proxy-lifecycle-subcommand.ts @@ -12,22 +12,24 @@ import { initUI, header, color, dim, ok, warn, info } from '../../utils/ui'; import { getProxyStatus, startProxy, stopProxy } from '../../cliproxy/services'; import { detectRunningProxy } from '../../cliproxy/proxy/proxy-detector'; import { resolveLifecyclePort } from '../../cliproxy/config/port-manager'; -import { getEffectiveManagementSecret } from '../../cliproxy/auth/auth-token-manager'; +import { getEffectiveManagementSecret, maskToken } from '../../cliproxy/auth/auth-token-manager'; /** * Print how to reach the local CLIProxy Control Panel (a.k.a. API Management - * Center) and the key needed to log in. + * Center) without exposing the management secret in routine output. * * The panel is served by CLIProxy at `/management.html` on the proxy port and - * its login is gated by the management secret (default `ccs`). The login screen - * only asks for a "Management Key" with no hint, so users frequently cannot get - * in. Surfacing the URL + resolved key here removes that guesswork. + * its login is gated by the management secret (default `ccs`). Keep the resolved + * key masked here because `start` and `status` output is commonly shared in + * support tickets and logs. Users can explicitly reveal tokens with + * `ccs tokens --show` when they need the raw value. */ export function printControlPanelAccess(port: number): void { const secret = getEffectiveManagementSecret(); console.log(''); console.log(` Control Panel: http://127.0.0.1:${port}/management.html`); - console.log(` Panel login key: ${secret}`); + console.log(` Panel login key: ${maskToken(secret)}`); + console.log(dim(' To show the full key: ccs tokens --show')); } export async function handleStart(verbose = false): Promise {