Blame · Line-by-line history
fork-privacy.test.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| a1beefb | 1 | /** |
| 2 | * Forking must not publish a private repository. | |
| 3 | * | |
| 4 | * POST /:owner/:repo/fork was gated only by `requireAuth` plus `repoExists()`. | |
| 5 | * repoExists is a bare filesystem probe (Bun.file(<repoPath>/HEAD).exists()) | |
| 6 | * that never reads the repositories row, so it never sees isPrivate — and the | |
| 7 | * insert hardcoded `isPrivate: false`. Any logged-in user could fork a private | |
| 8 | * repo they had no read access to, and the clone landed as a PUBLIC repo. | |
| 9 | * One POST turned someone else's private source public. | |
| 10 | */ | |
| 11 | ||
| 12 | import { describe, expect, it } from "bun:test"; | |
| 13 | import { readFileSync } from "fs"; | |
| 14 | ||
| 15 | const SRC = readFileSync("src/routes/fork.tsx", "utf8"); | |
| 16 | const handler = SRC.slice(SRC.indexOf('fork.post("/:owner/:repo/fork"')); | |
| 17 | ||
| 18 | describe("fork access gate", () => { | |
| 19 | it("checks read access via resolveRepoAccess before cloning", () => { | |
| 20 | expect(handler).toContain("resolveRepoAccess"); | |
| 21 | expect(handler).toContain('access === "none"'); | |
| 22 | }); | |
| 23 | ||
| 24 | it("gates BEFORE the git clone, not after", () => { | |
| 25 | const gate = handler.indexOf('access === "none"'); | |
| 26 | const clone = handler.indexOf('"git", "clone"'); | |
| 27 | expect(gate).toBeGreaterThan(-1); | |
| 28 | expect(clone).toBeGreaterThan(-1); | |
| 29 | // Cloning first would copy the private objects to disk even if the DB | |
| 30 | // insert were later refused. | |
| 31 | expect(gate).toBeLessThan(clone); | |
| 32 | }); | |
| 33 | ||
| 34 | it("denies indistinguishably from a missing repo", () => { | |
| 35 | // Must not become an oracle for which private repos exist: the denial | |
| 36 | // path is the same redirect a non-existent repo takes. | |
| 37 | const denial = handler.slice( | |
| 38 | handler.indexOf('access === "none"'), | |
| 39 | handler.indexOf('"git", "clone"') | |
| 40 | ); | |
| 41 | expect(denial).toContain("c.redirect(`/${ownerName}/${repoName}`)"); | |
| 42 | expect(denial).not.toMatch(/403|Forbidden|not allowed/i); | |
| 43 | }); | |
| 44 | }); | |
| 45 | ||
| 46 | describe("fork visibility", () => { | |
| 47 | it("inherits the source repo's visibility instead of hardcoding public", () => { | |
| 48 | expect(handler).toContain("isPrivate: sourceRepo.isPrivate"); | |
| 49 | // Match the code shape, not the prose: the phrase also appears in the | |
| 50 | // comment explaining what the old behaviour was. | |
| 51 | expect(handler).not.toMatch(/^\s*isPrivate: false,/m); | |
| 52 | }); | |
| 53 | }); |