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

The one command that opens an app's terminal reaches the reader as "[email protected]" #267

Merged opened this

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

Merged

scout/forge:scout/254-the-one-command-that-opens-an-app-s-term → main 1 commit fork

  • Merged. scout/254-the-one-command-that-opens-an-app-s-term was merged into main as 4aa2daf.

Review

Sign in to review this change.
3 files changed +101 −1
src/web/ui.ts +9 −1
@@ -417,6 +417,14 @@ export function previewDataNote(
417417 * — or one line saying the terminal is off, when there is no command to
418418 * show. Every page that names a way in draws this one, never its own words
419419 * for "off".
420 *
421 * The command has a `login@host` in it, which an email-scanning edge on the
422 * forge's own domain will otherwise rewrite into a `mailto:` link — turning
423 * the one line a reader must copy character for character into a link that
424 * hides the login. `<!--email_off-->…<!--/email_off-->` is the marker such
425 * scanners honour; it costs nothing where none runs, and the text between
426 * the markers is exactly the command, unchanged and still one selectable,
427 * copyable line.
420428 */
421429 export function terminalLine(
422430 command: string | undefined,
@@ -425,7 +433,7 @@ export function terminalLine(
425433 if (command === undefined)
426434 return html`<p class="muted small">The terminal is off.</p>`;
427435 return html`<p class="terminal-line">
428 <code class="mono">${command}</code>
436 <code class="mono"><!--email_off-->${command}<!--/email_off--></code>
429437 ${when(
430438 fingerprint,
431439 (fp) => html`<span class="muted small">host key ${fp}</span>`,
test/apps.test.ts +86
@@ -1661,3 +1661,89 @@ describe("a new app's own words promise only the check the forge runs (issue #19
16611661 }
16621662 });
16631663 });
1664
1665 /**
1666 * Issue #254: the terminal command has a `login@host` in it, which an
1667 * email-scanning edge on the forge's own domain rewrites into a `mailto:`
1668 * link — the one line a reader must copy character for character, turned
1669 * into something that neither shows the login nor copies as text.
1670 * `terminalLine` (`src/web/ui.ts`) now wraps it in the `<!--email_off-->`
1671 * marker such scanners honour, which leaves the command itself — the thing
1672 * a paste must carry — untouched.
1673 */
1674 describe("the App page's terminal line survives an email-scanning edge (issue #254)", () => {
1675 let dataDir: string;
1676 let app: App;
1677 let servers: Servers;
1678 let runtime: AppsRuntime;
1679 let base: string;
1680 let cookie: string;
1681 const DOMAIN = "apps.test";
1682
1683 beforeAll(async () => {
1684 dataDir = await mkdtemp(join(tmpdir(), "forge-app-terminal-edge-"));
1685 app = createApp({ dataDir, port: 0 });
1686 app.config.sshPort = 2222;
1687 app.config.sshHostKey = "SHA256:abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG";
1688 servers = await serve(app);
1689 base = servers.url;
1690 runtime = new AppsRuntime(app, {
1691 domain: DOMAIN,
1692 scheme: "http",
1693 publicPort: servers.port,
1694 forgeUrl: servers.url,
1695 concurrency: 1,
1696 });
1697 await runtime.start();
1698 const created = await createUser(app.db, {
1699 username: "priya",
1700 email: "[email protected]",
1701 password: "a-good-passphrase",
1702 adminIfFirst: true,
1703 });
1704 if (!created.ok) throw new Error(created.error);
1705 const made = await newApp(app, created.value, {
1706 name: "beacon",
1707 description: "a light that stays on",
1708 visibility: "public",
1709 });
1710 if (!made.ok) throw new Error(String(made.error));
1711 cookie = (
1712 (
1713 await fetch(`${base}/signin`, {
1714 method: "POST",
1715 body: new URLSearchParams({
1716 username: "priya",
1717 password: "a-good-passphrase",
1718 }),
1719 redirect: "manual",
1720 })
1721 ).headers.get("set-cookie") ?? ""
1722 ).split(";")[0]!;
1723 });
1724
1725 afterAll(async () => {
1726 await runtime.stop();
1727 servers.stop();
1728 app.close();
1729 await rm(dataDir, { recursive: true, force: true });
1730 });
1731
1732 test("the command sits between email_off markers, and the visible text is still the exact line", async () => {
1733 const res = await fetch(`${base}/priya/beacon/app`, {
1734 headers: { cookie },
1735 });
1736 expect(res.status).toBe(200);
1737 const html = await res.text();
1738 const command = `ssh -p 2222 beacon@localhost`;
1739 // The whole command is wrapped, as a single unbroken run of text — an
1740 // email-scanning edge honours the markers and leaves what is between
1741 // them alone, so a reader still copies exactly this string.
1742 expect(html).toContain(`<!--email_off-->${command}<!--/email_off-->`);
1743 // Strip only the markers: what is left is exactly the command inside
1744 // its tag, unchanged — nothing dropped or split, no stray space added.
1745 const withoutMarkers = html.replace(/<!--\/?email_off-->/g, "");
1746 expect(withoutMarkers).toContain(`<code class="mono">${command}</code>`);
1747 expect(html).toContain("host key SHA256:abcdefghijklmnopqrstuvwxyz");
1748 });
1749 });
test/studio.test.ts +6
@@ -2678,6 +2678,12 @@ describe("the studio app page's terminal", () => {
26782678 expect(mine).toContain(
26792679 "host key SHA256:abcdefghijklmnopqrstuvwxyz",
26802680 );
2681 // Wrapped in the marker an email-scanning edge honours, so the
2682 // login@host in the command is never rewritten into a mailto: link —
2683 // and the command itself sits between the markers unchanged.
2684 expect(mine).toContain(
2685 "<!--email_off-->ssh -p 2222 [email protected]<!--/email_off-->",
2686 );
26812687 const theirs = await appPage(reader);
26822688 expect(theirs).not.toContain("ssh -p 2222 [email protected]");
26832689 expect(theirs).not.toContain("The terminal is off");

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs