27749f473202

record only the redemption attempts that were allowed, capping the list at ten per address (v3.0.73)

time
2026-09-28 05:29 UTC · 2 weeks ago
machine
Junes-MacBook-Pro.local
agent
claude-opus-5-5
session
fix a2 review findings
parent
2cea84c3cba0
3 files package.json+1 −1src/server/connect.ts+3 −1tests/connect.test.ts+16 −0
package.json+1 −1
@@ -1,6 +1,6 @@11 {22 	"name": "kepr",3-	"version": "3.0.72",3+	"version": "3.0.73",44 	"private": true,55 	"type": "module",66 	"scripts": {

view file

src/server/connect.ts+3 −1
@@ -109,8 +109,10 @@109109 		attempts = attempts.filter((attempt) => attempt.at > now - ATTEMPT_WINDOW_MS);110110 		const address = createHash("sha256").update(attemptKey).update(clientIp(c)).digest("hex");111111 		const used = attempts.filter((attempt) => attempt.address === address).length;112-		attempts.push({ at: now, address });112+		// A refused attempt is not recorded: that caps the list at ten entries per address, and keeps113+		// hammering from pushing the block out past the hour.113114 		if (used >= ATTEMPTS_PER_ADDRESS) return noCode(c);115+		attempts.push({ at: now, address });114116 		let code = "";115117 		try {116118 			const fields = await c.req.parseBody();

view file

tests/connect.test.ts+16 −0
@@ -261,6 +261,22 @@261261 		expect((await redeem(app, code, address)).status).toBe(200);262262 	});263263 264+	test("refused attempts are not recorded, so hammering neither grows the list nor the block", async () => {265+		const { app, browser } = await connected();266+		const address = "192.0.2.90";267+		for (let i = 0; i < 10; i++) await redeem(app, "bad", address);268+		// Fifty more inside the hour are all refused, and none of them is remembered: kept, they would269+		// cost memory per request and push the block out an hour past the last one.270+		clock += 3_540_000;271+		setSystemTime(clock);272+		for (let i = 0; i < 50; i++) expect((await redeem(app, "bad", address)).status).toBe(404);273+		// An hour after the tenth counted attempt the peer is served again.274+		clock += 61_000;275+		setSystemTime(clock);276+		const { code } = await issue(browser, "after the block");277+		expect((await redeem(app, code, address)).status).toBe(200);278+	});279+264280 	test("a forged X-Forwarded-For does not buy another bucketful", async () => {265281 		const { app, browser } = await connected();266282 		const { code } = await issue(browser);

view file