-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: remove tokens in unhandled rejection log
- Loading branch information
1 parent
a1324da
commit 62abee9
Showing
4 changed files
with
186 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { inspect } from "node:util"; | ||
|
||
const secretPatterns = { | ||
BEARER_TOKEN: /Bearer [A-Za-z0-9-._~+/]{30,}/, | ||
OAUTH_KEY: /OAuth [A-Za-z0-9-._~+/]{30,}/, | ||
OAUTH_TOKEN: /oauth_token=[A-Za-z0-9-._~+/]{30,}/, | ||
OAUTH_ACCESS_TOKEN: /access_token=[A-Za-z0-9-._~+/]{30,}/, | ||
JWT_TOKEN: /ey[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*/, | ||
AWS_ACCESS_KEY: /AKIA[A-Z0-9]{16}/, | ||
GOOGLE_CLOUD_SECRET_KEY: /AIza[a-zA-Z0-9-_]{35}/, | ||
STRIPE_LIVE_API_KEY: /sk_live_[a-zA-Z0-9]{24}/, | ||
STRIPE_TEST_API_KEY: /sk_test_[a-zA-Z0-9]{24}/, | ||
GITHUB_PAGES_ACCESS_TOKEN: /ghp_[a-zA-Z0-9]{36}/, | ||
SLACK_API_TOKEN: /xox[baprs]-[a-zA-Z0-9]{12,}/, | ||
REFRESH_TOKEN: /refresh_token_[a-zA-Z0-9-_]{32,}/, | ||
SESSION_ID: /sess_[a-zA-Z0-9-_]{22,}/, | ||
} as const; | ||
|
||
export const objectReplaceSecrets = <T>(obj: T): T => { | ||
if (!obj) { | ||
return obj; | ||
} | ||
|
||
if (typeof obj === "string") { | ||
let result = obj as string; | ||
|
||
for (const pattern in secretPatterns) { | ||
result = result.replace(secretPatterns[pattern as keyof typeof secretPatterns], `<${pattern}>`); | ||
} | ||
|
||
return result as T; | ||
} | ||
|
||
if (typeof obj !== "object") { | ||
return obj; | ||
} | ||
|
||
if (Array.isArray(obj)) { | ||
return obj.map(item => objectReplaceSecrets(item)) as T; | ||
} | ||
|
||
const clone = Object.create(obj) as T; | ||
|
||
for (const key of Object.getOwnPropertyNames(obj)) { | ||
clone[key as keyof T] = objectReplaceSecrets(obj[key as keyof T]); | ||
} | ||
|
||
for (const key of Object.getOwnPropertySymbols(obj)) { | ||
clone[key as keyof T] = objectReplaceSecrets(obj[key as keyof T]); | ||
} | ||
|
||
return clone; | ||
}; | ||
|
||
export const utilInspectSafe = <T>(obj: T): string => inspect(objectReplaceSecrets(obj)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import { objectReplaceSecrets } from "../../../src/utils/secret-replacer"; | ||
|
||
describe("objectReplaceSecrets", () => { | ||
describe("String Inputs", () => { | ||
it("should replace OAuth Key patterns", () => { | ||
const input = "OAuth abcdefghijklmnopqrstuvwxyz012345"; | ||
|
||
const result = objectReplaceSecrets(input); | ||
|
||
assert.equal(result, "<OAUTH_KEY>"); | ||
}); | ||
|
||
it("should replace multiple patterns in a single string", () => { | ||
const input = "Bearer abcdefghijklmnopqrstuvwxyz012345 OAuth xyz0123456789abcdefghijklmnopqrstuvwxyz012"; | ||
|
||
const result = objectReplaceSecrets(input); | ||
|
||
assert.equal(result, "<BEARER_TOKEN> <OAUTH_KEY>"); | ||
}); | ||
}); | ||
|
||
describe("Object Inputs", () => { | ||
it("should recursively replace secrets in nested objects", () => { | ||
const input = { | ||
token: "Bearer abcdefghijklmnopqrstuvwxyz012345", | ||
user: { | ||
apiKey: "sk_live_abcdefghijklmnopqrstuvwx", | ||
metadata: { | ||
refreshToken: "refresh_token_abcdefghijklmnopqrstuvwxyz012345", | ||
}, | ||
}, | ||
}; | ||
|
||
const result = objectReplaceSecrets(input); | ||
|
||
assert.deepEqual(result, { | ||
token: "<BEARER_TOKEN>", | ||
user: { | ||
apiKey: "<STRIPE_LIVE_API_KEY>", | ||
metadata: { | ||
refreshToken: "<REFRESH_TOKEN>", | ||
}, | ||
}, | ||
}); | ||
}); | ||
|
||
it("should handle objects with mixed data types", () => { | ||
const input = { | ||
id: 123, | ||
secret: "AKIAIOSFODNN7EXAMPLE", | ||
isActive: true, | ||
details: { | ||
token: "Bearer abcdefghijklmnopqrstuvwxyz012345", | ||
}, | ||
}; | ||
|
||
const result = objectReplaceSecrets(input); | ||
|
||
assert.deepEqual(result, { | ||
id: 123, | ||
secret: "<AWS_ACCESS_KEY>", | ||
isActive: true, | ||
details: { | ||
token: "<BEARER_TOKEN>", | ||
}, | ||
}); | ||
}); | ||
}); | ||
|
||
describe("Array Inputs", () => { | ||
it("should replace secrets in an array of strings", () => { | ||
const input = ["Bearer abcdefghijklmnopqrstuvwxyz012345", "OAuth xyz0123456789abcdefghijklmnopqrstuvwxyz"]; | ||
|
||
const result = objectReplaceSecrets(input); | ||
|
||
assert.deepEqual(result, ["<BEARER_TOKEN>", "<OAUTH_KEY>"]); | ||
}); | ||
|
||
it("should recursively replace secrets in an array of objects", () => { | ||
const input = [ | ||
{ token: "Bearer abcdefghijklmnopqrstuvwxyz012345" }, | ||
{ apiKey: "sk_live_abcdefghijklmnopqrstuvwx" }, | ||
]; | ||
|
||
const result = objectReplaceSecrets(input); | ||
|
||
assert.deepEqual(result, [{ token: "<BEARER_TOKEN>" }, { apiKey: "<STRIPE_LIVE_API_KEY>" }]); | ||
}); | ||
}); | ||
|
||
describe("Edge Cases", () => { | ||
it("should return null for null input", () => { | ||
const input = null; | ||
const result = objectReplaceSecrets(input); | ||
assert.isNull(result); | ||
}); | ||
|
||
it("should return undefined for undefined input", () => { | ||
const input = undefined; | ||
const result = objectReplaceSecrets(input); | ||
assert.isUndefined(result); | ||
}); | ||
|
||
it("should return non-string primitives unchanged", () => { | ||
const input = 123; | ||
const result = objectReplaceSecrets(input); | ||
assert.equal(result, 123); | ||
|
||
const input2 = true; | ||
const result2 = objectReplaceSecrets(input2); | ||
assert.equal(result2, true); | ||
}); | ||
|
||
it("should handle empty strings and objects", () => { | ||
const input1 = ""; | ||
const result1 = objectReplaceSecrets(input1); | ||
assert.equal(result1, ""); | ||
|
||
const input2 = {}; | ||
const result2 = objectReplaceSecrets(input2); | ||
assert.deepEqual(result2, {}); | ||
}); | ||
}); | ||
}); |