Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Draft
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
14 changes: 14 additions & 0 deletions packages/engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ await browserLease.release();

Most users should use `@hyperframes/producer` or the `hyperframes` CLI instead of calling the engine directly.

## Strict Chromium sandbox mode

HyperFrames keeps its existing browser launch behavior by default for backwards compatibility. A trusted Linux renderer can opt into a fail-closed Chromium process sandbox and site isolation profile with:

```bash
PRODUCER_BROWSER_SANDBOX_MODE=strict hyperframes render ./composition
```

Strict mode refuses to launch on Linux unless HyperFrames can verify that it is running as a non-root user. It also omits `--no-sandbox`, `--disable-setuid-sandbox`, and `--no-zygote`, enables `--site-per-process`, and prevents the normal render engine from disabling site isolation. The host or container must provide a working Chromium sandbox; HyperFrames does not fall back to an unsafe launch when strict mode fails.

This setting governs browsers launched by the render engine. Auxiliary CLI commands that launch their own browser are outside this contract.

Trusted renderer health checks can additionally set `PRODUCER_SANDBOX_STATUS_PATH` to an absolute file path. Before any composition page is opened, HyperFrames uses the final render browser itself to load `chrome://sandbox` and atomically writes a bounded, mode-`0600` JSON status record. The browser launch fails if that opted-in capture cannot complete. This record is runtime diagnostic evidence, not a cryptographic attestation or a substitute for container isolation.

## Documentation

Full documentation: [hyperframes.heygen.com/packages/engine](https://hyperframes.heygen.com/packages/engine)
Expand Down
9 changes: 9 additions & 0 deletions packages/engine/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ export {
type CaptureMode,
type AcquiredBrowser,
} from "./services/browserManager.js";
export {
BROWSER_SANDBOX_MODE_ENV,
assertBrowserSandboxRuntime,
buildDisabledBrowserFeaturesArg,
getBrowserSandboxLaunchArgs,
getBrowserSandboxProcessArgs,
resolveBrowserSandboxMode,
type BrowserSandboxMode,
} from "./services/browserSandbox.js";
export {
augmentProtocolTimeoutError,
isProtocolTimeoutError,
Expand Down
66 changes: 66 additions & 0 deletions packages/engine/src/services/browserManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ describe("BeginFrame capability probe", () => {

describe("buildChromeArgs browser GPU mode", () => {
const base = { width: 1920, height: 1080 };
const originalBrowserSandboxMode = process.env.PRODUCER_BROWSER_SANDBOX_MODE;

afterEach(() => {
if (originalBrowserSandboxMode === undefined) {
delete process.env.PRODUCER_BROWSER_SANDBOX_MODE;
} else {
process.env.PRODUCER_BROWSER_SANDBOX_MODE = originalBrowserSandboxMode;
}
});

it("uses SwiftShader software GL by default for reproducible local renders", () => {
const args = buildChromeArgs(base);
Expand Down Expand Up @@ -187,6 +196,20 @@ describe("buildChromeArgs browser GPU mode", () => {
expect(args).toContain("--use-angle=swiftshader");
expect(args).not.toContain("--use-angle=metal");
});

it("uses the Chromium process sandbox and site isolation in strict mode", () => {
process.env.PRODUCER_BROWSER_SANDBOX_MODE = "strict";

const args = buildChromeArgs({ ...base, platform: "linux" });

expect(args).toContain("--site-per-process");
expect(args).not.toContain("--no-sandbox");
expect(args).not.toContain("--disable-setuid-sandbox");
expect(args).not.toContain("--no-zygote");
const disabledFeatures = args.find((arg) => arg.startsWith("--disable-features="));
expect(disabledFeatures).not.toContain("IsolateOrigins");
expect(disabledFeatures).not.toContain("site-per-process");
});
});

describe("browser launch capture-mode contract", () => {
Expand Down Expand Up @@ -759,3 +782,46 @@ describe("browser pool", () => {
await acquirePromise.catch(() => {});
});
});

describe("browser sandbox status launch gate", () => {
const originalStatusPath = process.env.PRODUCER_SANDBOX_STATUS_PATH;

afterEach(async () => {
if (originalStatusPath === undefined) {
delete process.env.PRODUCER_SANDBOX_STATUS_PATH;
} else {
process.env.PRODUCER_SANDBOX_STATUS_PATH = originalStatusPath;
}
await drainBrowserPool();
_setPuppeteerForTests(undefined);
});

it("closes Chromium and rejects acquisition when the opted-in status capture fails", async () => {
const directory = mkdtempSync(join(tmpdir(), "hf-sandbox-gate-"));
const close = vi.fn().mockResolvedValue(undefined);
const browser = {
connected: true,
version: vi.fn().mockResolvedValue("HeadlessChrome/131.0.0.0"),
process: vi.fn().mockReturnValue({ pid: 4242, kill: vi.fn(), killed: false }),
newPage: vi.fn().mockResolvedValue({
goto: vi.fn().mockRejectedValue(new Error("sandbox status unavailable")),
close: vi.fn().mockResolvedValue(undefined),
}),
close,
disconnect: vi.fn(),
} as unknown as Browser;
_setPuppeteerForTests({
launch: vi.fn().mockResolvedValue(browser),
} as unknown as PuppeteerNode);
process.env.PRODUCER_SANDBOX_STATUS_PATH = join(directory, "status.json");

try {
await expect(
acquireBrowser(["--no-sandbox"], { enableBrowserPool: false, forceScreenshot: true }),
).rejects.toThrow("sandbox status unavailable");
expect(close).toHaveBeenCalledOnce();
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
});
33 changes: 27 additions & 6 deletions packages/engine/src/services/browserManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@ import { existsSync, readdirSync } from "fs";
import { join } from "path";
import { homedir } from "os";
import { DEFAULT_CONFIG, type EngineConfig } from "../config.js";
import {
assertBrowserSandboxRuntime,
buildDisabledBrowserFeaturesArg,
getBrowserSandboxLaunchArgs,
getBrowserSandboxProcessArgs,
resolveBrowserSandboxMode,
} from "./browserSandbox.js";
import { getSystemTotalMb, LOW_MEMORY_TOTAL_MB_THRESHOLD } from "./systemMemory.js";
import {
BrowserLeasePool,
type BrowserLaunchFingerprint,
type BrowserLease,
type CaptureMode,
} from "./browserLeasePool.js";
import { captureBrowserSandboxStatus } from "./browserSandboxStatus.js";

export { BrowserLeasePool } from "./browserLeasePool.js";
export type {
Expand Down Expand Up @@ -449,9 +457,10 @@ async function getPuppeteerOrNull(): Promise<PuppeteerNode | null> {
}

function getHardwareGpuProbeArgs(platform: NodeJS.Platform): string[] {
const browserSandboxMode = resolveBrowserSandboxMode();
assertBrowserSandboxRuntime(browserSandboxMode);
return [
"--no-sandbox",
"--disable-setuid-sandbox",
...getBrowserSandboxLaunchArgs(browserSandboxMode),
"--disable-dev-shm-usage",
"--enable-webgl",
"--ignore-gpu-blocklist",
Expand Down Expand Up @@ -645,6 +654,7 @@ async function launchBrowser(
}
}

await captureBrowserSandboxStatus(browser);
return { browser, captureMode };
} catch (error) {
await browser?.close().catch(() => {});
Expand Down Expand Up @@ -761,6 +771,8 @@ export function buildChromeArgs(
options: BuildChromeArgsOptions,
config?: Partial<Pick<EngineConfig, "browserGpuMode" | "disableGpu" | "chromePath">>,
): string[] {
const browserSandboxMode = resolveBrowserSandboxMode();
assertBrowserSandboxRuntime(browserSandboxMode);
const platform = options.platform ?? process.platform;
const gpuDisabled = config?.disableGpu ?? DEFAULT_CONFIG.disableGpu;
const browserGpuMode = gpuDisabled
Expand All @@ -771,8 +783,7 @@ export function buildChromeArgs(
// appear in Puppeteer's defaults, Playwright, Remotion, and Chrome's own
// headless-shell guidance.
const chromeArgs = [
"--no-sandbox",
"--disable-setuid-sandbox",
...getBrowserSandboxLaunchArgs(browserSandboxMode),
"--disable-dev-shm-usage",
CANVAS_DRAW_ELEMENT_FEATURE_FLAG,
"--enable-webgl",
Expand Down Expand Up @@ -800,13 +811,23 @@ export function buildChromeArgs(
"--disable-domain-reliability",
"--disable-print-preview",
"--no-pings",
"--no-zygote",
...getBrowserSandboxProcessArgs(browserSandboxMode),
// Memory — scale GPU budget to available system RAM
`--force-gpu-mem-available-mb=${getGpuMemBudgetMb()}`,
"--disk-cache-size=268435456",
...getLowMemoryFlags(),
// Disable features that add overhead
"--disable-features=AudioServiceOutOfProcess,IsolateOrigins,site-per-process,Translate,BackForwardCache,IntensiveWakeUpThrottling",
buildDisabledBrowserFeaturesArg(
[
"AudioServiceOutOfProcess",
"IsolateOrigins",
"site-per-process",
"Translate",
"BackForwardCache",
"IntensiveWakeUpThrottling",
],
browserSandboxMode,
),
// Allow AudioContext to start without a user gesture in headless Chrome.
// Without this flag, any code path that constructs an AudioContext
// (including GSAP tweening an <audio> element's volume) triggers the
Expand Down
53 changes: 53 additions & 0 deletions packages/engine/src/services/browserSandbox.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { describe, expect, it } from "vitest";

import {
assertBrowserSandboxRuntime,
buildDisabledBrowserFeaturesArg,
getBrowserSandboxLaunchArgs,
getBrowserSandboxProcessArgs,
resolveBrowserSandboxMode,
} from "./browserSandbox.js";

describe("browser sandbox policy", () => {
it("keeps the existing unsafe launch policy unless strict mode is explicit", () => {
expect(resolveBrowserSandboxMode(undefined)).toBe("legacy-unsafe");
expect(getBrowserSandboxLaunchArgs("legacy-unsafe")).toEqual([
"--no-sandbox",
"--disable-setuid-sandbox",
]);
expect(getBrowserSandboxProcessArgs("legacy-unsafe")).toEqual(["--no-zygote"]);
});

it("enables site isolation without sandbox-disabling launch switches in strict mode", () => {
expect(resolveBrowserSandboxMode(" strict ")).toBe("strict");
expect(getBrowserSandboxLaunchArgs("strict")).toEqual(["--site-per-process"]);
expect(getBrowserSandboxProcessArgs("strict")).toEqual([]);
expect(
buildDisabledBrowserFeaturesArg(
["AudioServiceOutOfProcess", "IsolateOrigins", "site-per-process", "Translate"],
"strict",
),
).toBe("--disable-features=AudioServiceOutOfProcess,Translate");
});

it("rejects unknown modes instead of silently weakening the browser policy", () => {
expect(() => resolveBrowserSandboxMode("enabled")).toThrow(
'PRODUCER_BROWSER_SANDBOX_MODE must be "legacy-unsafe" or "strict"',
);
});

it("rejects a strict Linux launch as root", () => {
expect(() => assertBrowserSandboxRuntime("strict", { platform: "linux", uid: 0 })).toThrow(
"requires HyperFrames to run as a non-root user",
);
expect(() =>
assertBrowserSandboxRuntime("strict", { platform: "linux", uid: 1000 }),
).not.toThrow();
});

it("rejects strict Linux mode when the process uid cannot be verified", () => {
expect(() =>
assertBrowserSandboxRuntime("strict", { platform: "linux", uid: undefined }),
).toThrow("requires HyperFrames to verify a non-root Linux user");
});
});
54 changes: 54 additions & 0 deletions packages/engine/src/services/browserSandbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
export const BROWSER_SANDBOX_MODE_ENV = "PRODUCER_BROWSER_SANDBOX_MODE";

export type BrowserSandboxMode = "legacy-unsafe" | "strict";

export function resolveBrowserSandboxMode(
rawValue: string | undefined = process.env[BROWSER_SANDBOX_MODE_ENV],
): BrowserSandboxMode {
const value = rawValue?.trim().toLowerCase();
if (value === undefined || value === "" || value === "legacy-unsafe") {
return "legacy-unsafe";
}
if (value === "strict") return value;
throw new Error(
`${BROWSER_SANDBOX_MODE_ENV} must be "legacy-unsafe" or "strict"; received ${JSON.stringify(rawValue)}`,
);
}

export function assertBrowserSandboxRuntime(
mode: BrowserSandboxMode,
runtime: { platform: NodeJS.Platform; uid: number | undefined } = {
platform: process.platform,
uid: process.getuid?.(),
},
): void {
if (mode === "strict" && runtime.platform === "linux" && runtime.uid === undefined) {
throw new Error(
`${BROWSER_SANDBOX_MODE_ENV}=strict requires HyperFrames to verify a non-root Linux user`,
);
}
if (mode === "strict" && runtime.platform === "linux" && runtime.uid === 0) {
throw new Error(
`${BROWSER_SANDBOX_MODE_ENV}=strict requires HyperFrames to run as a non-root user`,
);
}
}

export function getBrowserSandboxLaunchArgs(mode: BrowserSandboxMode): string[] {
return mode === "strict" ? ["--site-per-process"] : ["--no-sandbox", "--disable-setuid-sandbox"];
}

export function getBrowserSandboxProcessArgs(mode: BrowserSandboxMode): string[] {
return mode === "strict" ? [] : ["--no-zygote"];
}

export function buildDisabledBrowserFeaturesArg(
features: readonly string[],
mode: BrowserSandboxMode,
): string {
const disabledFeatures =
mode === "strict"
? features.filter((feature) => feature !== "IsolateOrigins" && feature !== "site-per-process")
: [...features];
return `--disable-features=${disabledFeatures.join(",")}`;
}
Loading