Skip to content

Commit eb9101c

Browse files
Redact sensitive information
1 parent 75b0bce commit eb9101c

2 files changed

Lines changed: 144 additions & 9 deletions

File tree

packages/server/shared/src/lib/logger/log-cleaner.ts

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,72 @@ import { ApplicationError } from '@openops/shared';
44

55
export const maxFieldLength = 2048;
66

7+
const REDACTED = '[REDACTED]';
8+
9+
const SENSITIVE_FIELDS = [
10+
'password',
11+
'newPassword',
12+
'oldPassword',
13+
'currentPassword',
14+
'confirmPassword',
15+
'token',
16+
'accessToken',
17+
'refreshToken',
18+
'apiKey',
19+
'secret',
20+
'privateKey',
21+
'authorization',
22+
'cookie',
23+
'sessionId',
24+
'passphrase',
25+
];
26+
27+
const isSensitiveField = (key: string): boolean => {
28+
return SENSITIVE_FIELDS.includes(key.toLowerCase());
29+
};
30+
31+
const redactSensitiveFields = (obj: any): any => {
32+
if (obj === null || obj === undefined) {
33+
return obj;
34+
}
35+
36+
if (Array.isArray(obj)) {
37+
return obj.map((item) => redactSensitiveFields(item));
38+
}
39+
40+
if (typeof obj !== 'object') {
41+
return obj;
42+
}
43+
44+
const redacted: any = {};
45+
for (const key in obj) {
46+
if (isSensitiveField(key)) {
47+
redacted[key] = REDACTED;
48+
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
49+
redacted[key] = redactSensitiveFields(obj[key]);
50+
} else {
51+
redacted[key] = obj[key];
52+
}
53+
}
54+
return redacted;
55+
};
56+
57+
const redactSensitiveDataInString = (
58+
value: string | undefined,
59+
): string | undefined => {
60+
if (!value) {
61+
return value;
62+
}
63+
let result = value;
64+
SENSITIVE_FIELDS.forEach((field) => {
65+
result = result.replace(
66+
new RegExp(`"${field}"\\s*:\\s*"[^"]*"`, 'gi'),
67+
`"${field}":"${REDACTED}"`,
68+
);
69+
});
70+
return result;
71+
};
72+
773
export const truncate = (
874
value: string | undefined,
975
maxLength: number = maxFieldLength,
@@ -15,7 +81,7 @@ export const truncate = (
1581

1682
export const cleanLogEvent = (logEvent: any) => {
1783
if (logEvent.message) {
18-
logEvent.message = truncate(logEvent.message);
84+
logEvent.message = redactSensitiveDataInString(truncate(logEvent.message));
1985
}
2086

2187
if (!logEvent.event) {
@@ -34,14 +100,18 @@ export const cleanLogEvent = (logEvent: any) => {
34100
continue;
35101
}
36102

37-
if (key === 'res' && value && value.raw) {
103+
if (isSensitiveField(key)) {
104+
eventData[key] = REDACTED;
105+
} else if (key === 'res' && value && value.raw) {
38106
extractRequestFields(value, eventData, logEvent);
39107
} else if (value instanceof Error) {
40108
extractErrorFields(key, value, eventData, logEvent);
41109
} else if (typeof value === 'number') {
42110
eventData[key] = Math.round(value * 100) / 100;
43111
} else if (typeof value === 'object') {
44-
eventData[key] = stringify(value);
112+
eventData[key] = stringify(redactSensitiveFields(value));
113+
} else if (typeof value === 'string') {
114+
eventData[key] = redactSensitiveDataInString(truncate(value));
45115
} else {
46116
eventData[key] = truncate(value);
47117
}
@@ -76,19 +146,23 @@ function extractErrorFields(
76146
) {
77147
const errorKey = key === 'err' ? 'error' : key;
78148
const { stack, message, name, ...context } = value;
79-
eventData[errorKey + 'Stack'] = truncate(stack);
149+
eventData[errorKey + 'Stack'] = redactSensitiveDataInString(truncate(stack));
80150
if (message) {
81-
eventData[errorKey + 'Message'] = truncate(message);
151+
eventData[errorKey + 'Message'] = redactSensitiveDataInString(
152+
truncate(message),
153+
);
82154
if (!logEvent.message) {
83-
logEvent.message = truncate(message);
155+
logEvent.message = redactSensitiveDataInString(truncate(message));
84156
}
85157
}
86158
eventData[errorKey + 'Name'] = truncate(name);
87159
if (value instanceof ApplicationError) {
88160
eventData[errorKey + 'Code'] = truncate(value.error.code);
89-
eventData[errorKey + 'Params'] = stringify(value.error.params);
90-
} else if (context && Object.keys(context).length) {
91-
eventData[errorKey + 'Context'] = stringify(context);
161+
eventData[errorKey + 'Params'] = stringify(
162+
redactSensitiveFields(value.error.params),
163+
);
164+
} else if (Object.keys(context).length) {
165+
eventData[errorKey + 'Context'] = stringify(redactSensitiveFields(context));
92166
}
93167
}
94168

packages/server/shared/test/log-cleaner.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,67 @@ describe('log-cleaner', () => {
137137
});
138138
});
139139

140+
describe('sensitive data redaction', () => {
141+
it('should redact password field', () => {
142+
const logEvent = {
143+
event: {
144+
email: 'user@example.com',
145+
password: 'secret',
146+
},
147+
};
148+
149+
const result = cleanLogEvent(logEvent);
150+
151+
expect(result.event.password).toBe('[REDACTED]');
152+
expect(result.event.email).toBe('user@example.com');
153+
});
154+
155+
it('should redact authorization field in objects', () => {
156+
const logEvent = {
157+
event: {
158+
request: {
159+
method: 'POST',
160+
authorization: 'Bearer token',
161+
},
162+
},
163+
};
164+
165+
const result = cleanLogEvent(logEvent);
166+
167+
expect(result.event.request).toContain('[REDACTED]');
168+
expect(result.event.request).not.toContain('Bearer token');
169+
});
170+
171+
it('should redact password in stringified JSON', () => {
172+
const logEvent = {
173+
event: {
174+
body: '{"email":"user@example.com","password":"secret"}',
175+
},
176+
};
177+
178+
const result = cleanLogEvent(logEvent);
179+
180+
expect(result.event.body).toContain('[REDACTED]');
181+
expect(result.event.body).not.toContain('secret');
182+
});
183+
184+
it('should redact password in nested objects', () => {
185+
const logEvent = {
186+
event: {
187+
request: {
188+
email: 'user@example.com',
189+
password: 'secret',
190+
},
191+
},
192+
};
193+
194+
const result = cleanLogEvent(logEvent);
195+
196+
expect(result.event.request).toContain('[REDACTED]');
197+
expect(result.event.request).not.toContain('secret');
198+
});
199+
});
200+
140201
describe('error objects', () => {
141202
it('should map error object', () => {
142203
const error = new Error('test error');

0 commit comments

Comments
 (0)