diff --git a/.jules/sentinel.md b/.jules/sentinel.md index e644cad..bdb431f 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -39,3 +39,8 @@ **Vulnerability:** The `filterSensitiveFields` function in `src/providers/index.ts` used a manual recursive loop to drop sensitive keys, but it failed to recurse into arrays. If a configuration object contained an array with sensitive nested objects, those secrets would be leaked in debug logs. **Learning:** Manual object traversal for redaction is prone to edge cases (like arrays or circular references). **Prevention:** Use a custom replacer function with `JSON.stringify` to safely and completely redact sensitive fields across all nested structures. + +## 2026-06-29 - Insufficient Broad Pattern Matching for Secrets +**Vulnerability:** The `SENSITIVE_KEY_PATTERNS` array used for redacting sensitive fields did not include patterns for `bearer`, `session`, `jwt`, and `cookie`. Any of these authentication tokens passed within headers or configuration would be logged in plaintext in error logs and debug outputs. +**Learning:** Hardcoded allowlists or blocklists for secrets need to be comprehensive and cover a wide variety of standard authentication schemes. +**Prevention:** Regularly audit redaction pattern lists against modern authentication methods (like JWTs, bearer tokens, and session cookies). diff --git a/src/sensitive.ts b/src/sensitive.ts index 3917a58..6bf387e 100644 --- a/src/sensitive.ts +++ b/src/sensitive.ts @@ -8,6 +8,10 @@ const SENSITIVE_KEY_PATTERNS = [ "auth", "credential", "authorization", + "bearer", + "session", + "jwt", + "cookie", ]; export function isSensitiveKey(key: string): boolean { diff --git a/tests/security.test.ts b/tests/security.test.ts index b8e9808..70ba9c3 100644 --- a/tests/security.test.ts +++ b/tests/security.test.ts @@ -134,6 +134,10 @@ describe("security", () => { expect(isSensitiveKey("x-api-token")).toBe(true); expect(isSensitiveKey("clientSecret")).toBe(true); expect(isSensitiveKey("credentialRef")).toBe(true); + expect(isSensitiveKey("bearerToken")).toBe(true); + expect(isSensitiveKey("session_id")).toBe(true); + expect(isSensitiveKey("jwt_payload")).toBe(true); + expect(isSensitiveKey("cookie")).toBe(true); expect(isSensitiveKey("Content-Type")).toBe(false); }); @@ -171,6 +175,10 @@ describe("security", () => { access_token: "should-be-filtered", apiKey: "should-be-filtered", clientSecret: "should-be-filtered", + bearer_token: "should-be-filtered", + session_id: "should-be-filtered", + jwt_token: "should-be-filtered", + cookie: "should-be-filtered", normal_field: "should-remain", base_url: "should-remain", }; @@ -187,6 +195,10 @@ describe("security", () => { expect(filtered).not.toHaveProperty("access_token"); expect(filtered).not.toHaveProperty("apiKey"); expect(filtered).not.toHaveProperty("clientSecret"); + expect(filtered).not.toHaveProperty("bearer_token"); + expect(filtered).not.toHaveProperty("session_id"); + expect(filtered).not.toHaveProperty("jwt_token"); + expect(filtered).not.toHaveProperty("cookie"); }); it("should be case-insensitive", () => {