Skip to content

Commit b38436d

Browse files
authored
Enhance sanitization logic w/ base common cases (#18639)
## Description This change intends to prevent secrets getting leaked into the logs. Modifications were made to sanitization formatter, so that known cases were sensitive data is leaked gets formatted straight away, and that sanitization logic is performed and consolidated inside the telemetry library. This common cases are also performed before searching for unknown cases when the sanitization is enabled. In the future, the plan is to monitor for new detected cases, and add them to the common cases when detected (which are always enabled and performed in an efficient way).
1 parent ef97697 commit b38436d

File tree

7 files changed

+173
-34
lines changed

7 files changed

+173
-34
lines changed

server/routerlicious/packages/services-telemetry/src/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,7 @@ export {
4040
getGlobalTelemetryContext,
4141
setGlobalTelemetryContext,
4242
} from "./telemetryContext";
43-
export { SanitizationLumberFormatter } from "./sanitizationLumberFormatter";
43+
export {
44+
SanitizationLumberFormatter,
45+
BaseSanitizationLumberFormatter,
46+
} from "./sanitizationLumberFormatter";

server/routerlicious/packages/services-telemetry/src/lumberjack.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import {
1414
ILumberFormatter,
1515
} from "./resources";
1616
import { getGlobal, getGlobalTelemetryContext } from "./telemetryContext";
17-
import { SanitizationLumberFormatter } from "./sanitizationLumberFormatter";
17+
import {
18+
BaseSanitizationLumberFormatter,
19+
SanitizationLumberFormatter,
20+
} from "./sanitizationLumberFormatter";
1821

1922
/**
2023
* @internal
@@ -170,6 +173,8 @@ export class Lumberjack {
170173
const lumberFormatters: ILumberFormatter[] = [];
171174
if (this._options.enableSanitization) {
172175
lumberFormatters.push(new SanitizationLumberFormatter());
176+
} else {
177+
lumberFormatters.push(new BaseSanitizationLumberFormatter());
173178
}
174179
this._formatters = lumberFormatters;
175180

@@ -187,6 +192,7 @@ export class Lumberjack {
187192
this._engineList,
188193
this._schemaValidators,
189194
properties,
195+
this._formatters,
190196
);
191197
}
192198

server/routerlicious/packages/services-telemetry/src/lumberjackCommonTestUtils.ts

+45
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,25 @@ export class TestFormatter implements ILumberFormatter {
5757
public transform(lumber: Lumber) {}
5858
}
5959

60+
/**
61+
* @internal
62+
*/
6063
export class TestSensitiveException extends Error {
6164
public password = "password";
6265
public apiKey = "apikey";
6366
public sessionId = "sessionid";
6467
public cookie = "cookie";
6568
public token = "token";
6669
public secret = "secret";
70+
public authorization = "authorization";
71+
public someProperty = {
72+
pass: "pass",
73+
};
6774
}
6875

76+
/**
77+
* @internal
78+
*/
6979
export class TestRegularException extends Error {
7080
public field1 = "field1";
7181
public field2 = "field2";
@@ -74,3 +84,38 @@ export class TestRegularException extends Error {
7484
public field5 = "field5";
7585
public field6 = "field6";
7686
}
87+
88+
/**
89+
* @internal
90+
*/
91+
export class TestBaseSensitiveException extends Error {
92+
public command: any;
93+
public request: any;
94+
public response: any;
95+
public config: any;
96+
97+
public constructor(message?: string) {
98+
super(message);
99+
100+
this.command = {
101+
args: ["arg1", "arg2"],
102+
};
103+
104+
const authorization = "Bearer aFakeToken1234";
105+
const Authorization = "Bearer anotherFakeToken1234";
106+
107+
this.request = {
108+
headers: { authorization },
109+
};
110+
111+
this.response = {
112+
request: {
113+
headers: { Authorization },
114+
},
115+
};
116+
117+
this.config = {
118+
headers: { Authorization },
119+
};
120+
}
121+
}

server/routerlicious/packages/services-telemetry/src/sanitizationLumberFormatter.ts

+51-10
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,48 @@ import { Lumber } from "./lumber";
99
/**
1010
* @internal
1111
*/
12-
export class SanitizationLumberFormatter implements ILumberFormatter {
12+
export class BaseSanitizationLumberFormatter implements ILumberFormatter {
13+
readonly redactedStr = "[LUMBER_REDACTED]";
14+
15+
public transform(lumber: Lumber<string>): void {
16+
if (lumber.exception) {
17+
this.sanitizeCommonCases(lumber.exception);
18+
}
19+
}
20+
21+
private sanitizeCommonCases(error: any): void {
22+
if (error?.command?.args) {
23+
error.command.args = [this.redactedStr];
24+
}
25+
this.handleHeadersSanitization(error);
26+
}
27+
28+
private handleHeadersSanitization(error: any): void {
29+
if (error?.request?.headers?.authorization || error?.request?.headers?.Authorization) {
30+
delete error.request.headers.authorization;
31+
delete error.request.headers.Authorization;
32+
error.request.headers.authorization = this.redactedStr;
33+
}
34+
if (
35+
error?.response?.request?.headers?.authorization ||
36+
error?.response?.request?.headers?.Authorization
37+
) {
38+
delete error.response.request.headers.authorization;
39+
delete error.response.request.headers.Authorization;
40+
error.response.request.headers.authorization = this.redactedStr;
41+
}
42+
if (error?.config?.headers?.authorization || error?.config?.headers?.Authorization) {
43+
delete error.config.headers.authorization;
44+
delete error.config.headers.Authorization;
45+
error.config.headers.authorization = this.redactedStr;
46+
}
47+
}
48+
}
49+
50+
/**
51+
* @internal
52+
*/
53+
export class SanitizationLumberFormatter extends BaseSanitizationLumberFormatter {
1354
private readonly sensitiveKeys = [
1455
/cookie/i,
1556
/passw(or)?d/i,
@@ -19,29 +60,29 @@ export class SanitizationLumberFormatter implements ILumberFormatter {
1960
/token/i,
2061
/api[._-]?key/i,
2162
/session[._-]?id/i,
63+
/^auth/i,
2264
];
2365

24-
private readonly redactedStr = "[LUMBER_REDACTED]";
25-
2666
public transform(lumber: Lumber<string>): void {
67+
super.transform(lumber);
68+
2769
if (lumber.exception) {
2870
const sensitiveKeys = new Set<string>();
29-
30-
this.redactException(lumber.exception, sensitiveKeys);
71+
this.redactException(lumber.exception, sensitiveKeys, "");
3172

3273
if (sensitiveKeys.size > 0) {
3374
lumber.setProperty("detectedSensitiveKeys", sensitiveKeys);
3475
}
3576
}
3677
}
3778

38-
private redactException(exception: Error, sensitiveKeys: Set<string>): void {
39-
Object.keys(exception).forEach((keyStr, value) => {
40-
if (typeof value === "object" && value !== null) {
41-
this.redactException(value, sensitiveKeys);
79+
private redactException(exception: Error, sensitiveKeys: Set<string>, keyPath: string): void {
80+
Object.keys(exception).forEach((keyStr) => {
81+
if (typeof exception[keyStr] === "object" && exception[keyStr] !== null) {
82+
this.redactException(exception[keyStr], sensitiveKeys, `${keyPath}.${keyStr}`);
4283
} else if (this.sensitiveKeys.some((regex) => regex.test(keyStr))) {
4384
exception[keyStr] = this.redactedStr;
44-
sensitiveKeys.add(keyStr);
85+
sensitiveKeys.add(`${keyPath}.${keyStr}`);
4586
}
4687
});
4788
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*!
2+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
import assert from "assert";
7+
import { Lumber } from "../lumber";
8+
import { LumberEventName } from "../lumberEventNames";
9+
import * as resources from "../resources";
10+
import {
11+
TestEngine1,
12+
TestRegularException,
13+
TestBaseSensitiveException,
14+
} from "../lumberjackCommonTestUtils";
15+
import { BaseSanitizationLumberFormatter } from "../sanitizationLumberFormatter";
16+
17+
describe("BaseSanitizationLumberFormatter", () => {
18+
it("Formatter goes through an exception without sensitive data and doesn't modify it.", () => {
19+
const errorMessage = "ErrorMessage";
20+
const engine = new TestEngine1();
21+
const regularException = new TestRegularException();
22+
const formatter = new BaseSanitizationLumberFormatter();
23+
const lumber = new Lumber(LumberEventName.UnitTestEvent, resources.LumberType.Metric, [
24+
engine,
25+
]);
26+
27+
lumber.error(errorMessage, regularException);
28+
formatter.transform(lumber);
29+
30+
assert.strictEqual(lumber.exception, regularException);
31+
assert.strictEqual(lumber.properties.size, 0);
32+
});
33+
34+
it("Formatter goes through an exception with sensitive data and modifies it.", () => {
35+
const redactedStr = "[LUMBER_REDACTED]";
36+
const errorMessage = "ErrorMessage";
37+
const engine = new TestEngine1();
38+
const sensitiveException = new TestBaseSensitiveException();
39+
const formatter = new BaseSanitizationLumberFormatter();
40+
const lumber = new Lumber(LumberEventName.UnitTestEvent, resources.LumberType.Metric, [
41+
engine,
42+
]);
43+
44+
lumber.error(errorMessage, sensitiveException);
45+
formatter.transform(lumber);
46+
47+
const baseLumberException = lumber.exception as TestBaseSensitiveException;
48+
49+
assert.strictEqual(baseLumberException.command.args[0], redactedStr);
50+
assert.strictEqual(baseLumberException.request.headers.authorization, redactedStr);
51+
assert.strictEqual(baseLumberException.response.request.headers.authorization, redactedStr);
52+
});
53+
});

server/routerlicious/packages/services-telemetry/src/test/sanitizationLumberFormatter.spec.ts

+11-7
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,17 @@ describe("SanitizationLumberFormatter", () => {
5353
assert.strictEqual(lumberException.cookie, redactedStr);
5454
assert.strictEqual(lumberException.token, redactedStr);
5555
assert.strictEqual(lumberException.secret, redactedStr);
56+
assert.strictEqual(lumberException.authorization, redactedStr);
57+
assert.strictEqual(lumberException.someProperty.pass, redactedStr);
5658

57-
assert.strictEqual(sensitiveKeys.size, 6);
58-
assert.strictEqual(sensitiveKeys.has("password"), true);
59-
assert.strictEqual(sensitiveKeys.has("apiKey"), true);
60-
assert.strictEqual(sensitiveKeys.has("sessionId"), true);
61-
assert.strictEqual(sensitiveKeys.has("cookie"), true);
62-
assert.strictEqual(sensitiveKeys.has("token"), true);
63-
assert.strictEqual(sensitiveKeys.has("secret"), true);
59+
assert.strictEqual(sensitiveKeys.size, 8);
60+
assert.strictEqual(sensitiveKeys.has(".password"), true);
61+
assert.strictEqual(sensitiveKeys.has(".apiKey"), true);
62+
assert.strictEqual(sensitiveKeys.has(".sessionId"), true);
63+
assert.strictEqual(sensitiveKeys.has(".cookie"), true);
64+
assert.strictEqual(sensitiveKeys.has(".token"), true);
65+
assert.strictEqual(sensitiveKeys.has(".secret"), true);
66+
assert.strictEqual(sensitiveKeys.has(".authorization"), true);
67+
assert.strictEqual(sensitiveKeys.has(".someProperty.pass"), true);
6468
});
6569
});

server/routerlicious/packages/services-utils/src/auth.ts

+2-15
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,6 @@ function getTokenFromRequest(request: Request): string {
165165

166166
const defaultMaxTokenLifetimeSec = 60 * 60; // 1 hour
167167

168-
// Used to sanitize Redis error object and remove sensitive information
169-
function sanitizeError(error: any) {
170-
if (error?.command?.args) {
171-
error.command.args = ["REDACTED"];
172-
}
173-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
174-
return error;
175-
}
176-
177168
/**
178169
* @internal
179170
*/
@@ -219,11 +210,7 @@ export async function verifyToken(
219210
// Check token cache first
220211
if ((options.enableTokenCache || options.ensureSingleUseToken) && options.tokenCache) {
221212
const cachedToken = await options.tokenCache.get(token).catch((error) => {
222-
Lumberjack.error(
223-
"Unable to retrieve cached JWT",
224-
logProperties,
225-
sanitizeError(error),
226-
);
213+
Lumberjack.error("Unable to retrieve cached JWT", logProperties, error);
227214
return false;
228215
});
229216

@@ -249,7 +236,7 @@ export async function verifyToken(
249236
tokenLifetimeMs !== undefined ? Math.floor(tokenLifetimeMs / 1000) : undefined,
250237
)
251238
.catch((error) => {
252-
Lumberjack.error("Unable to cache JWT", logProperties, sanitizeError(error));
239+
Lumberjack.error("Unable to cache JWT", logProperties, error);
253240
});
254241
}
255242
} catch (error) {

0 commit comments

Comments
 (0)