mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-22 20:24:32 +00:00
fix(config): preserve hidden auth secrets and block scalars
This commit is contained in:
@@ -201,7 +201,38 @@ function redactYamlScalarLine(line: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isYamlBlockScalarIndicator(value: string): boolean {
|
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 {
|
function shouldRedactRawYamlPath(pathKeys: string[]): boolean {
|
||||||
@@ -404,7 +435,7 @@ function mergeCliproxyAuthConfig(
|
|||||||
nextAuth: UnifiedConfig['cliproxy']['auth']
|
nextAuth: UnifiedConfig['cliproxy']['auth']
|
||||||
): UnifiedConfig['cliproxy']['auth'] {
|
): UnifiedConfig['cliproxy']['auth'] {
|
||||||
if (!nextAuth) {
|
if (!nextAuth) {
|
||||||
return undefined;
|
return currentAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mergedAuth = { ...nextAuth };
|
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 () => {
|
it('redacts block-scalar secrets from raw YAML responses', async () => {
|
||||||
const yamlPath = path.join(tempHome, '.ccs', 'config.yaml');
|
const yamlPath = path.join(tempHome, '.ccs', 'config.yaml');
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
@@ -428,18 +496,18 @@ describe('web-server config-routes account context validation', () => {
|
|||||||
' gemini:',
|
' gemini:',
|
||||||
' provider: gemini',
|
' provider: gemini',
|
||||||
' auth:',
|
' auth:',
|
||||||
' api_key: |',
|
' api_key: |-',
|
||||||
' variant-api-secret-line-1',
|
' variant-api-secret-line-1',
|
||||||
' variant-api-secret-line-2',
|
' variant-api-secret-line-2',
|
||||||
' auth:',
|
' auth:',
|
||||||
' api_key: >',
|
' api_key: >+',
|
||||||
' cliproxy-global-api-secret',
|
' cliproxy-global-api-secret',
|
||||||
' management_secret: |',
|
' management_secret: |+',
|
||||||
' cliproxy-global-management-secret',
|
' cliproxy-global-management-secret',
|
||||||
'global_env:',
|
'global_env:',
|
||||||
' enabled: true',
|
' enabled: true',
|
||||||
' env:',
|
' env:',
|
||||||
' GITHUB_TOKEN: |',
|
' GITHUB_TOKEN: |-',
|
||||||
' github-token-secret-line-1',
|
' github-token-secret-line-1',
|
||||||
' github-token-secret-line-2',
|
' github-token-secret-line-2',
|
||||||
'cliproxy_server:',
|
'cliproxy_server:',
|
||||||
@@ -447,12 +515,12 @@ describe('web-server config-routes account context validation', () => {
|
|||||||
' enabled: true',
|
' enabled: true',
|
||||||
' host: proxy.example.com',
|
' host: proxy.example.com',
|
||||||
' protocol: https',
|
' protocol: https',
|
||||||
' auth_token: >',
|
' auth_token: >-',
|
||||||
' remote-auth-secret',
|
' remote-auth-secret',
|
||||||
'dashboard_auth:',
|
'dashboard_auth:',
|
||||||
' enabled: true',
|
' enabled: true',
|
||||||
' username: admin',
|
' username: admin',
|
||||||
' password_hash: |',
|
' password_hash: >+',
|
||||||
' dashboard-password-secret',
|
' dashboard-password-secret',
|
||||||
'',
|
'',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
|
|||||||
Reference in New Issue
Block a user