- 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
309 lines
10 KiB
JavaScript
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
|