- contracts: isSensitiveQueryParam (whole-name match on SENSITIVE_QUERY_PARAMS or isSensitiveKey), the single source for query credential names - rss/redact: drop local SENSITIVE_QUERY_KEYS; param checks go through isSensitiveQueryParam (+ caller extras); REDACTED re-exported from contracts - browser/redact: CREDENTIAL_PARAM built from SENSITIVE_QUERY_PARAMS, JSON keys also checked with isSensitiveKey; keeps only its own rules (path passkey, cookie names, a11y password fields); remove stray regex statement - redact-consistency.test: 24 credential spellings (pass_key, c_secure_pass, refresh_token, password, uid_key, auth, ...) masked by contracts, rss and browser alike; plain params untouched; rss extract/unredact round-trip Co-Authored-By: Claude Opus 5.5 <[email protected]>
72 lines
2.3 KiB
TypeScript
72 lines
2.3 KiB
TypeScript
import { isSensitiveQueryParam, redactUrl as redactUrlShared } from "@umt/contracts";
|
|
import { describe, expect, it } from "vitest";
|
|
import { redactBrowserUrl, urlHasCredential } from "./browser/redact.ts";
|
|
import { extractSecretParams, redactUrl as redactRssUrl, redactText, unredactUrl } from "./rss/redact.ts";
|
|
|
|
// 验收 07 S-4:脱敏键表只有一份(@umt/contracts redact.ts)。contracts(logger + 模型上下文)、rss、browser
|
|
// 三处对同一组凭证参数给出一致结论;任何一处漏遮都会让 passkey 进日志或模型。
|
|
|
|
const SECRET = "s3cr3tVALUE42";
|
|
const CREDENTIAL_PARAMS = [
|
|
"passkey",
|
|
"pass_key",
|
|
"pass-key",
|
|
"PassKey",
|
|
"authkey",
|
|
"auth_key",
|
|
"torrent_pass",
|
|
"rsskey",
|
|
"rss_key",
|
|
"c_secure_pass",
|
|
"downhash",
|
|
"apikey",
|
|
"api_key",
|
|
"access_token",
|
|
"refresh_token",
|
|
"token",
|
|
"auth",
|
|
"sign",
|
|
"signature",
|
|
"credential",
|
|
"secret",
|
|
"password",
|
|
"uid_key",
|
|
"key",
|
|
];
|
|
const PLAIN_PARAMS = ["id", "cat", "page", "q", "tokens", "limit", "type"];
|
|
|
|
describe("脱敏键表三处一致", () => {
|
|
it.each(CREDENTIAL_PARAMS)("凭证参数 %s:contracts / rss / browser 都遮蔽", (p) => {
|
|
const url = `https://pt.example.org/rss.php?id=7&${p}=${SECRET}&cat=401`;
|
|
expect(isSensitiveQueryParam(p)).toBe(true);
|
|
for (const out of [
|
|
redactUrlShared(url),
|
|
redactRssUrl(url),
|
|
redactText(`GET ${url} failed`),
|
|
redactBrowserUrl(url),
|
|
]) {
|
|
expect(out, out).not.toContain(SECRET);
|
|
expect(out).toContain("id=7");
|
|
}
|
|
expect(urlHasCredential(url)).toBe(true);
|
|
// rss 的去重键可还原:取出的 secret 正好填回被遮的参数
|
|
const secrets = extractSecretParams(url);
|
|
expect(Object.values(secrets)).toContain(SECRET);
|
|
expect(unredactUrl(redactRssUrl(url), secrets)).toContain(`${p}=${SECRET}`);
|
|
});
|
|
|
|
it.each(PLAIN_PARAMS)("普通参数 %s:三处都不遮", (p) => {
|
|
const url = `https://pt.example.org/torrents.php?${p}=abc123`;
|
|
expect(isSensitiveQueryParam(p)).toBe(false);
|
|
expect(redactUrlShared(url)).toBe(url);
|
|
expect(redactRssUrl(url)).toBe(url);
|
|
expect(redactBrowserUrl(url)).toBe(url);
|
|
});
|
|
|
|
it("URL userinfo 三处都抹掉", () => {
|
|
const url = `https://user:${SECRET}@pt.example.org/x`;
|
|
for (const out of [redactUrlShared(url), redactRssUrl(url), redactBrowserUrl(url)])
|
|
expect(out).not.toContain(SECRET);
|
|
});
|
|
});
|