import b4a from 'b4a' import fs from '#host-fs' import unixPathResolve from 'unix-path-resolve' import { raceWithAbortAndTimeout } from './bare-os-abort.js' import { isDelegateKindAllowed, loadBareOsHostDelegates, parseDelegateAllowSet } from './host-delegate-registry.js' import { bareOsDelegateRateAllow } from './delegate-rate-limit.js' import { bareOsDelegateConcurrentExit, bareOsDelegateConcurrentTryEnter } from './bare-os-delegate-concurrent.js' const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor /** @type {Map} */ const workerHeartbeatByKey = new Map() /** * @param {string} key */ export function bareOsNoteWorkerHeartbeat(key) { workerHeartbeatByKey.set(String(key || '').slice(0, 128), Date.now()) } /** * @param {{ staleAfterMs?: number }} [opts] */ export function bareOsWorkerHeartbeatSnapshot(opts = {}) { const now = Date.now() const staleAfterMs = Math.max( 100, Math.min(300000, Number(opts.staleAfterMs) || 30000) ) /** @type {{ key: string, lastMs: number, stale: boolean }[]} */ const rows = [] for (const [key, lastMs] of workerHeartbeatByKey.entries()) { rows.push({ key, lastMs, stale: now - lastMs > staleAfterMs }) } return { schema: 1, atMs: now, staleAfterMs, workers: rows } } /** * Persist non-secret liveness checkpoint when enabled. * @param {Record | null | undefined} env */ export async function bareOsPersistWorkerLivenessCheckpoint(env) { const p = String(env?.BARE_OS_WORKER_LIVENESS_PATH || '').trim() if (!p || (!p.startsWith('/') && !p.startsWith('./'))) return false try { await fs.promises.writeFile( p, JSON.stringify(bareOsWorkerHeartbeatSnapshot(), null, 2) + '\n' ) return true } catch { return false } } /** * Pear/Bare cannot resolve `node:module`. Holepunch's `bare-module` exposes the same `createRequire(parentURL)` as Node. Only called from bin-worker offload after the Node early-return in `tryRunBinInBareWorker`. * @returns {((specifier: string) => unknown) | null} */ async function bareOsCreateRequireFromMetaUrl() { try { const bm = await import('bare-module') const createRequire = bm.createRequire || bm.default?.createRequire if (typeof createRequire !== 'function') return null return createRequire(import.meta.url) } catch { return null } } /** * @param {string} cmd * @param {Record | null | undefined} env */ const _BIN_WORKER_TEXTPROC = new Set([ 'awk', 'sed', 'jq', 'cut', 'tr', 'sort', 'uniq' ]) const _BIN_WORKER_MATHPROC = new Set(['bc', 'dc']) const _BIN_WORKER_IOPROC = new Set(['cat', 'head', 'tail']) const _BIN_WORKER_MEDIAPROC = new Set(['ffmpeg', 'ffprobe']) /** Crypto-class `/bin` entries (e.g. openssl) under `cryptoproc:*` (word 11). */ const _BIN_WORKER_CRYPTOPROC = new Set(['openssl']) /** Indexer-adjacent tools under `indexerproc:*` (word 11; reserved; extend when `/bin` gains indexer helpers). */ const _BIN_WORKER_INDEXERPROC = new Set([]) /** VFS metadata utilities under `metaproc:*` (ACL / xattr sidecars). */ const _BIN_WORKER_METAPROC = new Set(['getfacl', 'setfacl', 'xattr']) /** Process/shell-adjacent builtins allowed under `sysproc:*` (documentary group; bare-process-class). */ const _BIN_WORKER_SYSPROC = new Set([ 'pwd', 'echo', 'printenv', 'uname', 'id', 'env', 'true', 'false' ]) /** * @param {string} cmd * @param {Record | null | undefined} env */ function binWorkerOffloadEnabled(cmd, env) { if (!env) return false if ( env.BARE_OS_BIN_WORKER_OFFLOAD !== '1' && env.BARE_OS_BIN_WORKER_OFFLOAD !== 'true' ) { return false } const allowRaw = String(env.BARE_OS_BIN_WORKER_ALLOW || '').trim() if (!allowRaw) return cmd === 'awk' || cmd === 'sed' || cmd === 'jq' const parts = allowRaw .split(',') .map((s) => s.trim()) .filter(Boolean) for (const p of parts) { if (p === 'textproc:*') { if (_BIN_WORKER_TEXTPROC.has(cmd)) return true continue } if (p === 'mathproc:*') { if (_BIN_WORKER_MATHPROC.has(cmd)) return true continue } if (p === 'ioproc:*') { if (_BIN_WORKER_IOPROC.has(cmd)) return true continue } if (p === 'mediaproc:*') { if (_BIN_WORKER_MEDIAPROC.has(cmd)) return true continue } if (p === 'sysproc:*') { if (_BIN_WORKER_SYSPROC.has(cmd)) return true continue } if (p === 'cryptoproc:*') { if (_BIN_WORKER_CRYPTOPROC.has(cmd)) return true continue } if (p === 'indexerproc:*') { if (_BIN_WORKER_INDEXERPROC.has(cmd)) return true continue } if (p === 'metaproc:*') { if (_BIN_WORKER_METAPROC.has(cmd)) return true continue } if (p.endsWith(':*')) { const pre = p.slice(0, -2) if (pre === 'textproc' && _BIN_WORKER_TEXTPROC.has(cmd)) return true if (pre === 'mathproc' && _BIN_WORKER_MATHPROC.has(cmd)) return true if (pre === 'ioproc' && _BIN_WORKER_IOPROC.has(cmd)) return true if (pre === 'mediaproc' && _BIN_WORKER_MEDIAPROC.has(cmd)) return true if (pre === 'sysproc' && _BIN_WORKER_SYSPROC.has(cmd)) return true if (pre === 'cryptoproc' && _BIN_WORKER_CRYPTOPROC.has(cmd)) return true if (pre === 'indexerproc' && _BIN_WORKER_INDEXERPROC.has(cmd)) return true if (pre === 'metaproc' && _BIN_WORKER_METAPROC.has(cmd)) return true continue } if (p === cmd) return true } return false } /** * @param {Record} ctx * @param {string[]} argv * @param {string} source * @returns {Promise} true when the worker handled the command */ async function tryRunBinInBareWorker(ctx, argv, source) { if ( typeof process !== 'undefined' && process.release && process.release.name === 'node' ) { return false } const cmd = argv[0] const env = ctx.vfs?.env if (!binWorkerOffloadEnabled(cmd, env)) return false const reportFallback = (reason, err) => { if (!env) return const on = env.BARE_OS_BIN_WORKER_REPORT_FALLBACK === '1' || env.BARE_OS_BIN_WORKER_REPORT_FALLBACK === 'true' if (!on) return const msg = err && err.message ? `${reason}: ${err.message}` : String(reason) try { ctx.console?.error?.(`[bare-os] bin-worker fallback for ${cmd}: ${msg}`) } catch { /* ignore */ } } try { const req = await bareOsCreateRequireFromMetaUrl() if (!req) { reportFallback('bare-module createRequire unavailable') return false } const { runBinOffloaded } = req('./bare-os-bin-offload-worker.cjs') const wasmRaw = env && String(env.BARE_OS_BIN_WORKER_WASM_MS_MAX || '').trim() const wasmN = wasmRaw ? Number.parseInt(wasmRaw, 10) : 0 const maxWasmMs = Number.isFinite(wasmN) && wasmN > 0 ? Math.min(wasmN, 3_600_000) : 0 const msg = await runBinOffloaded( { source, argv }, maxWasmMs > 0 ? { maxWasmMs } : undefined ) if (!msg || msg.ok === false) { reportFallback( msg && typeof msg === 'object' && msg.reason ? `worker rejected (${msg.reason})` : 'worker rejected' ) return false } const logs = Array.isArray(msg.logs) ? msg.logs : [] const errs = Array.isArray(msg.errs) ? msg.errs : [] for (const line of logs) ctx.console.log(line) for (const line of errs) ctx.console.error(line) if (Number(msg.droppedLogs) > 0 || Number(msg.droppedErrs) > 0) { ctx.console.error( `[bare-os] bin-worker output truncated for ${cmd} (dropped logs=${Number(msg.droppedLogs) || 0}, errs=${Number(msg.droppedErrs) || 0})` ) } ctx.exitCode = msg.exitCode != null ? Number(msg.exitCode) || 0 : 0 return true } catch (e) { reportFallback('worker exception', /** @type {Error} */ (e)) return false } } /** @type {ReturnType} */ let _delegateCache = null function bareOsHostDelegates() { if (!_delegateCache) _delegateCache = loadBareOsHostDelegates() return _delegateCache } /** * @param {Record} ctx * @param {string} kind * @param {string[]} argv */ async function auditDelegateJson(ctx, kind, argv) { const env = ctx.vfs?.env if (!env || (env.BARE_OS_AUDIT !== '1' && env.BARE_OS_AUDIT !== 'true')) return if (env.BARE_OS_AUDIT_JSON !== '1' && env.BARE_OS_AUDIT_JSON !== 'true') return const { appendVarLog, AUDIT_LOG } = await import('./bare-os-var-log.js') const args = argv.slice(0, 24).map((a) => { const s = String(a) return s.length > 160 ? s.slice(0, 160) + '…' : s }) void appendVarLog( ctx, AUDIT_LOG, 'json', JSON.stringify({ auditSchemaVersion: 8, requestSmugglingClass: 'none', type: 'delegate', kind, ts: Date.now(), argv: args, argvRawCount: argv.length, argvTruncated: argv.length > 24, sessionId: String(env.BARE_OS_SESSION_ID || '').trim() || undefined, delegateChainDepth: 1, delegateChainTruncated: false }) ) } /** Strip one leading Unix shebang so AsyncFunction does not see `#!` as invalid syntax. */ function stripShebang(source) { if (typeof source !== 'string' || !source.startsWith('#!')) return source const m = source.match(/^#![^\n]*\n/) return m ? source.slice(m[0].length) : source } /** * Execute kernel source from Hyperdrive (trusted). Expects top-level `async function start(ctx)`. * @param {string} source * @param {Record} ctx */ export async function runKernelFromSource(source, ctx) { const fn = new AsyncFunction( 'ctx', `${source}\nif (typeof start !== 'function') throw new Error('kernel must define async function start')\nreturn start(ctx)\n` ) return fn(ctx) } /** * Evaluate user script source: top-level statements run in an async function with `ctx` and `argv`. * If the script defines a top-level `run` function, it is awaited after the body (same as `/bin` utilities). * * @param {Record} ctx * @param {string} src * @param {string[]} argv * @param {string} [_label] reserved for diagnostics */ /** * Evaluate user script source (same contract as `/bin` utilities). * @param {Record} ctx * @param {string} src * @param {string[]} argv * @param {string} [_label] */ export async function runUserScriptFromSource(ctx, src, argv, _label = argv[0]) { try { const body = stripShebang(src) const sessionConsole = ctx && ctx.console && typeof ctx.console === 'object' && typeof ctx.console.log === 'function' ? ctx.console : null const prelude = sessionConsole != null ? 'var console = ctx.console;\n' : '' const fn = new AsyncFunction( 'ctx', 'argv', `${prelude}${body}\nif (typeof run === 'function') await run(ctx, argv)\n` ) await fn(ctx, argv) if (ctx.exitCode === undefined || ctx.exitCode === null) ctx.exitCode = 0 return } catch (e) { const msg = e?.message || String(e) const stack = e?.stack if (typeof ctx.console?.error === 'function') { ctx.console.error(msg) if (stack && typeof stack === 'string' && stack !== msg) { const lines = stack.split('\n').slice(1, 4) for (const line of lines) { if (line && line.trim()) ctx.console.error(line.trim()) } } } ctx.exitCode = 1 ctx.bareOsScriptCompletedWithCatch = true } } /** * Run a command: bare `*.js` in cwd (before PATH), path script on any routed drive, then PATH on system. * @param {Record} ctx * @param {string[]} argv * @param {import('./bare-os-abort.js').BareOsAbortOpts} [runOpts] */ export async function runBinCommand(ctx, argv, runOpts) { const env = ctx?.vfs?.env const defTo = Number.parseInt( String(env?.BARE_OS_RUNBIN_DEFAULT_TIMEOUT_MS || ''), 10 ) /** @type {import('./bare-os-abort.js').BareOsAbortOpts | undefined} */ let merged = runOpts && typeof runOpts === 'object' ? { ...runOpts } : undefined if (Number.isFinite(defTo) && defTo > 0) { const cap = Math.min(defTo, 3_600_000) if (!merged) merged = { timeoutMs: cap } else if (merged.timeoutMs == null) merged.timeoutMs = cap } const bridgeOn = env?.BARE_OS_BARE_SUBPROCESS_BRIDGE === '1' || env?.BARE_OS_BARE_SUBPROCESS_BRIDGE === 'true' if (bridgeOn) { const subMs = Number.parseInt( String(env?.BARE_OS_BARE_SUBPROCESS_TIMEOUT_MS || ''), 10 ) if (Number.isFinite(subMs) && subMs > 0) { const cap = Math.min(subMs, 3_600_000) if (!merged) merged = { timeoutMs: cap } else if (merged.timeoutMs == null) merged.timeoutMs = cap } } return raceWithAbortAndTimeout( runBinCommandInner(ctx, argv), merged, 'runBinCommand' ) } /** * @param {Record} ctx * @param {string[]} argv */ async function runBinCommandInner(ctx, argv) { const cmd = argv[0] const systemDrive = ctx.drive const vfs = ctx.vfs const pathEnv = (vfs && vfs.env && vfs.env.PATH) || '/bin' const allow = parseDelegateAllowSet(vfs?.env) const argvTail = argv.length > 1 ? argv.slice(1) : [] // `.sh` scripts should execute through the shell frontend, not JS eval. const runViaSh = (scriptPath) => runBinCommandInner(ctx, ['sh', scriptPath, ...argvTail]) for (const del of bareOsHostDelegates()) { if (!del.shouldDelegate(cmd)) continue if (!isDelegateKindAllowed(del.kind, allow)) { // `curl` / `wget` have drive-resident `/bin` scripts that call // `ctx.bareOsRunCurlCli` / `ctx.bareOsRunWgetCli` (same fetch backends). if (del.kind === 'curl' || del.kind === 'wget') continue ctx.console.error('delegate denied by BARE_OS_DELEGATE_ALLOW: ' + del.kind) ctx.exitCode = 126 return } if (!bareOsDelegateRateAllow(del.kind, vfs?.env)) { ctx.console.error('delegate rate limit exceeded: ' + del.kind) ctx.exitCode = 126 return } const env = vfs?.env const auditOnly = env && (env.BARE_OS_DELEGATE_AUDIT_ONLY === '1' || env.BARE_OS_DELEGATE_AUDIT_ONLY === 'true') && (env.BARE_OS_AUDIT === '1' || env.BARE_OS_AUDIT === 'true') if (auditOnly) { await auditDelegateJson(ctx, del.kind, argv) const { appendVarLog, AUDIT_LOG } = await import('./bare-os-var-log.js') void appendVarLog( ctx, AUDIT_LOG, 'delegate-audit-only', del.kind + ' ' + argv.slice(0, 8).join(' ') ) ctx.console.error( '[bare-os] delegate audit-only (skipped run): ' + del.kind ) ctx.exitCode = 0 return } if (!bareOsDelegateConcurrentTryEnter(del.kind, vfs?.env)) { ctx.console.error('delegate concurrency limit exceeded: ' + del.kind) ctx.exitCode = 126 return } await auditDelegateJson(ctx, del.kind, argv) try { return await del.run(ctx, argv) } finally { bareOsDelegateConcurrentExit(del.kind) } } if (cmd.includes('/')) { if (cmd.endsWith('.sh')) return runViaSh(cmd) const abs = vfs.resolveLogical(cmd) const { drive, path } = vfs.route(abs) const buf = await drive.get(path, { follow: true }) if (!buf) { ctx.console.log('not found: ' + cmd) ctx.exitCode = 127 return } const source = b4a.toString(buf) if (await tryRunBinInBareWorker(ctx, argv, source)) return return runUserScriptFromSource(ctx, source, argv, cmd) } // `script.js` in $PWD before PATH (same VFS routing as `./script.js`). if (cmd.endsWith('.sh')) { const abs = vfs.resolveLogical(cmd) const { drive, path } = vfs.route(abs) const buf = await drive.get(path, { follow: true }) if (buf) return runViaSh(cmd) } // `script.js` in $PWD before PATH (same VFS routing as `./script.js`). if (cmd.endsWith('.js')) { const abs = vfs.resolveLogical(cmd) const { drive, path } = vfs.route(abs) const buf = await drive.get(path, { follow: true }) if (buf) { const source = b4a.toString(buf) if (await tryRunBinInBareWorker(ctx, argv, source)) return return runUserScriptFromSource(ctx, source, argv, cmd) } } const dirs = pathEnv.split(':').filter(Boolean) for (const dir of dirs) { const p = unixPathResolve(dir, cmd) const buf = await systemDrive.get(p, { follow: true }) if (buf) { const source = b4a.toString(buf) if (await tryRunBinInBareWorker(ctx, argv, source)) return return runUserScriptFromSource(ctx, source, argv, p) } } ctx.console.log('unknown command: ' + cmd) ctx.exitCode = 127 } /** * Resolve an executable name on PATH (system drive only). * @param {Record} ctx * @param {string} name * @returns {Promise} */ export async function resolveBinInPath(ctx, name) { if (!name || name.includes('/')) return null const systemDrive = ctx.drive const vfs = ctx.vfs const pathEnv = (vfs && vfs.env && vfs.env.PATH) || '/bin' const dirs = pathEnv.split(':').filter(Boolean) for (const dir of dirs) { const p = unixPathResolve(dir, name) const buf = await systemDrive.get(p, { follow: true }) if (buf) return p } return null }