Sanitize account check IPC statuses
Centralize DebridAccountStatus sanitizing for direct account-check responses. The shared sanitizer collects stored and submitted credential variants, including raw, trimmed, URL-encoded, URL-decoded, full credential lines, and login:secret forms, then redacts credential-like query params, key-value echoes, authorization, cookie, API-key, token, password, secret, session, backup passphrase, and archive password text before any status DTO reaches the renderer. Apply the sanitizer to bulk checkDebridAccounts results, single checkAccountCredentials results, and account command credential checks before returning, throwing, or persisting statuses. Reuse the same sanitizer for renderer snapshots so status redaction stays on one path. Replace the Debrid-Link key popup copy action with a truthful non-secret masked-identity copy action and cover the regression so no renderer path copies key.token or reports a secret-copy success without a secret readback.
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import { parseDebridLinkApiKeys } from "../shared/debrid-link-keys";
|
||||
import { parseMegaDebridAccounts } from "../shared/mega-debrid-accounts";
|
||||
import type { AppSettings, DebridAccountStatus } from "../shared/types";
|
||||
|
||||
const REDACTED = "[geschützt]";
|
||||
|
||||
function safeDecode(value: string): string {
|
||||
try {
|
||||
return decodeURIComponent(value);
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function addRedaction(values: Set<string>, value: unknown): void {
|
||||
if (typeof value !== "string") {
|
||||
return;
|
||||
}
|
||||
const raw = value;
|
||||
const trimmed = value.trim();
|
||||
for (const candidate of [raw, trimmed]) {
|
||||
if (!candidate) {
|
||||
continue;
|
||||
}
|
||||
values.add(candidate);
|
||||
const encoded = encodeURIComponent(candidate);
|
||||
values.add(encoded);
|
||||
values.add(encoded.replace(/%20/g, "+"));
|
||||
const decoded = safeDecode(candidate);
|
||||
if (decoded) {
|
||||
values.add(decoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getInputString(input: unknown, key: string): string | undefined {
|
||||
if (!input || typeof input !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const value = (input as Record<string, unknown>)[key];
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
export function collectAccountStatusRedactionValues(settings?: AppSettings, input?: unknown): string[] {
|
||||
const values = new Set<string>();
|
||||
if (settings) {
|
||||
addRedaction(values, settings.token);
|
||||
addRedaction(values, settings.megaPassword);
|
||||
addRedaction(values, settings.megaCredentials);
|
||||
addRedaction(values, settings.megaDebridApiCredentials);
|
||||
addRedaction(values, settings.megaDebridWebCredentials);
|
||||
addRedaction(values, settings.bestToken);
|
||||
addRedaction(values, settings.allDebridToken);
|
||||
addRedaction(values, settings.ddownloadPassword);
|
||||
addRedaction(values, settings.oneFichierApiKey);
|
||||
addRedaction(values, settings.debridLinkApiKeys);
|
||||
addRedaction(values, settings.linkSnappyPassword);
|
||||
addRedaction(values, settings.archivePasswordList);
|
||||
addRedaction(values, settings.notifyUrl);
|
||||
for (const raw of [settings.megaCredentials, settings.megaDebridApiCredentials, settings.megaDebridWebCredentials]) {
|
||||
for (const account of parseMegaDebridAccounts(raw, settings.megaPassword)) {
|
||||
addRedaction(values, account.password);
|
||||
addRedaction(values, `${account.login}:${account.password}`);
|
||||
}
|
||||
}
|
||||
for (const key of parseDebridLinkApiKeys(settings.debridLinkApiKeys)) {
|
||||
addRedaction(values, key.token);
|
||||
}
|
||||
}
|
||||
const inputIdentity = getInputString(input, "identity");
|
||||
const inputSecret = getInputString(input, "secret");
|
||||
addRedaction(values, inputSecret);
|
||||
if (inputIdentity && inputSecret) {
|
||||
addRedaction(values, `${inputIdentity.trim()}:${inputSecret.trim()}`);
|
||||
}
|
||||
return [...values].filter(Boolean).sort((left, right) => right.length - left.length);
|
||||
}
|
||||
|
||||
export function sanitizeAccountStatusText(value: string, redactions: readonly string[]): string {
|
||||
let result = value;
|
||||
result = result.replace(/\b(?:Authorization|Proxy-Authorization)\s*:\s*[^\r\n]+/gi, (match) => {
|
||||
const name = match.slice(0, match.indexOf(":"));
|
||||
return `${name}: ${REDACTED}`;
|
||||
});
|
||||
result = result.replace(/\b(?:Cookie|Set-Cookie)\s*:\s*[^\r\n]+/gi, (match) => {
|
||||
const name = match.slice(0, match.indexOf(":"));
|
||||
return `${name}: ${REDACTED}`;
|
||||
});
|
||||
result = result.replace(/\b(?:X-Api-Key|Api-Key|X-Auth-Token|X-Access-Token|Access-Token|Private-Token)\s*:\s*[^\r\n]+/gi, (match) => {
|
||||
const name = match.slice(0, match.indexOf(":"));
|
||||
return `${name}: ${REDACTED}`;
|
||||
});
|
||||
result = result.replace(/((?:[?&;\s,]|^)(?:password|pass|pwd|token|api[_-]?key|apikey|access[_-]?token|private[_-]?token|secret|session|cookie|(?:backup|archive)?[_ -]?passphrase|archive[_ -]?password)\s*[:=]\s*)[^&\s"')]+/gi, `$1${REDACTED}`);
|
||||
for (const secret of redactions) {
|
||||
result = result.split(secret).join(REDACTED);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function sanitizeDebridAccountStatus(status: DebridAccountStatus, redactions: readonly string[]): DebridAccountStatus {
|
||||
return {
|
||||
...status,
|
||||
accountId: sanitizeAccountStatusText(status.accountId, redactions),
|
||||
label: sanitizeAccountStatusText(status.label, redactions),
|
||||
maskedLogin: sanitizeAccountStatusText(status.maskedLogin, redactions),
|
||||
email: status.email ? sanitizeAccountStatusText(status.email, redactions) : undefined,
|
||||
message: sanitizeAccountStatusText(status.message, redactions)
|
||||
};
|
||||
}
|
||||
|
||||
export function sanitizeDebridAccountStatuses(statuses: readonly DebridAccountStatus[], redactions: readonly string[]): DebridAccountStatus[] {
|
||||
return statuses.map((status) => sanitizeDebridAccountStatus(status, redactions));
|
||||
}
|
||||
Reference in New Issue
Block a user