anvilsign in

collin/browser-terminal-extension

1// Run with: node --test "extension/lib/*.test.js"
2//
3// This is the boundary between page-controlled text and a live shell. Every
4// case below is something a hostile (or merely careless) page can put in an
5// id, a class, or an aria-label.
6
7const test = require("node:test");
8const assert = require("node:assert");
9const { execFileSync } = require("node:child_process");
10const S = require("./sanitize.js");
11
12test("newlines are removed — a newline in a terminal is a pressed Enter", () => {
13 for (const nl of ["\n", "\r", "\r\n", "\u2028", "\u2029"]) {
14 const { text } = S.forTerminal(`a${nl}whoami`);
15 assert.strictEqual(text, "'awhoami'", `newline survived: ${JSON.stringify(text)}`);
16 }
17});
18
19test("the classic injection payload cannot execute", () => {
20 const { text } = S.forTerminal("x'; rm -rf ~; echo '");
21 // Single-quoted, with the embedded quotes neutralised. Handing this to sh
22 // yields one literal argument.
23 assert.strictEqual(text, "'x'\\''; rm -rf ~; echo '\\'''");
24});
25
26test("shell metacharacters are inert inside single quotes", () => {
27 for (const payload of [
28 "$(whoami)",
29 "`id`",
30 "${HOME}",
31 "a && b",
32 "a || b",
33 "a; b",
34 "a | b",
35 "a > /etc/passwd",
36 "~/secrets",
37 "*",
38 "$IFS",
39 ]) {
40 const { text } = S.forTerminal(payload);
41 assert.strictEqual(text, `'${payload}'`, `mangled: ${payload}`);
42 }
43});
44
45test("escape sequences are stripped, not passed to the terminal", () => {
46 const { text, removedControl } = S.forTerminal("\u001b]0;pwned\u0007ok");
47 assert.ok(!text.includes("\u001b"), "ESC survived");
48 assert.ok(!text.includes("\u0007"), "BEL survived");
49 assert.strictEqual(text, "']0;pwnedok'");
50 assert.ok(removedControl, "should report that something was removed");
51});
52
53test("tab is stripped — it triggers shell completion", () => {
54 assert.strictEqual(S.forTerminal("a\tb").text, "'ab'");
55});
56
57test("NUL and other C0 controls are stripped", () => {
58 assert.strictEqual(S.forTerminal("a\u0000b\u0001c").text, "'abc'");
59});
60
61test("removedControl is false for ordinary input", () => {
62 const { text, removedControl, truncated } = S.forTerminal("#main > div.card");
63 assert.strictEqual(text, "'#main > div.card'");
64 assert.strictEqual(removedControl, false);
65 assert.strictEqual(truncated, false);
66});
67
68test("absurdly long input is capped and reported", () => {
69 const { text, truncated } = S.forTerminal("a".repeat(S.MAX_LEN * 3));
70 assert.ok(truncated);
71 assert.ok(text.length <= S.MAX_LEN + 2);
72});
73
74// The real proof: hand the quoted string to an actual shell and check it comes
75// back byte-for-byte as a single argument.
76test("quoting survives a round trip through sh", () => {
77 for (const payload of [
78 "x'; rm -rf ~; echo '",
79 "$(id)",
80 "it's a `test`",
81 'double "quotes" too',
82 "back\\slash",
83 "#main > div.card:nth-of-type(2)",
84 "//*[@id='thing']",
85 ]) {
86 const { text } = S.forTerminal(payload);
87 const out = execFileSync("/bin/sh", ["-c", `printf %s ${text}`], {
88 encoding: "utf8",
89 });
90 assert.strictEqual(out, payload, `sh did not treat it as one literal: ${payload}`);
91 }
92});
93
94// A newline must not become a second command even when sh evaluates the line.
95test("an injected newline cannot start a second command in sh", () => {
96 const { text } = S.forTerminal("harmless\nid");
97 const out = execFileSync("/bin/sh", ["-c", `printf %s ${text}`], {
98 encoding: "utf8",
99 });
100 assert.strictEqual(out, "harmlessid");
101 assert.ok(!/uid=/.test(out), "a second command ran");
102});
103
104test("clipboard output strips controls but does not shell-quote", () => {
105 assert.strictEqual(S.forClipboard("#main > .card"), "#main > .card");
106 assert.strictEqual(S.forClipboard("a\nwhoami"), "awhoami");
107 assert.strictEqual(S.forClipboard("it's"), "it's");
108});
109
110test("null and undefined do not throw", () => {
111 assert.strictEqual(S.forTerminal(null).text, "''");
112 assert.strictEqual(S.forTerminal(undefined).text, "''");
113 assert.strictEqual(S.forClipboard(null), "");
114});