/** * 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} 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} 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} */ 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 }