Files
bare-operating-system/packages/bare-os-booter/lib/kernel-runner.js
T
Raven Scott dd5890b98c Core error propagation hardening:
Updated kernel-runner delegate deny path to emit on stderr in packages/bare-os-booter/lib/kernel-runner.js.
Added structured errno metadata (err.code) via vfsErr(...) and applied it to key VFS traversal/permission throws in packages/bare-os-booter/lib/vfs.js (ENOENT/EACCES paths).
Critical command exit-code fixes:

packages/bare-os-coreutils/src/ls.js
Tracks access/stat failures and sets nonzero exit when any operand fails.
packages/bare-os-coreutils/src/grep.js
Recursive walker now reports traversal/stat errors back to main flow so fatal status becomes 2.
packages/bare-os-coreutils/src/rm.js
-f only suppresses not-found style errors; permission/deny failures stay nonzero.
packages/bare-os-coreutils/src/chmod.js
Treats falsy/no-op backend chmod result as failure (nonzero).
packages/bare-os-coreutils/src/find.js
Added root-path preflight so missing root now reports explicit error + nonzero.
Minor quirks:

packages/bare-os-coreutils/src/xargs.js
-P0 now maps to bounded parallel cap (env/default cap), not forced serial.
packages/bare-os-coreutils/src/ulimit.js
-f with value now returns explicit unsupported-setter diagnostic + nonzero.
-f alone reports unlimited.
Regression tests:

Added packages/bare-os-coreutils/test/error-propagation.test.mjs covering:
grep missing file => exit 2
rm -f permission error => nonzero
find missing root => nonzero + diagnostic
ulimit -f 1M => explicit unsupported + nonzero
xargs -P0 bounded behavior
2026-04-27 08:31:31 -04:00

558 lines
17 KiB
JavaScript

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<string, number>} */
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<string, string | undefined> | 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<string, string | undefined> | 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<string, string | undefined> | 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<string, unknown>} ctx
* @param {string[]} argv
* @param {string} source
* @returns {Promise<boolean>} 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<loadBareOsHostDelegates>} */
let _delegateCache = null
function bareOsHostDelegates() {
if (!_delegateCache) _delegateCache = loadBareOsHostDelegates()
return _delegateCache
}
/**
* @param {Record<string, unknown>} 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<string, unknown>} 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<string, unknown>} ctx
* @param {string} src
* @param {string[]} argv
* @param {string} [_label] reserved for diagnostics
*/
/**
* Evaluate user script source (same contract as `/bin` utilities).
* @param {Record<string, unknown>} 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<string, unknown>} 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<string, unknown>} 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<string, unknown>} ctx
* @param {string} name
* @returns {Promise<string | null>}
*/
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
}