From 31117fe660864b126cd04e203032db9fa6b0fbef Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 8 Aug 2026 08:52:22 -0700 Subject: [PATCH 1/2] Add product site and GitHub links to OAuth success footer Show corbits.dev and github.com/corbitsdev next to the product name on the authorization callback page so the success screen points operators at the public brand surfaces. Fixes CL-5604 --- src/auth/callback-page.test.ts | 15 +++++++++++++-- src/auth/callback-page.ts | 22 ++++++++++++++++++++-- src/branding.ts | 4 ++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/auth/callback-page.test.ts b/src/auth/callback-page.test.ts index 695ab46f4..549a7d063 100644 --- a/src/auth/callback-page.test.ts +++ b/src/auth/callback-page.test.ts @@ -42,8 +42,19 @@ describe("callbackPageHtml", () => { ); }); - test("the page reaches for nothing off the machine", () => { + test("success footer links to corbits.dev and the GitHub org", () => { const html = callbackPageHtml({ subject: "linear" }); - expect(html).not.toMatch(/https?:\/\/(?!www\.w3\.org)/); + expect(html).toContain('href="https://corbits.dev"'); + expect(html).toContain('href="https://github.com/corbitsdev"'); + expect(html).toContain(">corbits.dev<"); + expect(html).toContain(">github.com/corbitsdev<"); + }); + + test("the page loads no off-machine assets", () => { + const html = callbackPageHtml({ subject: "linear" }); + // Product links in the footer are intentional; nothing else may fetch. + expect(html).not.toMatch(/<(?:link|script)\b[^>]+\bsrc=/i); + expect(html).not.toMatch(/@import\b/); + expect(html).not.toMatch(/url\(\s*["']?https?:/i); }); }); diff --git a/src/auth/callback-page.ts b/src/auth/callback-page.ts index 652f2953e..cc7a3a57e 100644 --- a/src/auth/callback-page.ts +++ b/src/auth/callback-page.ts @@ -14,7 +14,11 @@ * authorization callback. */ -import { PRODUCT_NAME } from "../branding.js"; +import { + PRODUCT_GITHUB_URL, + PRODUCT_NAME, + PRODUCT_SITE_URL, +} from "../branding.js"; /** Corbits wordmark, background layers stripped so it inherits `currentColor`. */ const WORDMARK = ` `; @@ -103,7 +107,21 @@ footer { font-size: 0.75rem; letter-spacing: 0.02em; color: var(--ink-faint); + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 0.5rem 0.75rem; +} +footer a { + color: var(--ink-dim); + text-decoration: none; + border-bottom: 1px solid transparent; +} +footer a:hover { + color: var(--ink); + border-bottom-color: var(--rule); } +footer .sep { color: var(--rule); } @keyframes rise { from { opacity: 0; transform: translateY(0.5rem); } to { opacity: 1; transform: none; } @@ -274,7 +292,7 @@ export function callbackPageHtml(page: CallbackPage = {}): string { `

${heading}

`, `

${body}

`, "
", - ``, + ``, "", "", ``, diff --git a/src/branding.ts b/src/branding.ts index 5be552a60..2079e894e 100644 --- a/src/branding.ts +++ b/src/branding.ts @@ -11,6 +11,10 @@ export const PRODUCT_NAME = "Corbits Code"; // Short attribution form used in compact UI (status lines, footers). export const PRODUCT_SHORT_NAME = "Corbits"; +// Canonical public URLs shown on the OAuth callback page and other brand surfaces. +export const PRODUCT_SITE_URL = "https://corbits.dev"; +export const PRODUCT_GITHUB_URL = "https://github.com/corbitsdev"; + export const COMMAND_NAME = "corbits"; export const SETTINGS_DIR_NAME = ".corbits"; From 4d7f52b065fb212ee04cee48688fd652daae2e5d Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 8 Aug 2026 12:01:41 -0700 Subject: [PATCH 2/2] Derive callback footer link labels from branding constants The URLs lived in branding.ts while their display text stayed inline in the page template, so a rename would have had to touch both. Add matching label constants and restore the no-external-assets guard to its full strength by stripping the two intentional footer links before asserting no other off-machine URL or network call appears. --- src/auth/callback-page.test.ts | 58 +++++++++++++++++++++++++++------- src/auth/callback-page.ts | 14 +++++++- src/branding.ts | 6 +++- 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/auth/callback-page.test.ts b/src/auth/callback-page.test.ts index 549a7d063..15f68b1f3 100644 --- a/src/auth/callback-page.test.ts +++ b/src/auth/callback-page.test.ts @@ -1,5 +1,11 @@ import { describe, expect, test } from "bun:test"; +import { + PRODUCT_GITHUB_LABEL, + PRODUCT_GITHUB_URL, + PRODUCT_SITE_LABEL, + PRODUCT_SITE_URL, +} from "../branding.js"; import { callbackPageHtml, humanizeIdentifier } from "./callback-page.js"; describe("humanizeIdentifier", () => { @@ -42,19 +48,49 @@ describe("callbackPageHtml", () => { ); }); - test("success footer links to corbits.dev and the GitHub org", () => { + test("the footer links to the product site and the GitHub org", () => { const html = callbackPageHtml({ subject: "linear" }); - expect(html).toContain('href="https://corbits.dev"'); - expect(html).toContain('href="https://github.com/corbitsdev"'); - expect(html).toContain(">corbits.dev<"); - expect(html).toContain(">github.com/corbitsdev<"); + const link = (url: string, label: string) => + `${label}`; + expect(html).toContain(link(PRODUCT_SITE_URL, PRODUCT_SITE_LABEL)); + expect(html).toContain(link(PRODUCT_GITHUB_URL, PRODUCT_GITHUB_LABEL)); }); - test("the page loads no off-machine assets", () => { - const html = callbackPageHtml({ subject: "linear" }); - // Product links in the footer are intentional; nothing else may fetch. - expect(html).not.toMatch(/<(?:link|script)\b[^>]+\bsrc=/i); - expect(html).not.toMatch(/@import\b/); - expect(html).not.toMatch(/url\(\s*["']?https?:/i); + test("each footer label names the destination its URL actually points at", () => { + expect(PRODUCT_SITE_URL).toContain(PRODUCT_SITE_LABEL); + expect(PRODUCT_GITHUB_URL).toContain(PRODUCT_GITHUB_LABEL); }); + + // An allowlist rather than a shape match: an unexpected origin fails loudly + // instead of passing because it happened to be wrapped in an anchor tag. + const allowedOrigins = new Set([ + PRODUCT_SITE_URL, + PRODUCT_GITHUB_URL, + // The SVG namespace the wordmark declares; a URI, never fetched. + "http://www.w3.org/2000/svg", + ]); + + const offMachineOrigins = (html: string): readonly string[] => { + // Scheme-qualified and protocol-relative alike, since either would load. + const found = + html.match( + /(?:[a-z][a-z0-9+.-]*:)?\/\/[a-z0-9-]+(?:\.[a-z0-9-]+)+[^"'`)\s<>]*/gi, + ) ?? []; + return found.filter( + (ref) => ![...allowedOrigins].some((origin) => ref.startsWith(origin)), + ); + }; + + for (const [outcome, page] of [ + ["success", { subject: "linear" }], + ["failure", { subject: "linear", error: "access_denied" }], + ] as const) { + test(`the ${outcome} page names no off-machine origin beyond the footer links`, () => { + const html = callbackPageHtml(page); + expect(offMachineOrigins(html)).toEqual([]); + expect(html).not.toMatch( + /\b(?:fetch|XMLHttpRequest|WebSocket|EventSource|sendBeacon|importScripts)\s*\(/, + ); + }); + } }); diff --git a/src/auth/callback-page.ts b/src/auth/callback-page.ts index cc7a3a57e..afb6f5c52 100644 --- a/src/auth/callback-page.ts +++ b/src/auth/callback-page.ts @@ -15,8 +15,10 @@ */ import { + PRODUCT_GITHUB_LABEL, PRODUCT_GITHUB_URL, PRODUCT_NAME, + PRODUCT_SITE_LABEL, PRODUCT_SITE_URL, } from "../branding.js"; @@ -245,6 +247,16 @@ export function humanizeIdentifier(raw: string): string { return words.charAt(0).toUpperCase() + words.slice(1); } +/** + * A footer link, preceded by its separator. + * + * Opens in a new tab so the operator keeps the tab telling them the + * authorization finished and this window is safe to close. + */ +function footerLink(url: string, label: string): string { + return `${label}`; +} + export type CallbackPage = { /** What was being authorized: an MCP server or provider name. */ readonly subject?: string; @@ -292,7 +304,7 @@ export function callbackPageHtml(page: CallbackPage = {}): string { `

${heading}

`, `

${body}

`, "
", - ``, + `
${PRODUCT_NAME}${footerLink(PRODUCT_SITE_URL, PRODUCT_SITE_LABEL)}${footerLink(PRODUCT_GITHUB_URL, PRODUCT_GITHUB_LABEL)}
`, "", "", ``, diff --git a/src/branding.ts b/src/branding.ts index 2079e894e..66510e8f3 100644 --- a/src/branding.ts +++ b/src/branding.ts @@ -11,9 +11,13 @@ export const PRODUCT_NAME = "Corbits Code"; // Short attribution form used in compact UI (status lines, footers). export const PRODUCT_SHORT_NAME = "Corbits"; -// Canonical public URLs shown on the OAuth callback page and other brand surfaces. +// Canonical public URLs, each paired with the text shown in its place. The label +// is spelled out rather than stripped from the URL so it can diverge from the +// bare host later without a rendering helper having to special-case it. export const PRODUCT_SITE_URL = "https://corbits.dev"; +export const PRODUCT_SITE_LABEL = "corbits.dev"; export const PRODUCT_GITHUB_URL = "https://github.com/corbitsdev"; +export const PRODUCT_GITHUB_LABEL = "github.com/corbitsdev"; export const COMMAND_NAME = "corbits";