From 3f283786c4d4d6147e1c24cd6845220a753c5f54 Mon Sep 17 00:00:00 2001 From: Nikhil Sonti Date: Mon, 22 Jun 2026 15:38:02 -0700 Subject: [PATCH 1/4] fix: guard compiled server native addon loading --- .../apps/server/src/compiled-bootstrap.ts | 4 ++ .../apps/server/src/lib/native-addon-guard.ts | 19 +++++++ .../build/server/native-addon-policy.test.ts | 52 ++++++++++++++----- 3 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 packages/browseros-agent/apps/server/src/compiled-bootstrap.ts create mode 100644 packages/browseros-agent/apps/server/src/lib/native-addon-guard.ts diff --git a/packages/browseros-agent/apps/server/src/compiled-bootstrap.ts b/packages/browseros-agent/apps/server/src/compiled-bootstrap.ts new file mode 100644 index 000000000..1c488a5b7 --- /dev/null +++ b/packages/browseros-agent/apps/server/src/compiled-bootstrap.ts @@ -0,0 +1,4 @@ +import { installNativeAddonGuard } from './lib/native-addon-guard' + +installNativeAddonGuard() +await import('./index') diff --git a/packages/browseros-agent/apps/server/src/lib/native-addon-guard.ts b/packages/browseros-agent/apps/server/src/lib/native-addon-guard.ts new file mode 100644 index 000000000..12e663cba --- /dev/null +++ b/packages/browseros-agent/apps/server/src/lib/native-addon-guard.ts @@ -0,0 +1,19 @@ +export const NATIVE_ADDON_DISABLED_MESSAGE = + 'BrowserOS server disables native addon loading in compiled production builds' + +interface GuardedProcess extends NodeJS.Process { + __browserosNativeAddonGuardInstalled?: boolean +} + +/** Blocks native addons before Bun can extract bundled `.node` files. */ +export function installNativeAddonGuard(): void { + const guardedProcess = process as GuardedProcess + if (guardedProcess.__browserosNativeAddonGuardInstalled) return + + const guard: NodeJS.Process['dlopen'] = () => { + throw new Error(NATIVE_ADDON_DISABLED_MESSAGE) + } + + process.dlopen = guard + guardedProcess.__browserosNativeAddonGuardInstalled = true +} diff --git a/packages/browseros-agent/scripts/build/server/native-addon-policy.test.ts b/packages/browseros-agent/scripts/build/server/native-addon-policy.test.ts index 02e86b8c9..15104c49e 100644 --- a/packages/browseros-agent/scripts/build/server/native-addon-policy.test.ts +++ b/packages/browseros-agent/scripts/build/server/native-addon-policy.test.ts @@ -1,9 +1,12 @@ import { afterEach, describe, expect, it } from 'bun:test' -import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises' +import { mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' -import { COMPILED_SERVER_EXEC_ARGV } from './compile' +const nativeAddonGuardPath = join( + process.cwd(), + 'apps/server/src/lib/native-addon-guard.ts', +) describe('compiled server native addon policy', () => { let tempDir: string | null = null @@ -15,8 +18,28 @@ describe('compiled server native addon policy', () => { } }) - it('bakes native-addon loading disablement into compiled server binaries', () => { - expect(COMPILED_SERVER_EXEC_ARGV).toContain('--no-addons') + it('installs the native-addon guard idempotently', async () => { + tempDir = await mkdtemp(join(tmpdir(), 'browseros-native-addon-policy-')) + const sourcePath = join(tempDir, 'idempotent.ts') + await writeFile( + sourcePath, + [ + `import { installNativeAddonGuard } from ${JSON.stringify(nativeAddonGuardPath)}`, + 'installNativeAddonGuard()', + 'const guarded = process.dlopen', + 'installNativeAddonGuard()', + 'console.log(String(process.dlopen === guarded))', + ].join('\n'), + ) + + const result = await collectProcess( + Bun.spawn(['bun', sourcePath], { + stdout: 'pipe', + stderr: 'pipe', + }), + ) + + expect(result).toMatchObject({ exitCode: 0, stdout: 'true\n' }) }) it('prevents Bun from opening hidden temp native addons', async () => { @@ -32,25 +55,20 @@ describe('compiled server native addon policy', () => { await writeFile( sourcePath, [ + `import { installNativeAddonGuard, NATIVE_ADDON_DISABLED_MESSAGE } from ${JSON.stringify(nativeAddonGuardPath)}`, + 'installNativeAddonGuard()', 'try {', ' require("./addon.node")', '} catch (error) {', ' console.error(error?.message ?? String(error))', + ' console.error(NATIVE_ADDON_DISABLED_MESSAGE)', ' setInterval(() => {}, 1000)', '}', ].join('\n'), ) const build = Bun.spawn( - [ - 'bun', - 'build', - '--compile', - `--compile-exec-argv=${COMPILED_SERVER_EXEC_ARGV.join(' ')}`, - sourcePath, - '--outfile', - binaryPath, - ], + ['bun', 'build', '--compile', sourcePath, '--outfile', binaryPath], { stdout: 'pipe', stderr: 'pipe', @@ -85,8 +103,9 @@ describe('compiled server native addon policy', () => { const appResult = await collectProcess(app) expect(appResult.stderr).toContain( - 'Cannot load native addon because loading addons is disabled', + 'BrowserOS server disables native addon loading in compiled production builds', ) + expect(await listFiles(runTmpDir)).toEqual([]) expect(openFiles.stdout).not.toContain('.node') }) }) @@ -106,3 +125,8 @@ async function collectProcess(process: CollectableProcess) { return { stdout, stderr, exitCode } } + +async function listFiles(dir: string): Promise { + const entries = await readdir(dir, { recursive: true }) + return entries.map(String).sort() +} From b65def930385c5bfc80b6c726d0e0e26d748ce13 Mon Sep 17 00:00:00 2001 From: Nikhil Sonti Date: Mon, 22 Jun 2026 15:39:09 -0700 Subject: [PATCH 2/4] fix: stop embedding bun runtime argv in server binary --- .../browseros-agent/apps/server/src/index.ts | 17 ----------------- .../scripts/build/server/compile.ts | 8 +++----- .../build/server/native-addon-policy.test.ts | 8 ++++++++ 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/packages/browseros-agent/apps/server/src/index.ts b/packages/browseros-agent/apps/server/src/index.ts index 9eb2be49e..48990b3db 100755 --- a/packages/browseros-agent/apps/server/src/index.ts +++ b/packages/browseros-agent/apps/server/src/index.ts @@ -6,7 +6,6 @@ * BrowserOS Server - Entry Point */ -// Runtime check for Bun if (typeof Bun === 'undefined') { console.error('Error: This application requires Bun runtime.') console.error( @@ -23,22 +22,6 @@ import { loadServerConfig } from './config' import { isPortInUseError } from './lib/port-binding' import { Sentry } from './lib/sentry' import { Application } from './main' -import { VERSION } from './version' - -/** Handles app flags passed after Bun's compiled-runtime separator. */ -function isCompiledVersionRequest(argv: string[]): boolean { - return ( - process.execArgv.includes('--no-addons') && - argv.length === 4 && - argv[2] === '--' && - argv[3] === '--version' - ) -} - -if (isCompiledVersionRequest(process.argv)) { - console.log(VERSION) - process.exit(0) -} const configResult = loadServerConfig() diff --git a/packages/browseros-agent/scripts/build/server/compile.ts b/packages/browseros-agent/scripts/build/server/compile.ts index 4db29724c..28cccf370 100644 --- a/packages/browseros-agent/scripts/build/server/compile.ts +++ b/packages/browseros-agent/scripts/build/server/compile.ts @@ -9,11 +9,10 @@ import type { BuildTarget, CompiledServerBinary } from './types' const DIST_PROD_ROOT = 'dist/prod/server' const TMP_ROOT = join(DIST_PROD_ROOT, '.tmp') const BUNDLE_DIR = join(TMP_ROOT, 'bundle') -const BUNDLE_ENTRY = join(BUNDLE_DIR, 'index.js') +const BUNDLE_ENTRY = join(BUNDLE_DIR, 'compiled-bootstrap.js') const BINARIES_DIR = join(TMP_ROOT, 'binaries') -// Bun embeds native addons by extracting hidden temp `.node` files at runtime. -export const COMPILED_SERVER_EXEC_ARGV = ['--no-addons'] +export const SERVER_BUNDLE_ENTRYPOINT = 'apps/server/src/compiled-bootstrap.ts' function compiledBinaryPath(target: BuildTarget): string { return join( @@ -30,7 +29,7 @@ async function bundleServer( mkdirSync(BUNDLE_DIR, { recursive: true }) const result = await Bun.build({ - entrypoints: ['apps/server/src/index.ts'], + entrypoints: [SERVER_BUNDLE_ENTRYPOINT], outdir: BUNDLE_DIR, target: 'bun', minify: true, @@ -66,7 +65,6 @@ async function compileTarget( '--outfile', binaryPath, `--target=${target.bunTarget}`, - `--compile-exec-argv=${COMPILED_SERVER_EXEC_ARGV.join(' ')}`, '--external=node-pty', ] await runCommand('bun', args, env) diff --git a/packages/browseros-agent/scripts/build/server/native-addon-policy.test.ts b/packages/browseros-agent/scripts/build/server/native-addon-policy.test.ts index 15104c49e..ebdd41c8d 100644 --- a/packages/browseros-agent/scripts/build/server/native-addon-policy.test.ts +++ b/packages/browseros-agent/scripts/build/server/native-addon-policy.test.ts @@ -3,6 +3,8 @@ import { mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' +import { SERVER_BUNDLE_ENTRYPOINT } from './compile' + const nativeAddonGuardPath = join( process.cwd(), 'apps/server/src/lib/native-addon-guard.ts', @@ -18,6 +20,12 @@ describe('compiled server native addon policy', () => { } }) + it('bundles the compiled bootstrap entrypoint', () => { + expect(SERVER_BUNDLE_ENTRYPOINT).toBe( + 'apps/server/src/compiled-bootstrap.ts', + ) + }) + it('installs the native-addon guard idempotently', async () => { tempDir = await mkdtemp(join(tmpdir(), 'browseros-native-addon-policy-')) const sourcePath = join(tempDir, 'idempotent.ts') From c99e9b6e7ca3ef2a694de103bb95337f7d2e0337 Mon Sep 17 00:00:00 2001 From: Nikhil Sonti Date: Mon, 22 Jun 2026 15:39:55 -0700 Subject: [PATCH 3/4] test: assert bare server version flag --- packages/browseros-agent/apps/server/tests/build.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/browseros-agent/apps/server/tests/build.test.ts b/packages/browseros-agent/apps/server/tests/build.test.ts index 3f991ec6d..74d4b4b41 100644 --- a/packages/browseros-agent/apps/server/tests/build.test.ts +++ b/packages/browseros-agent/apps/server/tests/build.test.ts @@ -136,8 +136,7 @@ describe('server build', () => { assert.fail(`Build failed (exit ${buildExit}):\n${stderr}`) } - // Embedded Bun exec argv consumes bare runtime-looking flags. - const proc = Bun.spawn([binaryPath, '--', '--version'], { + const proc = Bun.spawn([binaryPath, '--version'], { stdout: 'pipe', stderr: 'pipe', }) @@ -152,7 +151,9 @@ describe('server build', () => { 0, `Binary --version exited non-zero:\n${versionStderr}`, ) - assert.strictEqual(versionOutput.trim(), expectedVersion) + const actualVersion = versionOutput.trim() + assert.strictEqual(actualVersion, expectedVersion) + assert.notStrictEqual(actualVersion, Bun.version) }, 300_000) it('archives CI builds without R2 config or production env secrets', async () => { From 09f280ed3af13983534039a6dddd18be02ae6828 Mon Sep 17 00:00:00 2001 From: Nikhil Sonti Date: Mon, 22 Jun 2026 15:41:57 -0700 Subject: [PATCH 4/4] chore: register compiled server entrypoint --- packages/browseros-agent/.fallowrc.json | 1 + .../browseros-agent/apps/server/src/lib/native-addon-guard.ts | 2 +- .../scripts/build/server/native-addon-policy.test.ts | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/browseros-agent/.fallowrc.json b/packages/browseros-agent/.fallowrc.json index b2cf34797..02dbebd41 100644 --- a/packages/browseros-agent/.fallowrc.json +++ b/packages/browseros-agent/.fallowrc.json @@ -2,6 +2,7 @@ "$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json", "entry": [ "apps/server/src/index.ts", + "apps/server/src/compiled-bootstrap.ts", "apps/agent/entrypoints/app/main.tsx", "apps/agent/entrypoints/sidepanel/main.tsx", "apps/agent/entrypoints/background/index.ts", diff --git a/packages/browseros-agent/apps/server/src/lib/native-addon-guard.ts b/packages/browseros-agent/apps/server/src/lib/native-addon-guard.ts index 12e663cba..6a2f20622 100644 --- a/packages/browseros-agent/apps/server/src/lib/native-addon-guard.ts +++ b/packages/browseros-agent/apps/server/src/lib/native-addon-guard.ts @@ -1,4 +1,4 @@ -export const NATIVE_ADDON_DISABLED_MESSAGE = +const NATIVE_ADDON_DISABLED_MESSAGE = 'BrowserOS server disables native addon loading in compiled production builds' interface GuardedProcess extends NodeJS.Process { diff --git a/packages/browseros-agent/scripts/build/server/native-addon-policy.test.ts b/packages/browseros-agent/scripts/build/server/native-addon-policy.test.ts index ebdd41c8d..2c3ae1bda 100644 --- a/packages/browseros-agent/scripts/build/server/native-addon-policy.test.ts +++ b/packages/browseros-agent/scripts/build/server/native-addon-policy.test.ts @@ -63,13 +63,12 @@ describe('compiled server native addon policy', () => { await writeFile( sourcePath, [ - `import { installNativeAddonGuard, NATIVE_ADDON_DISABLED_MESSAGE } from ${JSON.stringify(nativeAddonGuardPath)}`, + `import { installNativeAddonGuard } from ${JSON.stringify(nativeAddonGuardPath)}`, 'installNativeAddonGuard()', 'try {', ' require("./addon.node")', '} catch (error) {', ' console.error(error?.message ?? String(error))', - ' console.error(NATIVE_ADDON_DISABLED_MESSAGE)', ' setInterval(() => {}, 1000)', '}', ].join('\n'),