731c1ce0a9b6

close the connect review findings and follow koh3 3.0.94's renamed view command (v3.0.63)

time
2026-09-27 15:10 UTC · 2 weeks ago
machine
Junes-MacBook-Pro.local
agent
deepseek/deepseek-flash
session
kepr connect and public steal
parent
b16612d47778
11 files package.json+1 −1src/server/app.ts+5 −2src/server/connect.ts+3 −3tests/connect.test.ts+9 −1tests/fixture.ts+3 −3tests/front.test.ts+2 −2tests/koh-version.txt+1 −1tests/pages.test.ts+4 −1tests/releases.test.ts+1 −1tests/sync.test.ts+1 −1tests/tarball.test.ts+1 −1
package.json+1 −1
@@ -1,6 +1,6 @@11 {22 	"name": "kepr",3-	"version": "3.0.62",3+	"version": "3.0.63",44 	"private": true,55 	"type": "module",66 	"scripts": {

view file

src/server/app.ts+5 −2
@@ -66,8 +66,11 @@6666 		bodyLimit({6767 			maxSize: MAX_BODY_BYTES,6868 			onError: (c) =>69-				// An oversized redemption is one more failed redemption, not a different answer.70-				c.req.path === "/api/connect" ? noCode(c) : c.text("Request body too large", 413),69+				// An oversized redemption is one more failed redemption — but only where the route70+				// exists: without write keys it is an unknown path and answers as one.71+				c.req.path === "/api/connect" && connectKeys(config)72+					? noCode(c)73+					: c.text("Request body too large", 413),7174 		}),7275 	);7376 	// `/static/assets/` is the operator's uploads in `data/assets/` (§19), such as the signature.

view file

src/server/connect.ts+3 −3
@@ -32,9 +32,9 @@3232 	if (typeof value !== "string") return null;3333 	const characters = [...value];3434 	if (characters.length === 0 || characters.length > 64) return null;35-	// C0, DEL and C1 are not printable. Letters outside ASCII are: a machine can be named in any36-	// alphabet, and Koh sends the name as it stands.37-	if (characters.some((char) => char < " " || (char >= "\u007f" && char <= "\u009f"))) return null;35+	// `\p{C}` covers C0, DEL, C1 and the format codes, a bidi override among them that would reorder36+	// the machines list. Letters outside ASCII stay: a machine is named in its owner's language.37+	if (/[\p{C}\p{Zl}\p{Zp}]/u.test(value)) return null;3838 	return value;3939 }4040 

view file

tests/connect.test.ts+9 −1
@@ -104,6 +104,14 @@104104 			);105105 			expect(found.headers.get("cache-control")).toBe(absent.headers.get("cache-control"));106106 		}107+		// Even an oversized body is answered here the way the unknown path answers it.108+		const big = { code: "x".repeat(70_000) };109+		const oversized = await browser.post("/api/connect", big);110+		const unrouted = await browser.post("/nothing/here", big);111+		expect(`${oversized.status} ${await oversized.text()}`).toBe(112+			`${unrouted.status} ${await unrouted.text()}`,113+		);114+		expect(oversized.headers.get("cache-control")).toBe(unrouted.headers.get("cache-control"));107115 	});108116 109117 	test("a signed-out request redirects before the machine name is judged", async () => {@@ -144,7 +152,7 @@144152 145153 	test("connect validates and escapes machine names, and never caches or exposes secrets", async () => {146154 		const { browser } = await connected();147-		for (const machine of ["", "x".repeat(65), "bad\nname", "bad\u0085name"])155+		for (const machine of ["", "x".repeat(65), "bad\nname", "bad\u0085name", "bad\u202ename"])148156 			expect((await browser.get(`/connect?machine=${encodeURIComponent(machine)}`)).status).toBe(149157 				400,150158 			);

view file

tests/fixture.ts+3 −3
@@ -85,7 +85,7 @@8585 	);8686 	await writeFile(join(repoDir, "vendor/big.c"), "int x;\n".repeat(100));8787 	koh("save", "add docs, a secret and a deep helper");88-	const first = JSON.parse(koh("log", "--json")).head_id as string;88+	const first = JSON.parse(koh("view", "--json")).head_id as string;8989 	koh("flag", first, "--reason", "holds a secret");9090 	// Saves a second apart, so the timeline has time to spread over.9191 	Bun.sleepSync(1100);@@ -99,7 +99,7 @@9999 	koh("save", "say hello");100100 	koh("offer");101101 102-	const log = JSON.parse(koh("log", "--json")) as { saves: { id: string }[] };102+	const log = JSON.parse(koh("view", "--json")) as { saves: { id: string }[] };103103 	return {104104 		bucketDir,105105 		repo,@@ -156,7 +156,7 @@156156 		if (i === 0) run(repoDir, home, ["remote", "folder", bucketDir]);157157 		await writeFile(join(repoDir, "README.md"), `# ${name}\n\nThe ${name} project.\n`);158158 		run(repoDir, home, ["save", `release ${name}`]);159-		saves[name] = JSON.parse(run(repoDir, home, ["log", "--json"])).head_id;159+		saves[name] = JSON.parse(run(repoDir, home, ["view", "--json"])).head_id;160160 	}161161 	return {162162 		bucketDir,

view file

tests/front.test.ts+2 −2
@@ -35,8 +35,8 @@3535 	repos.koh("alpha", "offer");3636 	await writeFile(join(root, "work/vault/main.zig"), "const x = 1;\n");3737 	repos.koh("vault", "save", "vault message");38-	alphaSaves = JSON.parse(repos.koh("alpha", "log", "--json")).saves;39-	vaultSave = JSON.parse(repos.koh("vault", "log", "--json")).head_id;38+	alphaSaves = JSON.parse(repos.koh("alpha", "view", "--json")).saves;39+	vaultSave = JSON.parse(repos.koh("vault", "view", "--json")).head_id;4040 4141 	dataDir = await mkdtemp(join(tmpdir(), "kepr-front-"));4242 	kepr = openKeprDb(dataDir);

view file

tests/koh-version.txt+1 −1
@@ -1 +1 @@1-3.0.911+3.0.94

view file

tests/pages.test.ts+4 −1
@@ -47,7 +47,10 @@4747 		Bun.spawnSync(["koh3", "save", message], { cwd: tail.repoDir, env: tailEnv });4848 	}4949 	tailSaves = JSON.parse(50-		Bun.spawnSync(["koh3", "log", "--json"], { cwd: tail.repoDir, env: tailEnv }).stdout.toString(),50+		Bun.spawnSync(["koh3", "view", "--json"], {51+			cwd: tail.repoDir,52+			env: tailEnv,53+		}).stdout.toString(),5154 	).saves.map((save: { id: string }) => save.id);5255 	const dataDir = await mkdtemp(join(tmpdir(), "kepr-pages-"));5356 	await mkdir(join(dataDir, "assets"));

view file

tests/releases.test.ts+1 −1
@@ -250,7 +250,7 @@250250 		});251251 		// cli's head is a checkpoint made after its releases (koh3 offers it on its own).252252 		repos.koh("cli", "checkpoint", "after the releases");253-		checkpoint = JSON.parse(repos.koh("cli", "log", "--json")).head_id;253+		checkpoint = JSON.parse(repos.koh("cli", "view", "--json")).head_id;254254 		app = await releaseApp(bucketDir, (db) => {255255 			db.run("UPDATE repos SET public = 1 WHERE name = 'cli'");256256 		});

view file

tests/sync.test.ts+1 −1
@@ -166,7 +166,7 @@166166 	await writeFile(join(fx.repoDir, "after.txt"), "after\n");167167 	koh("save", "save again after the strip");168168 	koh("offer");169-	const after = JSON.parse(koh("log", "--json").stdout.toString()).head_id as string;169+	const after = JSON.parse(koh("view", "--json").stdout.toString()).head_id as string;170170 171171 	const view = await mkdtemp(join(tmpdir(), "kepr-view-"));172172 	const viewRepo = join(view, fx.repo);

view file

tests/tarball.test.ts+1 −1
@@ -48,7 +48,7 @@4848 	const env = { PATH: process.env.PATH, HOME: home, NO_COLOR: "1" };4949 	const save = Bun.spawnSync(["koh3", "save", "add build files"], { cwd: fx.repoDir, env });5050 	expect(save.exitCode).toBe(0);51-	const log = Bun.spawnSync(["koh3", "log", "--json"], { cwd: fx.repoDir, env });51+	const log = Bun.spawnSync(["koh3", "view", "--json"], { cwd: fx.repoDir, env });5252 	head = JSON.parse(log.stdout.toString()).head_id;5353 	dataDir = await mkdtemp(join(tmpdir(), "kepr-tarball-"));5454 	const kepr = openKeprDb(dataDir);

view file