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); }); });