Skip to content

Commit fbd6492

Browse files
committed
feat: add redactMessage function to sanitize sensitive data in logs
1 parent 14bb9b8 commit fbd6492

File tree

3 files changed

+123
-4
lines changed

3 files changed

+123
-4
lines changed

nodejs/src/common/log.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,27 @@
11
import winston from "winston";
22
import DailyRotateFile from "winston-daily-rotate-file";
33

4+
export function redactMessage(msg: any): any {
5+
if (typeof msg === "string") {
6+
return msg
7+
.replace(/("password"\s*:\s*")([^"]*)(")/gi, '$1[REDACTED]$3')
8+
.replace(/(token=)([^&\s]+)/gi, "$1[REDACTED]")
9+
.replace(/(\/\/[^:@\s]+:)([^@/\s]+)(@)/gi, "$1[REDACTED]$3");
10+
}
11+
if (msg && typeof msg === "object") {
12+
const clone: any = Array.isArray(msg) ? [] : {};
13+
for (const [k, v] of Object.entries(msg)) {
14+
if (["password", "token", "bearer_token"].includes(k.toLowerCase())) {
15+
clone[k] = "[REDACTED]";
16+
} else {
17+
clone[k] = redactMessage(v);
18+
}
19+
}
20+
return clone;
21+
}
22+
return msg;
23+
}
24+
425
const customFormat = winston.format.printf(
526
({ level, message, label, timestamp }) => {
627
if (
@@ -10,6 +31,7 @@ const customFormat = winston.format.printf(
1031
) {
1132
message = (message as any).toJSON();
1233
}
34+
message = redactMessage(message);
1335
return `${timestamp} [${label}] ${level}: ${message}`;
1436
}
1537
);

nodejs/src/tmq/tmqResponse.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,8 @@ export class WSTmqFetchBlockInfo {
147147
this.withTableName = dataView.getUint8(4) == 1 ? true : false;
148148
this.withSchema = dataView.getUint8(5) == 1 ? true : false;
149149

150-
// let dataBuffer = dataView.buffer.slice(6)
151150
let dataBuffer = new DataView(dataView.buffer, dataView.byteOffset + 6);
152151
let rows = 0;
153-
// const parseStartTime = new Date().getTime();
154-
// console.log("parseBlockInfos blockNum="+ blockNum)
155152
for (let i = 0; i < blockNum; i++) {
156153
let variableInfo = this.parseVariableByteInteger(dataBuffer);
157154
this.taosResult.setPrecision(variableInfo[1].getUint8(17));

nodejs/test/bulkPulling/log.test.ts

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import logger, { setLevel } from "../../src/common/log";
1+
import logger, { setLevel, redactMessage } from "../../src/common/log";
22

33
describe("log level print", () => {
44
test("normal connect", async () => {
@@ -21,3 +21,103 @@ describe("log level print", () => {
2121
expect(isLevel).toEqual(true);
2222
});
2323
});
24+
25+
describe("redact message", () => {
26+
test("redacts password field in JSON-like string", () => {
27+
const input = '{"password": "secret123"}';
28+
const output = redactMessage(input);
29+
expect(output).toBe('{"password": "[REDACTED]"}');
30+
});
31+
32+
test("redacts token in query string", () => {
33+
const input = "https://example.com?token=abcdef123456&other=1";
34+
const output = redactMessage(input);
35+
expect(output).toBe(
36+
"https://example.com?token=[REDACTED]&other=1"
37+
);
38+
});
39+
40+
test("redacts bearer token in query string", () => {
41+
const input = "https://example.com?bearer_token=abcdef123456&other=1";
42+
const output = redactMessage(input);
43+
expect(output).toBe(
44+
"https://example.com?bearer_token=[REDACTED]&other=1"
45+
);
46+
});
47+
48+
test("is case-insensitive for password key", () => {
49+
const input = '{"PassWord": "secret123"}';
50+
const output = redactMessage(input);
51+
expect(output).toBe('{"PassWord": "[REDACTED]"}');
52+
});
53+
54+
test("leaves string without sensitive data unchanged", () => {
55+
const input = "normal message without secrets";
56+
const output = redactMessage(input);
57+
expect(output).toBe(input);
58+
});
59+
60+
test("redacts password and token fields on plain object", () => {
61+
const input = {
62+
user: "u1",
63+
password: "secret",
64+
token: "abc123",
65+
bearer_token: "def456",
66+
other: "keep",
67+
};
68+
const output = redactMessage(input) as any;
69+
70+
expect(output.user).toBe("u1");
71+
expect(output.password).toBe("[REDACTED]");
72+
expect(output.token).toBe("[REDACTED]");
73+
expect(output.bearer_token).toBe("[REDACTED]");
74+
expect(output.other).toBe("keep");
75+
});
76+
77+
test("redacts nested objects recursively", () => {
78+
const input = {
79+
level1: {
80+
password: "p1",
81+
nested: {
82+
token: "t1",
83+
value: 42,
84+
},
85+
},
86+
};
87+
const output = redactMessage(input) as any;
88+
89+
expect(output.level1.password).toBe("[REDACTED]");
90+
expect(output.level1.nested.token).toBe("[REDACTED]");
91+
expect(output.level1.nested.value).toBe(42);
92+
});
93+
94+
test("redacts objects inside array", () => {
95+
const input = [
96+
{ password: "p1" },
97+
{ token: "t2", ok: true },
98+
];
99+
const output = redactMessage(input) as any[];
100+
101+
expect(output[0].password).toBe("[REDACTED]");
102+
expect(output[1].token).toBe("[REDACTED]");
103+
expect(output[1].ok).toBe(true);
104+
});
105+
106+
test("returns same primitive for non-object/non-string", () => {
107+
expect(redactMessage(123)).toBe(123);
108+
expect(redactMessage(null)).toBeNull();
109+
expect(redactMessage(undefined)).toBeUndefined();
110+
});
111+
112+
test("redacts password in ws url user:password@", () => {
113+
const input = "ws://root:taosdata@localhost:6041/ws";
114+
const output = redactMessage(input);
115+
expect(output).toBe("ws://root:[REDACTED]@localhost:6041/ws");
116+
});
117+
118+
test("does not change url without credentials", () => {
119+
const input = "ws://localhost:6041/ws";
120+
const output = redactMessage(input);
121+
expect(output).toBe(input);
122+
});
123+
});

0 commit comments

Comments
 (0)