Files
bare-operating-system/packages/bare-os-booter/lib/bare-os-sandbox.js
T
2026-04-04 18:41:34 -04:00

100 lines
3.1 KiB
JavaScript

/**
* Reduced `ctx` for `bareOsSandboxRunScript`: personal-drive writes only, no identity hooks.
*
* Stronger isolation (separate worker / bundle evaluate) is an optional future path; see
* holepunch `cross-worker` and `bare-bundle-evaluate` for Bare/Pear patterns. When
* `BARE_OS_SANDBOX_WORKER=1`, the booter defers evaluation on a fresh microtask only
* (not a separate thread); see developer-guide security chapter.
*/
import { applyGuestEnv, wipeSecret } from './identity-session.js'
import { isPersonalRoute } from './vfs-posix-meta.js'
/**
* @param {Record<string, unknown>} ctx
* @returns {boolean}
*/
function sandboxWritesOk(ctx, logicalPath) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.resolveLogical !== 'function') return false
if (typeof vfs.route !== 'function') return false
const abs = vfs.resolveLogical(logicalPath)
const r = vfs.route(abs)
const pd = ctx.personalDrive
if (!pd) return false
return isPersonalRoute(pd, r)
}
/**
* @param {Record<string, unknown>} ctx
*/
export function createBareOsSandboxContext(ctx) {
const vfs = ctx.vfs
if (!vfs) throw new Error('bareOsSandboxRunScript: missing vfs')
/**
* @param {string} name
* @param {(...args: unknown[]) => unknown} fn
* @param {number} pathArgIndex
*/
const wrapWrite = (name, fn, pathArgIndex = 0) => {
if (typeof fn !== 'function') return fn
return async function (...args) {
const path = args[pathArgIndex]
if (typeof path === 'string' && !sandboxWritesOk(ctx, path)) {
throw new Error(`sandbox: ${name}: write denied outside personal namespace`)
}
return fn.apply(vfs, args)
}
}
const sandboxVfs = Object.assign(Object.create(Object.getPrototypeOf(vfs)), vfs, {
writeFile: wrapWrite('writeFile', vfs.writeFile, 0),
mkdir: wrapWrite('mkdir', vfs.mkdir, 0),
unlink: wrapWrite('unlink', vfs.unlink, 0),
rmdir: wrapWrite('rmdir', vfs.rmdir, 0),
chmod: wrapWrite('chmod', vfs.chmod, 0),
chown: wrapWrite('chown', vfs.chown, 0),
symlink: wrapWrite('symlink', vfs.symlink, 1)
})
/** @type {Record<string, unknown>} */
const o = Object.assign({}, ctx, {
vfs: sandboxVfs,
bareOsSandboxed: true,
async applyUnlock() {
throw new Error('sandbox: identity unlock disabled')
},
async applyRegister() {
throw new Error('sandbox: identity register disabled')
},
async applyLogin() {
throw new Error('sandbox: identity login disabled')
},
async applyLogout() {
wipeSecret(this)
try {
await applyGuestEnv(this)
} catch {
/* ignore */
}
this.console?.log?.('sandbox: logged out (guest session)')
},
async saveVault() {
this.console?.log?.('sandbox: saveVault skipped (no vault persistence in sandbox)')
},
registerKernelShutdownHook() {},
bareOsRegisterVirtualFile() {
throw new Error('sandbox: bareOsRegisterVirtualFile disabled')
},
bareOsInvalidateVirtualFile() {
throw new Error('sandbox: bareOsInvalidateVirtualFile disabled')
},
bareOsUpdateVirtualFileMeta() {
throw new Error('sandbox: bareOsUpdateVirtualFileMeta disabled')
}
})
return o
}