Files
url_tracker_tool/apps/api/dist/services/header-redaction.service.js
Andrei 58f8093689 Rebrand from 'Redirect Intelligence v2' to 'URL Tracker Tool V2' throughout UI
- Updated all component headers and documentation
- Changed navbar and footer branding
- Updated homepage hero badge
- Modified page title in index.html
- Simplified footer text to 'Built with ❤️'
- Consistent V2 capitalization across all references
2025-08-19 19:12:23 +00:00

309 lines
10 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.headerRedactionService = exports.HeaderRedactionService = void 0;
const logger_1 = require("../lib/logger");
const SENSITIVE_HEADERS = new Set([
'authorization',
'x-api-key',
'x-auth-token',
'x-access-token',
'bearer',
'token',
'cookie',
'set-cookie',
'session',
'session-id',
'sessionid',
'jsessionid',
'phpsessid',
'x-real-ip',
'x-forwarded-for',
'x-client-ip',
'x-remote-addr',
'x-user-email',
'x-user-id',
'x-username',
'x-csrf-token',
'x-xsrf-token',
'x-csrftoken',
'csrf-token',
'xsrf-token',
'x-api-secret',
'x-private-key',
'x-secret',
'x-password',
'x-token',
'x-auth',
'x-authentication',
'x-forwarded-proto',
'x-forwarded-host',
'x-forwarded-server',
'x-original-forwarded-for',
'x-cluster-client-ip',
'cf-connecting-ip',
'true-client-ip',
'stripe-signature',
'paypal-auth-version',
'x-hub-signature',
'x-github-event',
'x-slack-signature',
]);
const PARTIALLY_REDACTABLE_HEADERS = new Set([
'user-agent',
'referer',
'origin',
'x-forwarded-by',
'via',
]);
const SAFE_HEADERS = new Set([
'accept',
'accept-encoding',
'accept-language',
'cache-control',
'connection',
'content-type',
'content-length',
'content-encoding',
'date',
'etag',
'expires',
'host',
'last-modified',
'location',
'pragma',
'server',
'vary',
'www-authenticate',
'x-powered-by',
'x-frame-options',
'x-content-type-options',
'strict-transport-security',
'content-security-policy',
'x-ratelimit-limit',
'x-ratelimit-remaining',
'x-ratelimit-reset',
'retry-after',
'age',
'allow',
'access-control-allow-origin',
'access-control-allow-methods',
'access-control-allow-headers',
'access-control-expose-headers',
'access-control-max-age',
'access-control-allow-credentials',
]);
class HeaderRedactionService {
redactHeaders(headers, options = {}) {
const { includeDebugHeaders = false, redactionLevel = 'full', customSensitiveHeaders = [], preserveHeaders = [], } = options;
const result = {
headers: {},
redactedCount: 0,
redactedHeaders: [],
partiallyRedactedHeaders: [],
};
const allSensitiveHeaders = new Set([
...SENSITIVE_HEADERS,
...customSensitiveHeaders.map(h => h.toLowerCase()),
]);
const preserveSet = new Set(preserveHeaders.map(h => h.toLowerCase()));
for (const [key, value] of Object.entries(headers)) {
const lowerKey = key.toLowerCase();
const stringValue = Array.isArray(value) ? value.join(', ') : value;
if (!stringValue || stringValue.trim() === '') {
continue;
}
if (preserveSet.has(lowerKey)) {
result.headers[key] = stringValue;
continue;
}
if (allSensitiveHeaders.has(lowerKey)) {
result.redactedCount++;
result.redactedHeaders.push(key);
if (redactionLevel === 'partial' || includeDebugHeaders) {
result.headers[key] = this.partiallyRedactValue(stringValue);
}
continue;
}
if (PARTIALLY_REDACTABLE_HEADERS.has(lowerKey)) {
result.headers[key] = this.partiallyRedactValue(stringValue);
result.partiallyRedactedHeaders.push(key);
continue;
}
if (SAFE_HEADERS.has(lowerKey)) {
result.headers[key] = stringValue;
continue;
}
if (includeDebugHeaders) {
result.headers[key] = this.partiallyRedactValue(stringValue);
result.partiallyRedactedHeaders.push(key);
}
else {
result.redactedCount++;
result.redactedHeaders.push(key);
}
}
logger_1.logger.debug('Headers redacted', {
originalCount: Object.keys(headers).length,
finalCount: Object.keys(result.headers).length,
redactedCount: result.redactedCount,
redactionLevel,
includeDebugHeaders,
});
return result;
}
partiallyRedactValue(value) {
if (value.length <= 8) {
return '*'.repeat(value.length);
}
const start = value.substring(0, 3);
const end = value.substring(value.length - 3);
const middle = '*'.repeat(Math.min(value.length - 6, 10));
return `${start}${middle}${end}`;
}
redactLogData(data, options = {}) {
if (!data || typeof data !== 'object') {
return data;
}
const redacted = { ...data };
if (redacted.headers) {
const result = this.redactHeaders(redacted.headers, options);
redacted.headers = result.headers;
}
if (redacted.request && redacted.request.headers) {
const result = this.redactHeaders(redacted.request.headers, options);
redacted.request.headers = result.headers;
}
if (redacted.response && redacted.response.headers) {
const result = this.redactHeaders(redacted.response.headers, options);
redacted.response.headers = result.headers;
}
if (redacted.body || redacted.data) {
const bodyData = redacted.body || redacted.data;
if (typeof bodyData === 'object') {
redacted.body = this.redactObjectData(bodyData);
redacted.data = this.redactObjectData(bodyData);
}
}
if (redacted.url && typeof redacted.url === 'string') {
redacted.url = this.redactSensitiveUrlParams(redacted.url);
}
if (redacted.originalUrl && typeof redacted.originalUrl === 'string') {
redacted.originalUrl = this.redactSensitiveUrlParams(redacted.originalUrl);
}
return redacted;
}
redactObjectData(obj) {
if (!obj || typeof obj !== 'object') {
return obj;
}
const sensitiveFields = new Set([
'password',
'secret',
'token',
'key',
'authorization',
'auth',
'apikey',
'api_key',
'access_token',
'refresh_token',
'session',
'cookie',
'csrf',
'xsrf',
]);
const redacted = Array.isArray(obj) ? [] : {};
for (const [key, value] of Object.entries(obj)) {
const lowerKey = key.toLowerCase();
if (sensitiveFields.has(lowerKey) || lowerKey.includes('password') || lowerKey.includes('secret')) {
redacted[key] = typeof value === 'string' ? this.partiallyRedactValue(value) : '[REDACTED]';
}
else if (typeof value === 'object' && value !== null) {
redacted[key] = this.redactObjectData(value);
}
else {
redacted[key] = value;
}
}
return redacted;
}
redactSensitiveUrlParams(url) {
try {
const urlObj = new URL(url);
const sensitiveParams = new Set([
'token',
'key',
'secret',
'password',
'auth',
'authorization',
'api_key',
'apikey',
'access_token',
'session',
'csrf',
'xsrf',
]);
for (const [key, value] of urlObj.searchParams.entries()) {
if (sensitiveParams.has(key.toLowerCase()) ||
key.toLowerCase().includes('password') ||
key.toLowerCase().includes('secret') ||
key.toLowerCase().includes('token')) {
urlObj.searchParams.set(key, this.partiallyRedactValue(value));
}
}
return urlObj.toString();
}
catch (error) {
logger_1.logger.warn('Failed to parse URL for redaction:', { url, error: error.message });
return url;
}
}
isSensitiveHeader(headerName, customSensitive = []) {
const lowerName = headerName.toLowerCase();
return SENSITIVE_HEADERS.has(lowerName) ||
customSensitive.map(h => h.toLowerCase()).includes(lowerName);
}
getSensitiveHeadersList() {
return Array.from(SENSITIVE_HEADERS).sort();
}
validateRedactionConfig(options) {
const errors = [];
if (options.redactionLevel && !['full', 'partial'].includes(options.redactionLevel)) {
errors.push('redactionLevel must be either "full" or "partial"');
}
if (options.customSensitiveHeaders) {
if (!Array.isArray(options.customSensitiveHeaders)) {
errors.push('customSensitiveHeaders must be an array');
}
else {
for (const header of options.customSensitiveHeaders) {
if (typeof header !== 'string') {
errors.push('All customSensitiveHeaders must be strings');
break;
}
}
}
}
if (options.preserveHeaders) {
if (!Array.isArray(options.preserveHeaders)) {
errors.push('preserveHeaders must be an array');
}
else {
for (const header of options.preserveHeaders) {
if (typeof header !== 'string') {
errors.push('All preserveHeaders must be strings');
break;
}
}
}
}
return {
valid: errors.length === 0,
errors,
};
}
}
exports.HeaderRedactionService = HeaderRedactionService;
exports.headerRedactionService = new HeaderRedactionService();
//# sourceMappingURL=header-redaction.service.js.map