6d494603f74d
fix: PCH-02-007 add Add-Padding header to HIBP k-anonymity range check (v4.0.0)
- time
- 2026-06-12 20:34 UTC · 4 months ago
- machine
- koh2
- agent
- gpt-5
- session
- pch-02 integration
- parent
- 16d51870a784
↓ peach-browser-6d494603.tar.gzatlas at this save
files changed2 files
src/background/vault.hibp-padding.test.ts+80 −0src/background/vault.ts+5 −1src/background/vault.hibp-padding.test.ts+80 −0
@@ -0,0 +1,80 @@1+// PCH-02-007 regression tests — the HIBP k-anonymity range request must ask2+// for response padding, and the parser must stay correct on padded responses.3+4+import { beforeEach, describe, expect, it, vi } from "vitest";5+6+// SHA-1("password") = 5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD87+const PREFIX = "5BAA6";8+const SUFFIX = "1E4C9B93F3F0682250B6CF8331B7EE68FD8";9+10+describe("PCH-02-007 — HIBP Add-Padding", () => {11+ let fetchMock: ReturnType<typeof vi.fn>;12+13+ beforeEach(() => {14+ vi.resetModules();15+16+ const chromeMock = {17+ storage: {18+ local: {19+ get: vi.fn(async () => ({})),20+ set: vi.fn(async () => undefined),21+ remove: vi.fn(async () => undefined),22+ },23+ },24+ alarms: {25+ clear: vi.fn(),26+ create: vi.fn(),27+ onAlarm: { addListener: vi.fn() },28+ },29+ idle: {30+ setDetectionInterval: vi.fn(),31+ onStateChanged: { addListener: vi.fn() },32+ },33+ } as unknown as typeof chrome;34+ (globalThis as { chrome?: typeof chrome }).chrome = chromeMock;35+36+ fetchMock = vi.fn(async () => ({37+ ok: true,38+ text: async () =>39+ [40+ "0018A45C4D1DEF81644B54AB7F969B88D65:1",41+ `${SUFFIX}:3303003`,42+ "FFFFFFAAAAAABBBBBBCCCCCCDDDDDDEEEEE:0",43+ ].join("\r\n"),44+ }));45+ vi.stubGlobal("fetch", fetchMock);46+ });47+48+ it("sends Add-Padding: true on the range request", async () => {49+ const vault = await import("./vault");50+ await vault.checkBreach("password");51+52+ expect(fetchMock).toHaveBeenCalledWith(53+ `https://api.pwnedpasswords.com/range/${PREFIX}`,54+ expect.objectContaining({55+ headers: expect.objectContaining({ "Add-Padding": "true" }),56+ }),57+ );58+ });59+60+ it("still parses the real breach count out of a padded response", async () => {61+ const vault = await import("./vault");62+ const count = await vault.checkBreach("password");63+ expect(count).toBe(3303003);64+ });65+66+ it("reports 0 when the response holds only padding lines", async () => {67+ fetchMock.mockImplementationOnce(async () => ({68+ ok: true,69+ text: async () =>70+ [71+ "FFFFFFAAAAAABBBBBBCCCCCCDDDDDDEEEEE:0",72+ "EEEEEEAAAAAABBBBBBCCCCCCDDDDDDFFFFF:0",73+ ].join("\r\n"),74+ }));75+76+ const vault = await import("./vault");77+ const count = await vault.checkBreach("password");78+ expect(count).toBe(0);79+ });80+});src/background/vault.ts+5 −1
@@ -227,7 +227,11 @@227227 const suffix = hashHex.slice(5);228228 229229 try {230- const res = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`);230+ // PCH-02-007: request HIBP response padding so response length does not231+ // reveal how many suffixes a queried prefix returned.232+ const res = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`, {233+ headers: { "Add-Padding": "true" },234+ });231235 if (!res.ok) return 0;232236 const text = await res.text();233237 const match = text.split("\r\n").find((l) => l.startsWith(suffix));