Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions actions/setup/js/redact_secrets.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ const BUILT_IN_PATTERNS = [

// Azure tokens
{ name: "Azure Storage Account Key", pattern: /[a-zA-Z0-9+/]{88}==/g },
{ name: "Azure SAS Token", pattern: /\?sv=[0-9-]+&s[rts]=[\w\-]+&sig=[A-Za-z0-9%+/=]+/g },
{ name: "Azure SAS Token", pattern: /\?sv=[0-9-]{1,20}&s[rts]=[\w\-]{1,20}&sig=[A-Za-z0-9%+/=]{1,200}/g },

// Google/GCP tokens
{ name: "Google API Key", pattern: /AIzaSy[0-9A-Za-z_-]{33}/g },
{ name: "Google OAuth Access Token", pattern: /ya29\.[0-9A-Za-z_-]+/g },
{ name: "Google OAuth Access Token", pattern: /ya29\.[0-9A-Za-z_-]{1,800}/g },

// AWS tokens
{ name: "AWS Access Key ID", pattern: /AKIA[0-9A-Z]{16}/g },
Expand Down
76 changes: 75 additions & 1 deletion actions/setup/js/redact_secrets.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ describe("redact_secrets.cjs", () => {
const secretValue = "abc123";
(fs.writeFileSync(testFile, `Secret: ${secretValue} test`), (process.env.GH_AW_SECRET_NAMES = "SIX_CHAR_SECRET"), (process.env.SECRET_SIX_CHAR_SECRET = secretValue));
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
(await eval(`(async () => { ${modifiedScript}; await main(); })()`), expect(fs.readFileSync(testFile, "utf8")).toBe("Secret: abc*** test"));
(await eval(`(async () => { ${modifiedScript}; await main(); })()`), expect(fs.readFileSync(testFile, "utf8")).toBe("Secret: ***REDACTED*** test"));
}),
it("should handle multiple secrets in same file", async () => {
const testFile = path.join(tempDir, "test.txt"),
Expand Down Expand Up @@ -459,6 +459,80 @@ Custom secret: my-secret-123456789012`;
expect(redacted).toContain("***REDACTED***");
expect(redacted).toContain("***REDACTED***");
});

describe("ReDoS protection", () => {
it("should handle pathological Azure SAS Token input without timing out", async () => {
const testFile = path.join(tempDir, "test.txt");
// Create pathological input that would cause ReDoS with unbounded quantifiers
const pathological = `?sv=${"9".repeat(1000)}&srt=${"w".repeat(1000)}&sig=${"A".repeat(1000)}`;
fs.writeFileSync(testFile, `Pathological: ${pathological}`);
process.env.GH_AW_SECRET_NAMES = "";
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);

// This should complete quickly (< 1 second) without hanging
const startTime = Date.now();
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
const duration = Date.now() - startTime;

// Verify it completed quickly (should be < 1000ms, but allow 5000ms for slower CI)
expect(duration).toBeLessThan(5000);

// The pattern shouldn't match due to length bounds
const content = fs.readFileSync(testFile, "utf8");
expect(content).toBe(`Pathological: ${pathological}`);
});

it("should handle pathological Google OAuth token input without timing out", async () => {
const testFile = path.join(tempDir, "test.txt");
// Create pathological input that would cause ReDoS with unbounded quantifiers
const pathological = `ya29.${"A".repeat(5000)}`;
fs.writeFileSync(testFile, `Token: ${pathological}`);
process.env.GH_AW_SECRET_NAMES = "";
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);

// This should complete quickly (< 1 second) without hanging
const startTime = Date.now();
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
const duration = Date.now() - startTime;

// Verify it completed quickly (should be < 1000ms, but allow 5000ms for slower CI)
expect(duration).toBeLessThan(5000);

// The pattern should match up to 800 chars and redact it
const content = fs.readFileSync(testFile, "utf8");
expect(content).toContain("***REDACTED***");
expect(content).not.toBe(`Token: ${pathological}`);
// Should still have unredacted 'A' chars at the end beyond 800 char limit
expect(content).toMatch(/\*\*\*REDACTED\*\*\*A+$/);
});

it("should still match valid Azure SAS tokens within bounds", async () => {
const testFile = path.join(tempDir, "test.txt");
// Valid Azure SAS token within bounds
const validSAS = "?sv=2021-06-08&sr=b&sig=AbCdEf0123456789+/=";
fs.writeFileSync(testFile, `SAS: ${validSAS}`);
process.env.GH_AW_SECRET_NAMES = "";
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
const redacted = fs.readFileSync(testFile, "utf8");
// Should be redacted since it's a valid pattern within bounds
expect(redacted).toBe("SAS: ***REDACTED***");
});

it("should still match valid Google OAuth tokens within bounds", async () => {
const testFile = path.join(tempDir, "test.txt");
// Valid Google OAuth token within bounds (typical length ~100-200 chars)
const validToken = "ya29." + "a".repeat(150);
fs.writeFileSync(testFile, `Token: ${validToken}`);
process.env.GH_AW_SECRET_NAMES = "";
const modifiedScript = redactScript.replace('findFiles("/tmp/gh-aw", targetExtensions)', `findFiles("${tempDir.replace(/\\/g, "\\\\")}", targetExtensions)`);
await eval(`(async () => { ${modifiedScript}; await main(); })()`);
const redacted = fs.readFileSync(testFile, "utf8");
// Should be redacted since it's a valid pattern within bounds
expect(redacted).toBe("Token: ***REDACTED***");
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Google OAuth Access Token"));
});
});
});
}));
});
Loading