mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
fix(logging): scrub every credential prefix the app mints
The log and error redactor missed the registration invite token (hlv_) and the native sign-in handoff code (hlh_). The filter that keeps secrets out of the idempotency replay cache and out of relayed upstream error bodies missed the handoff code and the three MCP OAuth artifacts (hlac_, hlrt_, hlc_). Both now cover every prefix the app issues: hlk_, hlr_, hls_, hle_, hlv_, hlh_, hlac_, hlrt_ and hlc_.
This commit is contained in:
1 parent
d7b5fcd6c2
commit
ba70e19cf9
4 files changed
+27
-6
No files matched your search
@@ -9,6 +9,10 @@ describe("looksSecretShaped", () => {
|
||||
["a share-link token", "see hls_def456"],
|
||||
["an invite token", "hlv_0a1b2c3d"],
|
||||
["an elevation token", "hle_0a1b2c3d"],
|
||||
["a native sign-in handoff code", "hlh_Ab9-xYz_12"],
|
||||
["an MCP authorization code", "hlac_eyJ0eXAi"],
|
||||
["an MCP refresh token", "hlrt_eyJ0eXAi"],
|
||||
["an MCP client id", "hlc_eyJ0eXAi"],
|
||||
["an OpenAI key", "echoed sk-1234567890"],
|
||||
["an Anthropic key", "sk-ant-api03-xyz12345"],
|
||||
])("matches %s", (_label, text) => {
|
||||
|
||||
@@ -117,6 +117,17 @@ describe("redactSecrets", () => {
|
||||
).toBe('{"token": "[REDACTED]"}');
|
||||
});
|
||||
|
||||
it.each([
|
||||
["an invite token", `hlv_${"0a".repeat(32)}`],
|
||||
["a session secret", `hls_${"ab".repeat(32)}`],
|
||||
["an elevation", `hle_${"cd".repeat(32)}`],
|
||||
["a native sign-in handoff code", `hlh_${"Ab9_-".repeat(9)}Ab`],
|
||||
["an MCP refresh token", `hlrt_${"x".repeat(40)}`],
|
||||
])("redacts %s wherever it appears", (_label, token) => {
|
||||
expect(redactSecrets(`code=${token} next`)).not.toContain(token);
|
||||
expect(redactSecrets(`value: ${token}`)).toBe("value: [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");
|
||||
|
||||
@@ -84,18 +84,21 @@ export function redactSecrets(input: string): string {
|
||||
// identifiers. Capture group 1 = the leading separator (preserved).
|
||||
.replace(/(^|[^A-Za-z0-9])sk-(?:ant-)?[A-Za-z0-9_-]{8,}/g, "$1[REDACTED]")
|
||||
// HealthLog hex-bodied credentials: `hlk_` access token, `hlr_` refresh
|
||||
// token, `hls_` session cookie secret (v1.30.32), `hle_` step-up
|
||||
// elevation (v1.30.34). Same boundary-aware shape as the `sk-` rule. The
|
||||
// token, `hls_` session cookie secret (v1.30.32) and clinician share
|
||||
// token, `hle_` step-up elevation (v1.30.34), `hlv_` registration invite.
|
||||
// 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[kres]_[A-Fa-f0-9]{32,}/g, "$1[REDACTED]")
|
||||
.replace(/(^|[^A-Za-z0-9])hl[kresv]_[A-Fa-f0-9]{32,}/g, "$1[REDACTED]")
|
||||
// v1.22.0 — the MCP OAuth bridge artifacts: the authorization code
|
||||
// (`hlac_`), refresh token (`hlrt_`), and DCR client id (`hlc_`). All
|
||||
// are signed, base64url-bodied, and credential-bearing, so they are
|
||||
// scrubbed at the egress boundary like the native token formats above.
|
||||
// `hlh_` is the one-time native sign-in handoff code, base64url too; it
|
||||
// rides a custom-scheme redirect and is exchanged for a token pair.
|
||||
.replace(
|
||||
/(^|[^A-Za-z0-9])hl(?:ac|rt|c)_[A-Za-z0-9_.-]{16,}/g,
|
||||
/(^|[^A-Za-z0-9])hl(?:ac|rt|c|h)_[A-Za-z0-9_.-]{16,}/g,
|
||||
"$1[REDACTED]",
|
||||
)
|
||||
// v1.7.0 — `insurance`/`insuranceNumber`/`kvnr` (German statutory-
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
* such text.
|
||||
*
|
||||
* `hlk_` access tokens, `hlr_` refresh tokens, `hls_` clinician share-link
|
||||
* tokens, `hlv_` registration invite tokens, `hle_` elevation tokens;
|
||||
* tokens and session secrets, `hlv_` registration invite tokens, `hle_`
|
||||
* elevation tokens, `hlh_` native sign-in handoff codes, and the MCP OAuth
|
||||
* artifacts `hlac_` (authorization code), `hlrt_` (refresh token) and `hlc_`
|
||||
* (client id);
|
||||
* `sk-…` / `sk-ant-…` OpenAI and Anthropic keys, in the full token form
|
||||
* only: a bare `sk-` substring ("task-id", "risk-management") is not a key,
|
||||
* and matching it would refuse benign text.
|
||||
@@ -14,7 +17,7 @@
|
||||
* that knows the secrets in play must also refuse text containing them.
|
||||
*/
|
||||
const SECRET_SHAPED =
|
||||
/(?:\b(?:hlk_|hlr_|hls_|hlv_|hle_)[A-Za-z0-9_-]+|\bsk-(?:ant-)?[A-Za-z0-9_-]{8,})/;
|
||||
/(?:\b(?:hlk_|hlr_|hls_|hlv_|hle_|hlh_|hlac_|hlrt_|hlc_)[A-Za-z0-9_-]+|\bsk-(?:ant-)?[A-Za-z0-9_-]{8,})/;
|
||||
|
||||
export function looksSecretShaped(text: string): boolean {
|
||||
return SECRET_SHAPED.test(text);
|
||||
|
||||
Reference in new issue
Block a user