mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
fix(security): redact standalone hlk_ / hlr_ native API tokens in logs
The Bearer rule already covers `Authorization: Bearer hlk_…` headers, but standalone tokens in error bodies, JSON dumps, or raw debug strings were untouched. The idempotency replay-cache rejects bodies containing `hlk_` / `hlr_` (CLAUDE.md headless-client-API patterns) exactly so cached responses can never echo a token back; this commit adds the matching egress guard for log/error surfaces. Boundary-aware shape mirrors the v1.5 phase-2 sk- regex tightening so benign identifiers like `html_render` or short `hlk_short` strings are not over-matched. Closes H4 from .planning/phase-6-code-review-findings.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
8e17f227e4
commit
f0cfd26d96
2 files changed
+32
No files matched your search
@@ -85,6 +85,32 @@ describe("redactSecrets", () => {
|
|||||||
expect(redactSecrets(",sk-1234567890abcdef,")).toBe(",[REDACTED],");
|
expect(redactSecrets(",sk-1234567890abcdef,")).toBe(",[REDACTED],");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("redacts standalone HealthLog native API tokens (hlk_ / hlr_)", () => {
|
||||||
|
// Start of string.
|
||||||
|
expect(
|
||||||
|
redactSecrets("hlk_abc123def4567890abcdef1234567890ABCDEF1234567890abcd"),
|
||||||
|
).toBe("[REDACTED]");
|
||||||
|
expect(
|
||||||
|
redactSecrets("hlr_abc123def4567890abcdef1234567890ABCDEF1234567890abcd"),
|
||||||
|
).toBe("[REDACTED]");
|
||||||
|
// Preceded by separator, leading separator preserved.
|
||||||
|
expect(
|
||||||
|
redactSecrets(
|
||||||
|
"refresh: hlr_abc123def4567890abcdef1234567890ABCDEF1234567890abcd end",
|
||||||
|
),
|
||||||
|
).toBe("refresh: [REDACTED] end");
|
||||||
|
expect(
|
||||||
|
redactSecrets('{"token": "hlk_0123456789abcdef0123456789abcdef0123"}'),
|
||||||
|
).toBe('{"token": "[REDACTED]"}');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does NOT redact non-token strings starting with `hl`", () => {
|
||||||
|
expect(redactSecrets("html_render finished")).toBe("html_render finished");
|
||||||
|
expect(redactSecrets("healthcheck ok")).toBe("healthcheck ok");
|
||||||
|
// Too-short trailing hex — keep as-is.
|
||||||
|
expect(redactSecrets("hlk_short")).toBe("hlk_short");
|
||||||
|
});
|
||||||
|
|
||||||
it("handles multiple secrets in one string", () => {
|
it("handles multiple secrets in one string", () => {
|
||||||
// Bearer matches `\S+` so it greedily consumes `hlk_x;` — that's
|
// Bearer matches `\S+` so it greedily consumes `hlk_x;` — that's
|
||||||
// safe (over-redaction is fine when the alternative is leaking).
|
// safe (over-redaction is fine when the alternative is leaking).
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ export function redactSecrets(input: string): string {
|
|||||||
// tiny safety margin against `sk-1`-style false-positives in code
|
// tiny safety margin against `sk-1`-style false-positives in code
|
||||||
// identifiers. Capture group 1 = the leading separator (preserved).
|
// identifiers. Capture group 1 = the leading separator (preserved).
|
||||||
.replace(/(^|[^A-Za-z0-9])sk-(?:ant-)?[A-Za-z0-9_-]{8,}/g, "$1[REDACTED]")
|
.replace(/(^|[^A-Za-z0-9])sk-(?:ant-)?[A-Za-z0-9_-]{8,}/g, "$1[REDACTED]")
|
||||||
|
// HealthLog native API tokens (`hlk_<64hex>` access, `hlr_<64hex>`
|
||||||
|
// refresh). Same boundary-aware shape as the `sk-` rule. The
|
||||||
|
// idempotency replay-cache already rejects bodies containing
|
||||||
|
// these prefixes (CLAUDE.md headless-client-API patterns); this
|
||||||
|
// is the matching egress guard for log/error surfaces.
|
||||||
|
.replace(/(^|[^A-Za-z0-9])hl[kr]_[A-Fa-f0-9]{32,}/g, "$1[REDACTED]")
|
||||||
.replace(
|
.replace(
|
||||||
/([?&])(secret|code|token|api[_-]?key)=[^&\s]+/gi,
|
/([?&])(secret|code|token|api[_-]?key)=[^&\s]+/gi,
|
||||||
"$1$2=[REDACTED]",
|
"$1$2=[REDACTED]",
|
||||||
|
|||||||
Reference in new issue
Block a user