From e01f5d9d8a1b5ee967f2286b3bec6f779a04bafe Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Mon, 25 Apr 2022 11:12:29 -0700 Subject: [PATCH 1/4] fix: enable cross-origin isolation --- src/cross-origin-isolation.ts | 12 ++++++++++++ src/server.ts | 2 ++ src/test/server_test.ts | 10 ++++++++++ 3 files changed, 24 insertions(+) create mode 100644 src/cross-origin-isolation.ts diff --git a/src/cross-origin-isolation.ts b/src/cross-origin-isolation.ts new file mode 100644 index 0000000..3ffdb54 --- /dev/null +++ b/src/cross-origin-isolation.ts @@ -0,0 +1,12 @@ +import {Middleware} from 'koa'; + +// Enable cross-origin isolation for more precise timers: +// https://developer.chrome.com/blog/cross-origin-isolated-hr-timers/ +export function crossOriginIsolation(): Middleware { + // Based on https://github.com/fishel-feng/koa-isolated + return async function isolated(ctx, next) { + ctx.set('Cross-Origin-Opener-Policy', 'same-origin'); + ctx.set('Cross-Origin-Embedder-Policy', 'require-corp'); + await next(); + }; +} diff --git a/src/server.ts b/src/server.ts index 7840fa8..f75e83f 100644 --- a/src/server.ts +++ b/src/server.ts @@ -19,6 +19,7 @@ import {nodeResolve} from 'koa-node-resolve'; import {BenchmarkResponse, Deferred} from './types'; import {NpmInstall} from './versions'; +import {crossOriginIsolation} from './cross-origin-isolation'; export interface ServerOpts { host: string; @@ -88,6 +89,7 @@ export class Server { this.server = server; const app = new Koa(); + app.use(crossOriginIsolation()); app.use(bodyParser()); app.use(mount('/submitResults', this.submitResults.bind(this))); app.use(this.instrumentRequests.bind(this)); diff --git a/src/test/server_test.ts b/src/test/server_test.ts index 052a18f..863a56c 100644 --- a/src/test/server_test.ts +++ b/src/test/server_test.ts @@ -135,4 +135,14 @@ suite('server', () => { session = server.endSession(); assert.equal(session.bytesSent, 0); }); + + test('enables cross-origin isolation', async () => { + const res = await fetch(`${server.url}/import-bare-module.html`); + + assert.equal(res.headers.get('Cross-Origin-Opener-Policy'), 'same-origin'); + assert.equal( + res.headers.get('Cross-Origin-Embedder-Policy'), + 'require-corp' + ); + }); }); From 3cd4426d2d2c315d5a861fd82b031ae0bd876a99 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sun, 4 Aug 2024 09:12:47 -0700 Subject: [PATCH 2/4] fix: add header and cli option --- README.md | 1 + src/cli.ts | 1 + src/config.ts | 6 +++ src/cross-origin-isolation.ts | 5 +++ src/defaults.ts | 1 + src/flags.ts | 1 + src/server.ts | 7 +++- src/test/config_test.ts | 5 +++ src/test/server_test.ts | 75 +++++++++++++++++++++-------------- 9 files changed, 71 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 61eb287..4838d2e 100644 --- a/README.md +++ b/README.md @@ -872,3 +872,4 @@ tach http://example.com | `--trace` | `false` | Enable performance tracing ([details](#performance-traces)) | | `--trace-log-dir` | `${cwd}/logs` | The directory to put tracing log files. Defaults to `${cwd}/logs`. | | `--trace-cat` | [default categories](./src/defaults.ts) | The tracing categories to record. Should be a string of comma-separated category names | +| `--cross-origin-isolated` | `false` | Add HTTP headers to enable [cross-origin isolation](https://developer.mozilla.org/en-US/docs/Web/API/Window/crossOriginIsolated). | diff --git a/src/cli.ts b/src/cli.ts index 67966e8..1ca1ab5 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -162,6 +162,7 @@ $ tach http://example.com mountPoints, resolveBareModules: config.resolveBareModules, cache: config.mode !== 'manual', + crossOriginIsolated: config.crossOriginIsolated, }); for (const spec of specs) { servers.set(spec, server); diff --git a/src/config.ts b/src/config.ts index f1e943e..7e07d68 100644 --- a/src/config.ts +++ b/src/config.ts @@ -37,6 +37,7 @@ export interface Config { npmrc?: string; csvFileStats: string; csvFileRaw: string; + crossOriginIsolated: boolean; } export async function makeConfig(opts: Opts): Promise { @@ -55,6 +56,7 @@ export async function makeConfig(opts: Opts): Promise { ? parseGithubCheckFlag(opts['github-check']) : undefined, remoteAccessibleHost: opts['remote-accessible-host'], + crossOriginIsolated: opts['cross-origin-isolated'], }; let config: Config; @@ -175,6 +177,10 @@ export function applyDefaults(partial: Partial): Config { : defaults.resolveBareModules, root: partial.root !== undefined ? partial.root : defaults.root, timeout: partial.timeout !== undefined ? partial.timeout : defaults.timeout, + crossOriginIsolated: + partial.crossOriginIsolated !== undefined + ? partial.crossOriginIsolated + : defaults.crossOriginIsolated, }; } diff --git a/src/cross-origin-isolation.ts b/src/cross-origin-isolation.ts index 3ffdb54..29dc5dd 100644 --- a/src/cross-origin-isolation.ts +++ b/src/cross-origin-isolation.ts @@ -1,3 +1,8 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ import {Middleware} from 'koa'; // Enable cross-origin isolation for more precise timers: diff --git a/src/defaults.ts b/src/defaults.ts index 4d698e1..ccca3d3 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -20,6 +20,7 @@ export const mode = 'automatic'; export const resolveBareModules = true; export const forceCleanNpmInstall = false; export const measurementExpression = 'window.tachometerResult'; +export const crossOriginIsolated = false; export const traceLogDir = path.join(process.cwd(), 'logs'); export const traceCategories = [ 'blink', diff --git a/src/flags.ts b/src/flags.ts index 57e9dd0..f621844 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -266,6 +266,7 @@ export interface Opts { trace: boolean; 'trace-log-dir': string; 'trace-cat': string; + 'cross-origin-isolated': boolean; // Extra arguments not associated with a flag are put here. These are our // benchmark names/URLs. diff --git a/src/server.ts b/src/server.ts index 27e6052..b21e571 100644 --- a/src/server.ts +++ b/src/server.ts @@ -19,7 +19,7 @@ import {nodeResolve} from 'koa-node-resolve'; import {BenchmarkResponse, Deferred} from './types.js'; import {NpmInstall} from './versions.js'; -import {crossOriginIsolation} from './cross-origin-isolation'; +import {crossOriginIsolation} from './cross-origin-isolation.js'; import * as url from 'url'; const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); @@ -32,6 +32,7 @@ export interface ServerOpts { mountPoints: MountPoint[]; resolveBareModules: boolean; cache: boolean; + crossOriginIsolated: boolean; } export interface MountPoint { @@ -92,7 +93,9 @@ export class Server { this.server = server; const app = new Koa(); - app.use(crossOriginIsolation()); + if (opts.crossOriginIsolated) { + app.use(crossOriginIsolation()); + } app.use(bodyParser()); app.use(mount('/submitResults', this.submitResults.bind(this))); app.use(this.instrumentRequests.bind(this)); diff --git a/src/test/config_test.ts b/src/test/config_test.ts index b828449..a97a71e 100644 --- a/src/test/config_test.ts +++ b/src/test/config_test.ts @@ -46,6 +46,7 @@ suite('makeConfig', function () { csvFileStats: '', csvFileRaw: '', githubCheck: undefined, + crossOriginIsolated: false, benchmarks: [ { browser: { @@ -92,6 +93,7 @@ suite('makeConfig', function () { csvFileRaw: '', // TODO(aomarks) Be consistent about undefined vs unset. githubCheck: undefined, + crossOriginIsolated: false, benchmarks: [ { browser: { @@ -137,6 +139,7 @@ suite('makeConfig', function () { csvFileStats: '', csvFileRaw: '', githubCheck: undefined, + crossOriginIsolated: false, benchmarks: [ { browser: { @@ -190,6 +193,7 @@ suite('makeConfig', function () { remoteAccessibleHost: '', // TODO(aomarks) Be consistent about undefined vs unset. githubCheck: undefined, + crossOriginIsolated: false, benchmarks: [ { browser: { @@ -237,6 +241,7 @@ suite('makeConfig', function () { remoteAccessibleHost: '', // TODO(aomarks) Be consistent about undefined vs unset. githubCheck: undefined, + crossOriginIsolated: false, benchmarks: [ { browser: { diff --git a/src/test/server_test.ts b/src/test/server_test.ts index cce4555..cf40c93 100644 --- a/src/test/server_test.ts +++ b/src/test/server_test.ts @@ -17,20 +17,25 @@ import {testData} from './test_helpers.js'; suite('server', () => { let server: Server; + const defaultOptions = { + host: 'localhost', + ports: [0], // random + root: testData, + resolveBareModules: true, + npmInstalls: [], + mountPoints: [ + { + diskPath: testData, + urlPath: '/', + }, + ], + cache: true, + crossOriginIsolated: false, + }; + setup(async () => { server = await Server.start({ - host: 'localhost', - ports: [0], // random - root: testData, - resolveBareModules: true, - npmInstalls: [], - mountPoints: [ - { - diskPath: testData, - urlPath: '/', - }, - ], - cache: true, + ...defaultOptions, }); }); @@ -88,18 +93,8 @@ suite('server', () => { await server.close(); server = await Server.start({ - host: 'localhost', - ports: [0], // random - root: testData, - resolveBareModules: true, + ...defaultOptions, npmInstalls: [{installDir, packageJson}], - mountPoints: [ - { - diskPath: testData, - urlPath: '/', - }, - ], - cache: true, }); }); @@ -138,13 +133,35 @@ suite('server', () => { assert.equal(session.bytesSent, 0); }); - test('enables cross-origin isolation', async () => { + test('cross-origin isolation is disabled by default', async () => { const res = await fetch(`${server.url}/import-bare-module.html`); - assert.equal(res.headers.get('Cross-Origin-Opener-Policy'), 'same-origin'); - assert.equal( - res.headers.get('Cross-Origin-Embedder-Policy'), - 'require-corp' - ); + assert.equal(res.headers.get('Cross-Origin-Opener-Policy'), null); + assert.equal(res.headers.get('Cross-Origin-Embedder-Policy'), null); + }); + + suite('cross origin isolation enabled', async () => { + setup(async () => { + // Close the base server and replace it with a custom server that is + // configured with crossOriginIsolated=true + await server.close(); + server = await Server.start({ + ...defaultOptions, + crossOriginIsolated: true, + }); + }); + + test('cross-origin isolation can be enabled', async () => { + const res = await fetch(`${server.url}/import-bare-module.html`); + + assert.equal( + res.headers.get('Cross-Origin-Opener-Policy'), + 'same-origin' + ); + assert.equal( + res.headers.get('Cross-Origin-Embedder-Policy'), + 'require-corp' + ); + }); }); }); From 0aee155c75e838a9ec04012c736e94d3b8aacf7a Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sun, 4 Aug 2024 09:39:34 -0700 Subject: [PATCH 3/4] fix: actually expose the cli option --- src/flags.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/flags.ts b/src/flags.ts index f621844..da3df1c 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -235,6 +235,11 @@ export const optDefs: commandLineUsage.OptionDefinition[] = [ type: String, defaultValue: defaults.traceCategories.join(','), }, + { + name: 'cross-origin-isolated', + description: 'Add HTTP headers to enable cross-origin isolation', + type: Boolean, + }, ]; export interface Opts { From 0b7e64343d5645bf1d75e1a806999edf2c180525 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sun, 4 Aug 2024 17:42:14 -0700 Subject: [PATCH 4/4] docs: update changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55d2a43..46b28aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). - +## Unreleased + +- Added the `--cross-origin-isolated` CLI flag to enable [cross-origin isolation](https://developer.mozilla.org/en-US/docs/Web/API/Window/crossOriginIsolated). ## [0.7.1] 2024-07-18