Public: anyone can read this; the crew can change it.

A test pins where a tell says it was said: a `from` that is not this forge's page or app address is refused #7125

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/7092-a-test-pins-where-a-tell-says-it-was-sai → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/7092-a-test-pins-where-a-tell-says-it-was-sai was merged into main as 1d8511e.

Review

Sign in to review this change.
1 file changed +86 −0
test/tell-from.test.ts added +86
@@ -0,0 +1,86 @@
1 /** `from` on a tell is a place on this forge, or an app's own address from that app (#7092). */
2 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
3 import { mkdtemp, rm } from "node:fs/promises";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createApp, type App } from "../src/app.ts";
7 import { createRepo } from "../src/actions/repos.ts";
8 import { tell } from "../src/actions/tell.ts";
9 import { listIssues } from "../src/core/issues.ts";
10 import type { Repo } from "../src/core/repos.ts";
11 import { createUser, type User } from "../src/core/users.ts";
12
13 let dir: string;
14 let app: App;
15 let alice: User;
16 let repo: Repo;
17
18 beforeAll(async () => {
19 dir = await mkdtemp(join(tmpdir(), "forge-tellfrom2-"));
20 app = createApp({ dataDir: dir, port: 0 });
21 const u = await createUser(app.db, {
22 origin: "seed",
23 trusted: true,
24 username: "alice",
25 email: "[email protected]",
26 password: "a-good-passphrase",
27 adminIfFirst: true,
28 });
29 if (!u.ok) throw new Error(u.error);
30 alice = u.value;
31 const r = await createRepo(app, alice, { name: "notes", visibility: "public" });
32 if (!r.ok) throw new Error(String(r.error));
33 repo = r.value;
34 });
35
36 afterAll(async () => {
37 await rm(dir, { recursive: true, force: true });
38 });
39
40 const stored = () => listIssues(app.db, repo.id, {}).length;
41
42 describe("tell from", () => {
43 test("a page on this forge is kept on the issue", async () => {
44 const t = await tell(app, alice, repo, "Page tell", 1, "/alice/notes/pulls/3");
45 expect(t.ok).toBe(true);
46 if (!t.ok) return;
47 expect(t.value.issue.body).toContain("Told from [/alice/notes/pulls/3](/alice/notes/pulls/3)");
48 });
49
50 test("another host, javascript:, or a control character is bad_from and stores nothing", async () => {
51 const before = stored();
52 for (const from of [
53 "https://evil.example/alice/notes",
54 "//evil.example/x",

The call runs, then continue skips the assertion for https:// inputs with fromApp=true. If one were wrongly accepted it would be stored and never checked. Assert bad_from here too, since these have paths.

55 "javascript:alert(1)",
56 "/alice/notes\nInjected",
57 "/alice/notes\x00",
58 "/alice/notes\x7f",
59 ]) {
60 for (const fromApp of [false, true]) {
61 const t = await tell(app, alice, repo, "Nope", 1, from, fromApp);
62 if (fromApp && from.startsWith("https://")) continue; // an app address, below
63 expect(t.ok).toBe(false);
64 if (!t.ok) expect(t.error).toBe("bad_from");
65 }
66 }
67 expect(stored()).toBe(before);
68 });
69
70 test("an app's own address is accepted only when the tell comes from that app", async () => {
71 const address = "https://notes--alice.apps.example";
72 const before = stored();
73 const web = await tell(app, alice, repo, "From the web", 1, address);
74 expect(web.ok).toBe(false);
75 if (!web.ok) expect(web.error).toBe("bad_from");
76 expect(stored()).toBe(before);
77 const fromApp = await tell(app, alice, repo, "From the app", 1, address, true);
78 expect(fromApp.ok).toBe(true);
79 if (fromApp.ok) expect(fromApp.value.issue.body).toContain(`Told from [${address}]`);
80 // Even from an app, only scheme and host: no path, no javascript:.
81 for (const from of [`${address}/x`, "javascript:alert(1)", "https://a b.example"]) {
82 const t = await tell(app, alice, repo, "Bad app", 1, from, true);
83 expect(t.ok).toBe(false);
84 }
85 });
86 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs