Files

125 lines
5.0 KiB
JavaScript

/**
* Privacy Utilities
* =================
* Frontend PII sanitization for error data.
* Mirrors backend sanitizer.py patterns.
*
* R5: Frontend Error Boundaries
* @module privacy_utils
*/
import { getDoctorSetting } from "./comfyui_frontend_compat.js";
// ═══════════════════════════════════════════════════════════════════════════
// PATTERNS
// ═══════════════════════════════════════════════════════════════════════════
const PATTERNS = {
// Windows user paths: C:\Users\username -> <USER_PATH>
windowsUserPath: /C:\\Users\\[^\\]+/gi,
// Unix home paths: /home/username -> <USER_HOME>
unixHomePath: /\/home\/[^\/]+/gi,
// macOS home paths: /Users/username -> <USER_HOME>
macHomePath: /\/Users\/[^\/]+/gi,
// API keys: sk-xxx, pk-xxx, api_xxx -> <API_KEY>
apiKey: /\b(sk|pk|api)[-_]?[a-zA-Z0-9]{20,}\b/gi,
// Emails: user@example.com -> <EMAIL>
email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/gi,
// Private IPs: 192.168.x.x, 10.x.x.x, 172.16-31.x.x -> <PRIVATE_IP>
privateIp: /\b(?:10|172\.(?:1[6-9]|2[0-9]|3[01])|192\.168)\.\d{1,3}\.\d{1,3}\b/g
};
// ═══════════════════════════════════════════════════════════════════════════
// HELPER FUNCTIONS
// ═══════════════════════════════════════════════════════════════════════════
/**
* Get privacy mode from ComfyUI settings.
* @returns {'none'|'basic'|'strict'} Current privacy mode
*/
function getPrivacyMode() {
try {
const mode = getDoctorSetting('Doctor.Privacy.Mode', 'basic', window.app);
return mode || 'basic';
} catch (err) {
return 'basic'; // Default to basic
}
}
/**
* Apply sanitization patterns to a string.
*/
function sanitizeString(str) {
if (!str) return str;
let result = str;
result = result.replace(PATTERNS.windowsUserPath, '<USER_PATH>');
result = result.replace(PATTERNS.unixHomePath, '<USER_HOME>');
result = result.replace(PATTERNS.macHomePath, '<USER_HOME>');
result = result.replace(PATTERNS.apiKey, '<API_KEY>');
result = result.replace(PATTERNS.email, '<EMAIL>');
result = result.replace(PATTERNS.privateIp, '<PRIVATE_IP>');
return result;
}
// ═══════════════════════════════════════════════════════════════════════════
// PUBLIC API
// ═══════════════════════════════════════════════════════════════════════════
/**
* Sanitize error data based on current privacy mode.
*
* Privacy Mode Matrix:
* | Mode | Stack Trace | File Paths | IPs |
* |--------|-------------|------------------|--------|
* | none | Full | Full | Full |
* | basic | Sanitized | Username removed | Hidden |
* | strict | Removed | Filename only | Hidden |
*
* @param {Object} data - Error data to sanitize
* @param {string} [data.message] - Error message
* @param {string} [data.source] - Source file path
* @param {string} [data.stack] - Stack trace
* @param {number} [data.lineno] - Line number
* @param {number} [data.colno] - Column number
* @returns {Object} Sanitized error data
*/
export function sanitizeErrorData(data) {
const mode = getPrivacyMode();
const { message, source, stack, lineno, colno } = data;
if (mode === 'none') {
return data; // No sanitization
}
let sanitizedMessage = message || '';
let sanitizedSource = source || '';
let sanitizedStack = stack || '';
if (mode === 'basic' || mode === 'strict') {
// Sanitize all fields
sanitizedMessage = sanitizeString(sanitizedMessage);
sanitizedSource = sanitizeString(sanitizedSource);
sanitizedStack = sanitizeString(sanitizedStack);
}
if (mode === 'strict') {
// Remove file paths entirely (keep only filename)
if (sanitizedSource) {
const parts = sanitizedSource.split(/[\/\\]/);
sanitizedSource = parts[parts.length - 1] || '<PATH>';
}
// Remove stack trace in strict mode
sanitizedStack = '';
}
return {
message: sanitizedMessage,
source: sanitizedSource,
stack: sanitizedStack,
lineno: mode === 'strict' ? null : lineno,
colno: mode === 'strict' ? null : colno
};
}