Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dor-tools-builtin/viewer/image/pixels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ const selected = (r: Raster, c: Rect, mask: Mask | undefined, x: number, y: numb
x >= c.x && y >= c.y && x < c.x + c.width && y < c.y + c.height && (!mask || mask[y * r.width + x] !== 0);

/** The most common color among unselected pixels 8-adjacent to the selection
* (`rect`, narrowed by `mask`); ties go to the first in row order. Null when the
* (`rect`, narrowed by `mask`); a tie goes to the color that reached the top count first. Null when the
* selection has no such ring, as when it covers the image. */
export function dominantBorderColor(r: Raster, rect: Rect, mask?: Mask): Rgba | null {
const c = clipRect(r, rect);
Expand Down
2 changes: 0 additions & 2 deletions lib/src/host/agent-browser-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,6 @@ function cliError(result: CliResult): string {
return result.stderr.trim() || result.stdout.trim() || `agent-browser exited ${result.exitCode}`;
}

// A capture can queue behind a page-loading `open` for the CLI's whole 25s
// action timeout; past the host's capture bound it is wedged, and killed.
const STREAM_PORT_READ_ATTEMPTS = 4;
const STREAM_PORT_READ_DELAY_MS = 150;
const PORT_PROBE_TIMEOUT_MS = 500;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/host/browser-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ const VIEWPORT_MEASURE_SCRIPT = '({width:innerWidth,height:innerHeight,dpr:devic
* and would walk straight past a caller's rejection into the page.
* `hasOwnProperty.call` keeps the table's own three names the only ones that
* select a script. Same guard, same reason as `own()` in
* `RemoteControlSection.tsx`. */
* `lib/src/components/remote-control-shared.ts`. */
function editScript(op: unknown): string | undefined {
return typeof op === 'string' && Object.prototype.hasOwnProperty.call(EDIT_SCRIPTS, op)
? EDIT_SCRIPTS[op as BrowserEditOp]
Expand Down
17 changes: 8 additions & 9 deletions lib/src/host/iframe-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@
* this file is the `http`/`net` plumbing. The logger is injected so neither host
* has to depend on the other's logging.
*
* Sibling to the agent-browser stream relay: same loopback-only bind and
* per-surface isolation, but this one speaks HTTP (parses and rewrites
* responses) and passes WebSocket upgrades through (dev-server HMR,
* openvscode-server).
* It binds loopback only, speaks HTTP (parses and rewrites responses), and
* passes WebSocket upgrades through (dev-server HMR, openvscode-server).
*
* Per-surface isolation, two deliberate notes vs the spec's sketch:
* Per-surface isolation:
* - Each grant gets its OWN ephemeral loopback server, so the grant's *origin*
* is the grant. That makes root-relative sub-resources (`/assets/x.js`) and
* absolute paths resolve and proxy transparently with zero body rewriting.
Expand Down Expand Up @@ -224,13 +222,14 @@ export async function createIframeProxyUrl(
}
// v1 proxies http:// upstreams only — loopback dev servers are overwhelmingly
// plain http, and rewriting authenticated https pages is the agent-browser's
// job (spec → Target policy).
// job (docs/specs/dor-browser.md → "Iframe Renderer").
if (upstream.protocol !== 'http:') {
return { ok: false, reason: 'scheme', detail: IFRAME_HTTP_ONLY };
}
// SSRF guard: the proxy fetches a user-supplied URL, so refuse the link-local
// / cloud-metadata ranges (169.254.169.254 and friends). Other private ranges
// are trusted — the boundary is the user's own `dor iframe` (spec → Security).
// are trusted — the boundary is the user's own `dor iframe`
// (docs/specs/dor-browser.md → "Iframe Host Capability And CSP").
if (isBlockedAddress(upstream.hostname)) {
return { ok: false, reason: 'scheme', detail: 'link-local / metadata addresses are refused' };
}
Expand Down Expand Up @@ -580,8 +579,8 @@ function rewriteOrigin(value: string, from: string, to: string): string {

// --- WebSocket upgrade passthrough (dev-server HMR, openvscode-server) -------

// Mirrors the stream relay: once the upgrade head is rewritten (Host/Origin
// pointed at the upstream) the proxy is a dumb byte pipe.
// Once the upgrade head is rewritten (Host/Origin pointed at the upstream) the
// proxy is a dumb byte pipe.
function handleUpgrade(grant: Grant, req: http.IncomingMessage, socket: net.Socket, head: Buffer): void {
// The upgrade path is where the Origin rewrite costs the most, so it gets the
// same two tests as handleRequest. WebSockets are not subject to CORS, so a
Expand Down
6 changes: 3 additions & 3 deletions relay/test/static.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ test('the policy is same-origin everywhere, unframeable, and loosens only what p
assert.deepEqual(policy, {
'default-src': ["'self'"],
// No *script* exception: the build emits none inline and loads nothing
// off-origin, which the built-output case below pins. `wasm-unsafe-eval`
// off-origin, which `lib/scripts/assert-pocket-worker.mjs` pins at build time. `wasm-unsafe-eval`
// is the addon's SIXEL decoder and permits WebAssembly compilation only —
// pinned so a widening to `'unsafe-eval'` cannot pass as the same thing.
'script-src': ["'self'", "'wasm-unsafe-eval'"],
Expand All @@ -180,9 +180,9 @@ test('the policy is same-origin everywhere, unframeable, and loosens only what p
});
});

test('connect-src names this deployment own relay and no other burrow', async () => {
test("connect-src names this deployment's own relay and no other host", async () => {
// `'self'` alone leaves ws/wss to browsers that have disagreed about whether
// it covers them, and a bare `ws:` would admit every burrow on the network.
// it covers them, and a bare `ws:` would admit every host on the network.
for (const [origin, expected] of [
['https://dormouse.tailnet.ts.net', 'wss://dormouse.tailnet.ts.net'],
['http://localhost:3000', 'ws://localhost:3000'],
Expand Down
8 changes: 4 additions & 4 deletions remote-lib-common/test/pairing-invitation.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ const MAX_ORIGIN_LENGTH = PAIRING_QR_URL_MAX_LENGTH - 1 - PAIRING_HASH_PREFIX.le

/** A real origin of exactly `length` characters, with DNS-legal labels. */
function originOfLength(length) {
const burrow = length - 'https://'.length - '.dev'.length;
const first = Math.min(burrow, 63);
const rest = burrow - first - 1;
const host = length - 'https://'.length - '.dev'.length;
const first = Math.min(host, 63);
const rest = host - first - 1;
return `https://${'a'.repeat(first)}${rest >= 0 ? `.${'b'.repeat(rest)}` : ''}.dev`;
}

Expand Down Expand Up @@ -203,7 +203,7 @@ test('plain HTTP is accepted on a loopback host, and nowhere else', async () =>
const parsed = await parsePairingInvitationUrl(url, origin, NOW);
assert.equal(parsed?.burrowId, BURROW_ID, origin);
}
// By burrow, never by suffix: a name that merely ends in `localhost` is an
// By host name, never by suffix: a name that merely ends in `localhost` is an
// ordinary remote origin a self-hoster could be pointed at.
for (const origin of ['http://evil.localhost', 'http://127.0.0.1.evil.example', 'http://pocket.example']) {
const url = `${origin}/${PAIRING_HASH_PREFIX}${FIELDS.join('.')}`;
Expand Down
3 changes: 2 additions & 1 deletion scripts/github-state-check.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ test('the recorded live state passes every clause', () => {
// The reporting step computes this fragment's verdict from its lines against
// the rules the specs pin to this script. Every rule gets a line, every line
// is in the grammar and names a pinned rule, so the computed verdict is the
// script's own — a mismatch between the two PRs' formats goes red here.
// script's own — a mismatch between this script's fragment format and
// `security-audit-report.mjs`'s parser goes red here.
test('the reporting step reads the fragment as the script judged it', () => {
const clean = judgeFragment(repoRoot, FRAGMENT, replay().text);
assert.equal(clean.verdict, 'PASS', [...clean.doubts, ...clean.missing, ...clean.malformed.map((m) => m.line)].join('\n'));
Expand Down
8 changes: 4 additions & 4 deletions website/scripts/docs-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ export class UnsupportedMarkdownError extends Error {
/** Attributes an `<img>` may carry. Everything else is rejected. */
export const IMG_ALLOWED_ATTRS = new Set(['src', 'alt', 'width', 'height', 'title']);

// ---------------------------------------------------------------------------
// Slugger — mirrors github-slugger so /docs anchors match GitHub's own.
// ---------------------------------------------------------------------------

/**
* Whether a URL carries a scheme (`https:`, `mailto:`, `vscode:`).
*
Expand All @@ -50,6 +46,10 @@ export function isProtocolRelative(url) {
return url.startsWith('//');
}

// ---------------------------------------------------------------------------
// Slugger — mirrors github-slugger so /docs anchors match GitHub's own.
// ---------------------------------------------------------------------------

export function slugify(text) {
return text
.trim()
Expand Down
Loading