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
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/254-the-one-command-that-opens-an-app-s-term → main 1 commit fork
3 files
changed +101
−1
src/web/ui.ts +9 −1
| @@ -417,6 +417,14 @@ export function previewDataNote( | ||
| 417 | 417 | * — or one line saying the terminal is off, when there is no command to |
| 418 | 418 | * show. Every page that names a way in draws this one, never its own words |
| 419 | 419 | * 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. | |
| 420 | 428 | */ |
| 421 | 429 | export function terminalLine( |
| 422 | 430 | command: string | undefined, |
| @@ -425,7 +433,7 @@ export function terminalLine( | ||
| 425 | 433 | if (command === undefined) |
| 426 | 434 | return html`<p class="muted small">The terminal is off.</p>`; |
| 427 | 435 | return html`<p class="terminal-line"> |
| 428 | <code class="mono">${command}</code> | |
| 436 | <code class="mono"><!--email_off-->${command}<!--/email_off--></code> | |
| 429 | 437 | ${when( |
| 430 | 438 | fingerprint, |
| 431 | 439 | (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 | ||
| 1661 | 1661 | } |
| 1662 | 1662 | }); |
| 1663 | 1663 | }); |
| 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", () => { | ||
| 2678 | 2678 | expect(mine).toContain( |
| 2679 | 2679 | "host key SHA256:abcdefghijklmnopqrstuvwxyz", |
| 2680 | 2680 | ); |
| 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 | ); | |
| 2681 | 2687 | const theirs = await appPage(reader); |
| 2682 | 2688 | expect(theirs).not.toContain("ssh -p 2222 [email protected]"); |
| 2683 | 2689 | expect(theirs).not.toContain("The terminal is off"); |
new above
offline