fix(config): preserve hidden auth secrets and block scalars

This commit is contained in:
Tam Nhu Tran
2026-03-24 15:25:49 -04:00
parent e983bed1da
commit 6ccf53ec7d
2 changed files with 107 additions and 8 deletions
+33 -2
View File
@@ -201,7 +201,38 @@ function redactYamlScalarLine(line: string): string {
}
function isYamlBlockScalarIndicator(value: string): boolean {
return /^[>|](?:[1-9][+-]?|[+-]?[1-9])?$/.test(value);
const trimmedValue = value.trim();
if (!(trimmedValue.startsWith('|') || trimmedValue.startsWith('>'))) {
return false;
}
const indicators = trimmedValue.slice(1);
if (indicators.length === 0) {
return true;
}
if (indicators.length > 2) {
return false;
}
let sawChompingIndicator = false;
let sawIndentIndicator = false;
for (const indicator of indicators) {
if ((indicator === '+' || indicator === '-') && !sawChompingIndicator) {
sawChompingIndicator = true;
continue;
}
if (/[1-9]/.test(indicator) && !sawIndentIndicator) {
sawIndentIndicator = true;
continue;
}
return false;
}
return true;
}
function shouldRedactRawYamlPath(pathKeys: string[]): boolean {
@@ -404,7 +435,7 @@ function mergeCliproxyAuthConfig(
nextAuth: UnifiedConfig['cliproxy']['auth']
): UnifiedConfig['cliproxy']['auth'] {
if (!nextAuth) {
return undefined;
return currentAuth;
}
const mergedAuth = { ...nextAuth };
@@ -412,6 +412,74 @@ describe('web-server config-routes account context validation', () => {
);
});
it('preserves cliproxy auth secrets when hidden auth subtrees are omitted on save', async () => {
const config = createEmptyUnifiedConfig();
config.cliproxy.auth = {
api_key: 'cliproxy-global-api-key',
management_secret: 'cliproxy-global-management-secret',
};
config.cliproxy.variants.gemini = {
provider: 'gemini',
auth: {
api_key: 'variant-api-key',
management_secret: 'variant-management-secret',
},
};
saveUnifiedConfig(config);
const getResponse = await fetch(`${baseUrl}/api/config`);
const sanitized = (await getResponse.json()) as typeof config;
delete sanitized.cliproxy.auth;
if (sanitized.cliproxy.variants.gemini) {
delete sanitized.cliproxy.variants.gemini.auth;
}
sanitized.cliproxy.kiro_no_incognito = true;
const putResponse = await putJson(baseUrl, '/api/config', sanitized);
expect(putResponse.status).toBe(200);
const savedConfig = loadUnifiedConfig();
expect(savedConfig?.cliproxy.kiro_no_incognito).toBe(true);
expect(savedConfig?.cliproxy.auth?.api_key).toBe('cliproxy-global-api-key');
expect(savedConfig?.cliproxy.auth?.management_secret).toBe(
'cliproxy-global-management-secret'
);
expect(savedConfig?.cliproxy.variants.gemini?.auth?.api_key).toBe('variant-api-key');
expect(savedConfig?.cliproxy.variants.gemini?.auth?.management_secret).toBe(
'variant-management-secret'
);
});
it('clears cliproxy auth secrets when empty auth objects are saved explicitly', async () => {
const config = createEmptyUnifiedConfig();
config.cliproxy.auth = {
api_key: 'cliproxy-global-api-key',
management_secret: 'cliproxy-global-management-secret',
};
config.cliproxy.variants.gemini = {
provider: 'gemini',
auth: {
api_key: 'variant-api-key',
management_secret: 'variant-management-secret',
},
};
saveUnifiedConfig(config);
const getResponse = await fetch(`${baseUrl}/api/config`);
const sanitized = (await getResponse.json()) as typeof config;
sanitized.cliproxy.auth = {};
if (sanitized.cliproxy.variants.gemini) {
sanitized.cliproxy.variants.gemini.auth = {};
}
const putResponse = await putJson(baseUrl, '/api/config', sanitized);
expect(putResponse.status).toBe(200);
const savedConfig = loadUnifiedConfig();
expect(savedConfig?.cliproxy.auth).toBeUndefined();
expect(savedConfig?.cliproxy.variants.gemini?.auth).toBeUndefined();
});
it('redacts block-scalar secrets from raw YAML responses', async () => {
const yamlPath = path.join(tempHome, '.ccs', 'config.yaml');
fs.writeFileSync(
@@ -428,18 +496,18 @@ describe('web-server config-routes account context validation', () => {
' gemini:',
' provider: gemini',
' auth:',
' api_key: |',
' api_key: |-',
' variant-api-secret-line-1',
' variant-api-secret-line-2',
' auth:',
' api_key: >',
' api_key: >+',
' cliproxy-global-api-secret',
' management_secret: |',
' management_secret: |+',
' cliproxy-global-management-secret',
'global_env:',
' enabled: true',
' env:',
' GITHUB_TOKEN: |',
' GITHUB_TOKEN: |-',
' github-token-secret-line-1',
' github-token-secret-line-2',
'cliproxy_server:',
@@ -447,12 +515,12 @@ describe('web-server config-routes account context validation', () => {
' enabled: true',
' host: proxy.example.com',
' protocol: https',
' auth_token: >',
' auth_token: >-',
' remote-auth-secret',
'dashboard_auth:',
' enabled: true',
' username: admin',
' password_hash: |',
' password_hash: >+',
' dashboard-password-secret',
'',
].join('\n')