fix(logging): redact sensitive portal and Electron diagnostics (#1182)

* fix: redact sensitive log data

* fix(ci): keep logging preload self-contained

* fix(logging): preserve shared diagnostics

* fix(logging): close trace redaction gaps

* fix(logging): redact Xtream path credentials

* fix(logging): close credential redaction gaps

* fix(logging): suppress external player arguments

* fix(logging): harden URL and date redaction

* fix(logging): redact map keys and URL fragments

* fix(logging): redact sensitive map values

* fix(logging): redact credentials in diagnostic text

* fix(logging): close remaining credential leaks
This commit is contained in:
4gray authored and GitHub committed 2026-07-19 08:30:21 +02:00
1 parent 29e624b0dd
commit 5cae310430
29 files changed
+1203 -158

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,
+14 -12
View File
@@ -7,7 +7,6 @@ import { DialogService } from '@iptvnator/ui/components';
import { DataService, SettingsStore } from '@iptvnator/services';
import {
AUTO_UPDATE_PLAYLISTS,
createDevLogger,
ELECTRON_BRIDGE_SECURITY_ERROR_CODES,
ERROR,
normalizeHost,
@@ -22,6 +21,7 @@ import {
} from '@iptvnator/shared/interfaces';
import { AppConfig } from '../../environments/environment';
import {
createLogger,
createPortalDebugRequestContext,
logPortalDebugEvent,
} from '@iptvnator/portal/shared/util';
@@ -54,7 +54,7 @@ export class ElectronService extends DataService {
private readonly store = inject(Store);
private readonly settingsStore = inject(SettingsStore);
private readonly translateService = inject(TranslateService);
private readonly debugLog = createDevLogger('ElectronService');
private readonly logger = createLogger('ElectronService');
private readonly silentXtreamActions = new Set<string>([
XtreamCodeActions.GetAccountInfo,
XtreamCodeActions.GetLiveCategories,
@@ -67,7 +67,6 @@ export class ElectronService extends DataService {
constructor() {
super();
this.debugLog('Electron service initialized...');
this.setupPlayerErrorListener();
this.setupPortalDebugListener();
}
@@ -81,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',
@@ -182,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;
}
}
@@ -211,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;
}
}
@@ -237,7 +239,7 @@ export class ElectronService extends DataService {
return playlists as T;
}
this.debugLog('Unknown IPC event type:', type);
this.logger.debug('Unknown IPC event type:', type);
return undefined as T;
}
@@ -265,7 +267,7 @@ export class ElectronService extends DataService {
return response;
} catch (err: unknown) {
const errorInfo = this.getErrorDetails(err);
console.error('Stalker request error:', err);
this.logger.error('Stalker request error:', err);
this.snackBar.open(
`Error: ${errorInfo?.message ?? ' Not found'}, status: ${errorInfo?.status ?? 404}`,
'Close',
@@ -362,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;
@@ -387,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, () => {
@@ -590,12 +592,12 @@ export class ElectronService extends DataService {
// Log error to console
if (isSilentAction) {
this.debugLog(
this.logger.debug(
`Background Xtream action failed (${action ?? 'unknown'}):`,
normalizedMessage
);
} else {
console.error('Xtream request error:', normalizedMessage);
this.logger.error('Xtream request error:', normalizedMessage);
}
// Only show snackbar for user-triggered Xtream requests
+6 -7
View File
@@ -15,7 +15,6 @@ import {
} from 'rxjs';
import { DataService } from '@iptvnator/services';
import {
createDevLogger,
ERROR,
Playlist,
PLAYLIST_PARSE_BY_URL,
@@ -32,6 +31,7 @@ import {
createPortalDebugSuccessEvent,
logPortalDebugEvent,
logPortalDebugRequest,
createLogger,
} from '@iptvnator/portal/shared/util';
import { getRuntimeBackendUrl } from './runtime-config';
@@ -71,7 +71,7 @@ export class PwaService extends DataService {
private readonly store = inject(Store);
private readonly swUpdate = inject(SwUpdate);
private readonly translateService = inject(TranslateService);
private readonly debugLog = createDevLogger('PwaService');
private readonly logger = createLogger('PwaService');
private readonly providerTargetIds = new Map<string, Promise<string>>();
private readonly silentXtreamActions = new Set<string>([
XtreamCodeActions.GetAccountInfo,
@@ -88,7 +88,6 @@ export class PwaService extends DataService {
constructor() {
super();
this.debugLog('PWA service initialized...');
}
/** Uses service worker mechanism to check for available application updates */
@@ -358,7 +357,7 @@ export class PwaService extends DataService {
);
if (isSilentAction) {
this.debugLog(
this.logger.debug(
`Background Xtream action failed (${action ?? 'unknown'}):`,
normalizedMessage
);
@@ -398,7 +397,7 @@ export class PwaService extends DataService {
// Log error to console
if (isSilentAction) {
this.debugLog(
this.logger.debug(
`Background Xtream action failed (${action ?? 'unknown'}):`,
normalizedMessage
);
@@ -409,7 +408,7 @@ export class PwaService extends DataService {
};
}
console.error('Xtream request error:', normalizedMessage);
this.logger.error('Xtream request error:', normalizedMessage);
this.snackBar.open(
`Xtream request failed: ${normalizedMessage}`,
'Close',
@@ -533,7 +532,7 @@ export class PwaService extends DataService {
} catch (err: unknown) {
const errorInfo = this.getErrorDetails(err);
logPortalDebugEvent(createPortalDebugErrorEvent(context, err));
console.error('Stalker request error:', err);
this.logger.error('Stalker request error:', err);
this.snackBar.open(
`Error: ${errorInfo?.message ?? ' Not found'}, status: ${errorInfo?.status ?? 404}`,