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
↓ kepr-731c1ce0.tar.gzatlas at this save
files changed11 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 −1package.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": {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.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 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 );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,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);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"));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 });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);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);