Skip to content

Commit 7e38018

Browse files
authored
Fix secret prefix preservation vulnerability (CWE-200) (#15233)
1 parent faa1930 commit 7e38018

File tree

2 files changed

+39
-43
lines changed

2 files changed

+39
-43
lines changed

actions/setup/js/redact_secrets.cjs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,9 @@ function redactBuiltInPatterns(content) {
8585
for (const { name, pattern } of BUILT_IN_PATTERNS) {
8686
const matches = redacted.match(pattern);
8787
if (matches && matches.length > 0) {
88-
// Redact each match
88+
// Redact each match with fixed-length string
89+
const replacement = "***REDACTED***";
8990
for (const match of matches) {
90-
const prefix = match.substring(0, 3);
91-
const asterisks = "*".repeat(Math.max(0, match.length - 3));
92-
const replacement = prefix + asterisks;
9391
redacted = redacted.split(match).join(replacement);
9492
}
9593
redactionCount += matches.length;
@@ -120,10 +118,8 @@ function redactSecrets(content, secretValues) {
120118
// Count occurrences before replacement
121119
// Use split and join for exact string matching (not regex)
122120
// This is safer than regex as it doesn't interpret special characters
123-
// Show first 3 letters followed by asterisks for the remaining length
124-
const prefix = secretValue.substring(0, 3);
125-
const asterisks = "*".repeat(Math.max(0, secretValue.length - 3));
126-
const replacement = prefix + asterisks;
121+
// Use fixed-length redaction string without prefix preservation
122+
const replacement = "***REDACTED***";
127123
const parts = redacted.split(secretValue);
128124
const occurrences = parts.length - 1;
129125
if (occurrences > 0) {

actions/setup/js/redact_secrets.test.cjs

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe("redact_secrets.cjs", () => {
5656
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
5757
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
5858
const redactedContent = fs.readFileSync(testFile, "utf8");
59-
(expect(redactedContent).toBe("Secret: ghp************************************* and another ghp*************************************"),
59+
(expect(redactedContent).toBe("Secret: ***REDACTED*** and another ***REDACTED***"),
6060
expect(mockCore.info).toHaveBeenCalledWith("Starting secret redaction in /tmp/gh-aw and /opt/gh-aw directories"),
6161
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Secret redaction complete")));
6262
}),
@@ -70,9 +70,9 @@ describe("redact_secrets.cjs", () => {
7070
(process.env.SECRET_API_KEY3 = "api-key-789"));
7171
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
7272
(await eval(`(async () => { ${modifiedScript}; await main(); })()`),
73-
expect(fs.readFileSync(path.join(tempDir, "test1.txt"), "utf8")).toBe("Secret: api********"),
74-
expect(fs.readFileSync(path.join(tempDir, "test2.json"), "utf8")).toBe('{"key": "api********"}'),
75-
expect(fs.readFileSync(path.join(tempDir, "test3.log"), "utf8")).toBe("Log: api********"));
73+
expect(fs.readFileSync(path.join(tempDir, "test1.txt"), "utf8")).toBe("Secret: ***REDACTED***"),
74+
expect(fs.readFileSync(path.join(tempDir, "test2.json"), "utf8")).toBe('{"key": "***REDACTED***"}'),
75+
expect(fs.readFileSync(path.join(tempDir, "test3.log"), "utf8")).toBe("Log: ***REDACTED***"));
7676
}),
7777
it("should use core.info for logging hits", async () => {
7878
const testFile = path.join(tempDir, "test.txt"),
@@ -109,7 +109,7 @@ describe("redact_secrets.cjs", () => {
109109
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
110110
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
111111
const redacted = fs.readFileSync(testFile, "utf8");
112-
expect(redacted).toBe("Token1: ghp*************************************\nToken2: sk-*********************\nToken1 again: ghp*************************************");
112+
expect(redacted).toBe("Token1: ***REDACTED***\nToken2: ***REDACTED***\nToken1 again: ***REDACTED***");
113113
}),
114114
it("should handle empty secret values gracefully", async () => {
115115
const testFile = path.join(tempDir, "test.txt");
@@ -131,10 +131,10 @@ describe("redact_secrets.cjs", () => {
131131
(process.env.SECRET_API_JSONL = "api-key-jsonl123"));
132132
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
133133
(await eval(`(async () => { ${modifiedScript}; await main(); })()`),
134-
expect(fs.readFileSync(path.join(tempDir, "test.md"), "utf8")).toBe("# Markdown\nSecret: api**********"),
135-
expect(fs.readFileSync(path.join(tempDir, "test.mdx"), "utf8")).toBe("# MDX\nSecret: api***********"),
136-
expect(fs.readFileSync(path.join(tempDir, "test.yml"), "utf8")).toBe("# YAML\nkey: api***********"),
137-
expect(fs.readFileSync(path.join(tempDir, "test.jsonl"), "utf8")).toBe('{"key": "api*************"}'));
134+
expect(fs.readFileSync(path.join(tempDir, "test.md"), "utf8")).toBe("# Markdown\nSecret: ***REDACTED***"),
135+
expect(fs.readFileSync(path.join(tempDir, "test.mdx"), "utf8")).toBe("# MDX\nSecret: ***REDACTED***"),
136+
expect(fs.readFileSync(path.join(tempDir, "test.yml"), "utf8")).toBe("# YAML\nkey: ***REDACTED***"),
137+
expect(fs.readFileSync(path.join(tempDir, "test.jsonl"), "utf8")).toBe('{"key": "***REDACTED***"}'));
138138
}));
139139
}),
140140
describe("built-in pattern detection", () => {
@@ -147,7 +147,7 @@ describe("redact_secrets.cjs", () => {
147147
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
148148
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
149149
const redacted = fs.readFileSync(testFile, "utf8");
150-
expect(redacted).toBe("Using token: ghp************************************* in this file");
150+
expect(redacted).toBe("Using token: ***REDACTED*** in this file");
151151
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("GitHub Personal Access Token"));
152152
});
153153

@@ -159,7 +159,7 @@ describe("redact_secrets.cjs", () => {
159159
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
160160
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
161161
const redacted = fs.readFileSync(testFile, "utf8");
162-
expect(redacted).toBe("Server token: ghs*************************************");
162+
expect(redacted).toBe("Server token: ***REDACTED***");
163163
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("GitHub Server-to-Server Token"));
164164
});
165165

@@ -171,7 +171,7 @@ describe("redact_secrets.cjs", () => {
171171
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
172172
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
173173
const redacted = fs.readFileSync(testFile, "utf8");
174-
expect(redacted).toBe("OAuth: gho*************************************");
174+
expect(redacted).toBe("OAuth: ***REDACTED***");
175175
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("GitHub OAuth Access Token"));
176176
});
177177

@@ -183,7 +183,7 @@ describe("redact_secrets.cjs", () => {
183183
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
184184
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
185185
const redacted = fs.readFileSync(testFile, "utf8");
186-
expect(redacted).toBe("User token: ghu*************************************");
186+
expect(redacted).toBe("User token: ***REDACTED***");
187187
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("GitHub User Access Token"));
188188
});
189189

@@ -195,7 +195,7 @@ describe("redact_secrets.cjs", () => {
195195
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
196196
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
197197
const redacted = fs.readFileSync(testFile, "utf8");
198-
expect(redacted).toBe("Fine-grained PAT: git******************************************************************************************");
198+
expect(redacted).toBe("Fine-grained PAT: ***REDACTED***");
199199
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("GitHub Fine-grained PAT"));
200200
});
201201

@@ -207,7 +207,7 @@ describe("redact_secrets.cjs", () => {
207207
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
208208
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
209209
const redacted = fs.readFileSync(testFile, "utf8");
210-
expect(redacted).toBe("Refresh: ghr*************************************");
210+
expect(redacted).toBe("Refresh: ***REDACTED***");
211211
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("GitHub Refresh Token"));
212212
});
213213

@@ -221,7 +221,7 @@ describe("redact_secrets.cjs", () => {
221221
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
222222
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
223223
const redacted = fs.readFileSync(testFile, "utf8");
224-
expect(redacted).toBe("PAT: ghp*************************************\nServer: ghs*************************************\nOAuth: gho*************************************");
224+
expect(redacted).toBe("PAT: ***REDACTED***\nServer: ***REDACTED***\nOAuth: ***REDACTED***");
225225
});
226226
});
227227

@@ -234,7 +234,7 @@ describe("redact_secrets.cjs", () => {
234234
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
235235
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
236236
const redacted = fs.readFileSync(testFile, "utf8");
237-
expect(redacted).toBe("Azure Key: ABC***************************************************************************************");
237+
expect(redacted).toBe("Azure Key: ***REDACTED***");
238238
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Azure Storage Account Key"));
239239
});
240240

@@ -260,7 +260,7 @@ describe("redact_secrets.cjs", () => {
260260
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
261261
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
262262
const redacted = fs.readFileSync(testFile, "utf8");
263-
expect(redacted).toBe("Google API Key: AIz************************************");
263+
expect(redacted).toBe("Google API Key: ***REDACTED***");
264264
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Google API Key"));
265265
});
266266

@@ -272,7 +272,7 @@ describe("redact_secrets.cjs", () => {
272272
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
273273
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
274274
const redacted = fs.readFileSync(testFile, "utf8");
275-
expect(redacted).toBe("OAuth Token: ya2********************************************");
275+
expect(redacted).toBe("OAuth Token: ***REDACTED***");
276276
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Google OAuth Access Token"));
277277
});
278278
});
@@ -286,7 +286,7 @@ describe("redact_secrets.cjs", () => {
286286
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
287287
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
288288
const redacted = fs.readFileSync(testFile, "utf8");
289-
expect(redacted).toBe("AWS Key: AKI*****************");
289+
expect(redacted).toBe("AWS Key: ***REDACTED***");
290290
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("AWS Access Key ID"));
291291
});
292292
});
@@ -300,7 +300,7 @@ describe("redact_secrets.cjs", () => {
300300
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
301301
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
302302
const redacted = fs.readFileSync(testFile, "utf8");
303-
expect(redacted).toBe("OpenAI Key: sk-************************************************");
303+
expect(redacted).toBe("OpenAI Key: ***REDACTED***");
304304
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("OpenAI API Key"));
305305
});
306306

@@ -312,7 +312,7 @@ describe("redact_secrets.cjs", () => {
312312
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
313313
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
314314
const redacted = fs.readFileSync(testFile, "utf8");
315-
expect(redacted).toBe("OpenAI Project Key: sk-************************************************************");
315+
expect(redacted).toBe("OpenAI Project Key: ***REDACTED***");
316316
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("OpenAI Project API Key"));
317317
});
318318
});
@@ -326,7 +326,7 @@ describe("redact_secrets.cjs", () => {
326326
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
327327
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
328328
const redacted = fs.readFileSync(testFile, "utf8");
329-
expect(redacted).toBe("Anthropic Key: sk-*********************************************************************************************************");
329+
expect(redacted).toBe("Anthropic Key: ***REDACTED***");
330330
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Anthropic API Key"));
331331
});
332332
});
@@ -342,7 +342,7 @@ describe("redact_secrets.cjs", () => {
342342
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
343343
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
344344
const redacted = fs.readFileSync(testFile, "utf8");
345-
expect(redacted).toBe("GitHub: ghp*************************************\nCustom: my-**************************");
345+
expect(redacted).toBe("GitHub: ***REDACTED***\nCustom: ***REDACTED***");
346346
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("GitHub Personal Access Token"));
347347
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("occurrence(s) of a secret"));
348348
});
@@ -357,7 +357,7 @@ describe("redact_secrets.cjs", () => {
357357
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
358358
const redacted = fs.readFileSync(testFile, "utf8");
359359
// Built-in pattern should redact it first
360-
expect(redacted).toBe("Token: ghp************************************* repeated: ghp*************************************");
360+
expect(redacted).toBe("Token: ***REDACTED*** repeated: ***REDACTED***");
361361
});
362362
});
363363

@@ -380,7 +380,7 @@ describe("redact_secrets.cjs", () => {
380380
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
381381
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
382382
const redacted = fs.readFileSync(testFile, "utf8");
383-
expect(redacted).toBe("First: ghp*************************************\nSecond: ghp*************************************\nThird: ghp*************************************");
383+
expect(redacted).toBe("First: ***REDACTED***\nSecond: ***REDACTED***\nThird: ***REDACTED***");
384384
});
385385

386386
it("should handle secrets in JSON content", async () => {
@@ -392,8 +392,8 @@ describe("redact_secrets.cjs", () => {
392392
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
393393
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
394394
const redacted = fs.readFileSync(testFile, "utf8");
395-
expect(redacted).toContain("ghp*************************************");
396-
expect(redacted).toContain("AIz************************************");
395+
expect(redacted).toContain("***REDACTED***");
396+
expect(redacted).toContain("***REDACTED***");
397397
});
398398

399399
it("should handle secrets in log files with timestamps", async () => {
@@ -404,7 +404,7 @@ describe("redact_secrets.cjs", () => {
404404
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
405405
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
406406
const redacted = fs.readFileSync(testFile, "utf8");
407-
expect(redacted).toBe("[2024-01-01 12:00:00] INFO: Using token ghp************************************* for authentication");
407+
expect(redacted).toBe("[2024-01-01 12:00:00] INFO: Using token ***REDACTED*** for authentication");
408408
});
409409

410410
it("should not redact partial matches", async () => {
@@ -427,7 +427,7 @@ describe("redact_secrets.cjs", () => {
427427
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
428428
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
429429
const redacted = fs.readFileSync(testFile, "utf8");
430-
expect(redacted).toBe("https://api.github.com?token=ghp*************************************");
430+
expect(redacted).toBe("https://api.github.com?token=***REDACTED***");
431431
});
432432

433433
it("should handle multiline content with various token types", async () => {
@@ -446,11 +446,11 @@ Custom secret: my-secret-123456789012`;
446446
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
447447
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
448448
const redacted = fs.readFileSync(testFile, "utf8");
449-
expect(redacted).toContain("ghp*************************************");
450-
expect(redacted).toContain("ABC***************************************************************************************");
451-
expect(redacted).toContain("AIz************************************");
452-
expect(redacted).toContain("AKI*****************");
453-
expect(redacted).toContain("my-*******************");
449+
expect(redacted).toContain("***REDACTED***");
450+
expect(redacted).toContain("***REDACTED***");
451+
expect(redacted).toContain("***REDACTED***");
452+
expect(redacted).toContain("***REDACTED***");
453+
expect(redacted).toContain("***REDACTED***");
454454
});
455455
});
456456
}));

0 commit comments

Comments
 (0)