mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-20 05:11:48 +00:00
Establish the structured-log foundation that downstream modules consume: - LogStage union (intake|route|auth|dispatch|upstream|transform|respond|cleanup) - AsyncLocalStorage-backed log-context for end-to-end requestId propagation - Logger.stage() emit helper with optional latencyMs and structured metadata - Extended log-redaction patterns for proxy-authorization, x-goog-api-key, oauth-code, refresh-token classes; Bearer/Basic/Token value-shape masking - Public exports: getRecentLogEntries, withRequestContext, runWithRequestId, getRequestId Refs #1141, #1138
140 lines
3.9 KiB
TypeScript
140 lines
3.9 KiB
TypeScript
import { describe, expect, it } from 'bun:test';
|
|
import {
|
|
redactArgv,
|
|
redactContext,
|
|
} from '../../../../src/services/logging/log-redaction';
|
|
|
|
describe('log redaction (extended sensitive keys)', () => {
|
|
const newSensitiveKeys = [
|
|
'refresh_token',
|
|
'id_token',
|
|
'access_token',
|
|
'client_secret',
|
|
'bearer',
|
|
'assertion',
|
|
'copilot_token',
|
|
'copilot-token',
|
|
'cursor_session_key',
|
|
'cursor-session-key',
|
|
'x-api-key',
|
|
'x_goog_api_key',
|
|
'proxy-authorization',
|
|
'oauth_code',
|
|
'auth_code',
|
|
'auth_token',
|
|
];
|
|
|
|
it.each(newSensitiveKeys)('redacts %s key (top-level)', (key) => {
|
|
const redacted = redactContext({ [key]: 'secret-value', safe: 'ok' });
|
|
expect(redacted[key]).toBe('[redacted]');
|
|
expect(redacted.safe).toBe('ok');
|
|
});
|
|
|
|
it('redacts new keys nested under headers', () => {
|
|
const redacted = redactContext({
|
|
headers: {
|
|
'x-api-key': 'sk-xxx',
|
|
'proxy-authorization': 'Bearer abc',
|
|
'copilot-token': 'gho_xxx',
|
|
},
|
|
});
|
|
expect(redacted).toEqual({
|
|
headers: {
|
|
'x-api-key': '[redacted]',
|
|
'proxy-authorization': '[redacted]',
|
|
'copilot-token': '[redacted]',
|
|
},
|
|
});
|
|
});
|
|
|
|
it('redacts Bearer/Basic/Token scheme prefix in raw string values', () => {
|
|
const redacted = redactContext({
|
|
raw: 'Bearer eyJhbGciOiJIUzI1NiJ9.payload.sig',
|
|
basic: 'Basic dXNlcjpwYXNz',
|
|
tokenLine: 'Token abc.def',
|
|
plain: 'no-scheme-here',
|
|
});
|
|
expect(redacted.raw).toBe('Bearer [redacted]');
|
|
expect(redacted.basic).toBe('Basic [redacted]');
|
|
expect(redacted.tokenLine).toBe('Token [redacted]');
|
|
expect(redacted.plain).toBe('no-scheme-here');
|
|
});
|
|
|
|
it('passes numeric/boolean values through even if key matches', () => {
|
|
// expires_at is not a sensitive key; ensure non-string sensitive values
|
|
// would also pass through if pattern matched (defense check).
|
|
const redacted = redactContext({
|
|
access_token: 'real-secret',
|
|
expires_at: 1234567890,
|
|
enabled: true,
|
|
});
|
|
expect(redacted.access_token).toBe('[redacted]');
|
|
expect(redacted.expires_at).toBe(1234567890);
|
|
expect(redacted.enabled).toBe(true);
|
|
});
|
|
|
|
it('recurses through arrays of objects', () => {
|
|
const redacted = redactContext({
|
|
tokens: [
|
|
{ access_token: 'a' },
|
|
{ refresh_token: 'b' },
|
|
{ id_token: 'c', meta: 'kept' },
|
|
],
|
|
});
|
|
expect(redacted).toEqual({
|
|
tokens: [
|
|
{ access_token: '[redacted]' },
|
|
{ refresh_token: '[redacted]' },
|
|
{ id_token: '[redacted]', meta: 'kept' },
|
|
],
|
|
});
|
|
});
|
|
|
|
it('fuzz-style: many random token-shaped keys all redact', () => {
|
|
const variants = ['token', 'auth_token', 'refresh-token', 'id-token', 'X-API-KEY'];
|
|
for (const v of variants) {
|
|
const out = redactContext({ [v]: 'secret' });
|
|
expect(out[v]).toBe('[redacted]');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('redactArgv', () => {
|
|
it('redacts the value following sensitive flags', () => {
|
|
expect(redactArgv(['--api-key', 'secret', '--other', 'ok'])).toEqual([
|
|
'--api-key',
|
|
'[redacted]',
|
|
'--other',
|
|
'ok',
|
|
]);
|
|
});
|
|
|
|
it('redacts multiple sensitive flags', () => {
|
|
expect(
|
|
redactArgv(['--token', 'a', '--secret', 'b', '--bearer', 'c', '--keep', 'd'])
|
|
).toEqual(['--token', '[redacted]', '--secret', '[redacted]', '--bearer', '[redacted]', '--keep', 'd']);
|
|
});
|
|
|
|
it('passes argv unchanged when no sensitive flags present', () => {
|
|
expect(redactArgv(['build', '--watch', '--out', 'dist'])).toEqual([
|
|
'build',
|
|
'--watch',
|
|
'--out',
|
|
'dist',
|
|
]);
|
|
});
|
|
|
|
it('handles trailing sensitive flag with no value', () => {
|
|
expect(redactArgv(['--api-key'])).toEqual(['--api-key']);
|
|
});
|
|
|
|
it('redacts kebab and snake case flag variants', () => {
|
|
expect(redactArgv(['--api_key', 'x', '--auth-token', 'y'])).toEqual([
|
|
'--api_key',
|
|
'[redacted]',
|
|
'--auth-token',
|
|
'[redacted]',
|
|
]);
|
|
});
|
|
});
|