fix(logging): close remaining credential leaks

This commit is contained in:
4gray committed 2026-07-19 07:52:11 +02:00
1 parent 7d2f525c9c
commit 7f4b5be848
4 files changed
+47 -15

No files matched your search

@@ -16,6 +16,7 @@ describe('ElectronService', () => {
const session = { id: 'session-1' };
let electronBridge: {
fetchPlaylistByUrl: jest.Mock;
onPlayerError: jest.Mock;
openInMpv: jest.Mock;
openInVlc: jest.Mock;
};
@@ -24,9 +25,11 @@ describe('ElectronService', () => {
beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation(() => undefined);
jest.spyOn(console, 'error').mockImplementation(() => undefined);
electronBridge = {
fetchPlaylistByUrl: jest.fn(),
onPlayerError: jest.fn(),
openInMpv: jest.fn().mockResolvedValue(session),
openInVlc: jest.fn().mockResolvedValue(session),
};
@@ -99,6 +102,23 @@ describe('ElectronService', () => {
expect(electronBridge.fetchPlaylistByUrl).not.toHaveBeenCalled();
});
it('redacts credentials from backend player errors before logging', () => {
const secret = 'player-error-token-secret';
const listener = electronBridge.onPlayerError.mock.calls[0][0];
listener({
player: 'MPV',
error: 'Playback failed',
originalError: `Request failed: token=${secret}&channel=news`,
});
const output = JSON.stringify(
(console.error as jest.Mock).mock.calls
);
expect(output).not.toContain(secret);
expect(output).toContain('channel=news');
});
it('shows the trust-host action for Electron-wrapped security errors', async () => {
const securityPayload = {
code: ELECTRON_BRIDGE_SECURITY_ERROR_CODES.InvalidTlsCertificate,
@@ -80,7 +80,10 @@ export class ElectronService extends DataService {
error: string;
originalError: string;
}) => {
console.error(`${data.player} Error:`, data.originalError);
this.logger.error(
`${data.player} Error:`,
data.originalError
);
this.snackBar.open(
`${data.player} Error: ${data.error}`,
'Close',
@@ -181,7 +184,7 @@ export class ElectronService extends DataService {
duration: 5000,
}
);
console.error('MPV launch error:', error);
this.logger.error('MPV launch error:', error);
throw error;
}
}
@@ -210,7 +213,7 @@ export class ElectronService extends DataService {
duration: 5000,
}
);
console.error('VLC launch error:', error);
this.logger.error('VLC launch error:', error);
throw error;
}
}
@@ -361,7 +364,7 @@ export class ElectronService extends DataService {
data.title
);
} else {
console.error(
this.logger.error(
'Either url or filePath must be provided, but not both.'
);
return;
@@ -386,7 +389,7 @@ export class ElectronService extends DataService {
{ duration: 2000 }
);
} catch (error: unknown) {
console.error('Playlist refresh error:', error);
this.logger.error('Playlist refresh error:', error);
if (
data.url &&
this.handlePlaylistSecurityError(error, () => {
@@ -51,10 +51,19 @@ describe('redactSensitiveData', () => {
it('redacts Stalker identity credentials while retaining request diagnostics', () => {
const identitySecrets = {
sn: 'stalker-sn-secret',
serialNumber: 'stalker-serial-number-secret',
device_id: 'stalker-device-id-secret',
deviceId1: 'stalker-device-id1-secret',
device_id2: 'stalker-device-id2-secret',
signature: 'stalker-signature-secret',
signature1: 'stalker-signature1-secret',
signature2: 'stalker-signature2-secret',
stalkerSerialNumber: 'playlist-stalker-serial-number-secret',
stalkerDeviceId1: 'playlist-stalker-device-id1-secret',
stalkerDeviceId2: 'playlist-stalker-device-id2-secret',
stalkerSignature1: 'playlist-stalker-signature1-secret',
stalkerSignature2: 'playlist-stalker-signature2-secret',
prehash: 'stalker-prehash-secret',
};
const result = redactSensitiveData({
@@ -141,10 +150,12 @@ describe('redactSensitiveData', () => {
const authorization = 'diagnostic-authorization-secret';
const authorizationAssignment =
'diagnostic-authorization-assignment-secret';
const stalkerDeviceId = 'diagnostic-stalker-device-id-secret';
const diagnostic = [
`Request failed: token=${token}&action=get_profile`,
`Upstream response Authorization: Bearer ${authorization}; status=401`,
`Retrying with authorization=Bearer ${authorizationAssignment}, attempt=2`,
`Identity rejected: stalkerDeviceId1: ${stalkerDeviceId}; action=handshake`,
];
const output = serialized(redactSensitiveData(diagnostic));
@@ -152,9 +163,11 @@ describe('redactSensitiveData', () => {
expect(output).not.toContain(token);
expect(output).not.toContain(authorization);
expect(output).not.toContain(authorizationAssignment);
expect(output).not.toContain(stalkerDeviceId);
expect(output).toContain('get_profile');
expect(output).toContain('status=401');
expect(output).toContain('attempt=2');
expect(output).toContain('action=handshake');
});
it('does not repeat a redacted Error message secret in its stack', () => {
@@ -33,15 +33,11 @@ const SENSITIVE_KEY_NAMES = new Set([
]);
const SENSITIVE_KEY_SUFFIXES = [
'apikey',
'authorization',
'cookie',
'macaddress',
'passwd',
'password',
'secret',
'token',
'username',
'apikey', 'authorization', 'cookie',
'deviceid', 'deviceid1', 'deviceid2',
'macaddress', 'passwd', 'password', 'prehash',
'serialnumber', 'signature', 'signature1', 'signature2',
'secret', 'token', 'username',
];
const XTREAM_CREDENTIAL_PATH_SEGMENTS = new Set([
@@ -185,7 +181,7 @@ function redactEmbeddedSensitivePairs(value: string): string {
: match
);
return redactedAssignments.replace(
/\b([a-z0-9_.-]*(?:api[-_.]?key|auth(?:orization)?|cookie|credentials|device[-_.]?id2?|login|mac(?:[-_.]?address)?|passwd|password|pwd|secret|set[-_.]?cookie|signature2?|sn|token|username))(\s*:\s*)(?:Bearer\s+)?[^;,\r\n]+/giu,
/\b([a-z0-9_.-]*(?:api[-_.]?key|auth(?:orization)?|cookie|credentials|device[-_.]?id[12]?|login|mac(?:[-_.]?address)?|passwd|password|prehash|pwd|secret|serial[-_.]?number|set[-_.]?cookie|signature[12]?|sn|token|username))(\s*:\s*)(?:Bearer\s+)?[^;,\r\n]+/giu,
(_match, key: string, separator: string) =>
`${key}${separator}${REDACTED_VALUE}`
);