UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

136 lines (135 loc) • 7.9 kB
import { canonicalizeTarget, lexicalViewOf, pathFamilyOf, writeTargetPath } from "../tools/fs/safety.js"; import { isWithin } from "./runner/session-rule-policy.js"; import { swallowedFenceRoot } from "./runner/advertised-writable-dirs.js"; import { declaredPathTargetOf, isProtectedWrite } from "./tool-registry.js"; import { pathTargetBaseOf } from "./effective-path-target.js"; import { judgePathRows } from "./path-guard-rows.js"; export function domainRow(source, dirs) { return { name: `write domain (${source})`, grammar: { kind: "domain", dirs, source }, polarity: "allow", faces: ["write"], placement: "anywhere", fold: "exact", exemptible: false, origin: source === "advertised" ? "builtin" : "addition", reach: (view) => (dirs.some((dir) => isWithin(dir, view.path)) ? { reach: "reached" } : { reach: "not_reached" }), }; } const ask = (message) => ({ action: "ask", message, decisionReason: "rule" }); function absoluteRootOf(rootPath, envCwd) { return rootPath === undefined ? undefined : lexicalViewOf(rootPath, envCwd); } export const WRITE_EXEMPT_DIR_COVERS_ROOT_CODE = "config.write_exempt_dir_covers_root"; function assertExemptDirsDoNotCoverRoot(exemptDirs, rootLexical) { if (exemptDirs === undefined || rootLexical === undefined) return; for (const raw of exemptDirs) { if (!raw) continue; const dirLexical = lexicalViewOf(raw, rootLexical); const swallowed = swallowedFenceRoot(dirLexical, [rootLexical]); if (swallowed === undefined) continue; const e = new Error(`createFsWriteGatePolicy: exemptDirs entry "${raw}" resolves to ${dirLexical}, which covers this gate's root ${swallowed} — ` + `an exemption that holds the root auto-approves every write under it, which is the whole workspace, so the gate refuses to be built rather than run with write governance silently off. ` + `If that IS what this deployment wants, say it with the knob that means it: defaultWrite: "allow", an open write face, or a permission rule.`); e.code = WRITE_EXEMPT_DIR_COVERS_ROOT_CODE; e.detail = { exemptDir: raw, exemptDirResolved: dirLexical, root: swallowed }; throw e; } } export function createFsWriteGatePolicy(opts) { const { env, rootPath, defaultWrite } = opts; const acceptDirs = opts.acceptDirs && opts.acceptDirs.length > 0 ? opts.acceptDirs : undefined; const exemptDirs = opts.exemptDirs && opts.exemptDirs.length > 0 ? opts.exemptDirs : undefined; const rootLexical = absoluteRootOf(rootPath, env.cwd); assertExemptDirsDoNotCoverRoot(exemptDirs, rootLexical); const guardedCapable = env.writeFileGuarded !== undefined; const advisory = guardedCapable ? "" : " (advisory adjudication: this environment provides no guarded write, so approval is judged before a separate, non-atomic write step)"; return { async check(req, signal) { if (!isProtectedWrite(req)) return { action: "allow" }; if (opts.requireGuardedWrite === true && !guardedCapable) { return { action: "deny", message: `write tool "${req.toolName}" refused: this deployment requires guarded (atomic precondition-verified) writes (requireGuardedWrite), but the execution environment provides no writeFileGuarded capability — refusing rather than falling back to a non-atomic write`, decisionReason: "rule", }; } const declared = declaredPathTargetOf(req); const path = declared === undefined ? undefined : writeTargetPath(req, declared); if (path === undefined) { return ask(`write tool "${req.toolName}" requires approval: the call has no resolvable path target to confine${declared === undefined ? " (the tool declares no path target)" : ""}${advisory}`); } const canon = await canonicalizeTarget(env, path, signal, pathFamilyOf({ root: rootPath, cwd: req.cwd ?? env.cwd }), pathTargetBaseOf(declared, { root: rootPath, cwd: req.cwd })); if (!canon.ok) { return ask(`write to "${path}" requires approval: its real target could not be resolved (${canon.message})${advisory}`); } const advertised = req.advertisedWritableRoots ?? []; const targetViews = { views: [{ path: canon.key, kind: "canonical" }] }; const admitted = { configured: [], accepted: [] }; const admits = () => judgePathRows([domainRow("advertised", advertised), domainRow("configured", admitted.configured), domainRow("accepted", admitted.accepted)], targetViews, "allow") !== undefined; if (admits()) return { action: "allow" }; let fenceRootsCanonical; let refusedDomain; for (const [dirs, structural] of [ [exemptDirs, true], [acceptDirs, false], ]) { if (!dirs) continue; for (const raw of dirs) { const dir = await canonicalizeTarget(env, raw, signal, pathFamilyOf({ root: rootPath ?? env.cwd }), rootPath); if (!dir.ok) continue; if (structural) { if (fenceRootsCanonical === undefined) { const r = rootPath === undefined ? undefined : await canonicalizeTarget(env, rootPath, signal, pathFamilyOf({ root: rootPath }), undefined); fenceRootsCanonical = r === undefined ? [] : r.ok ? [r.key] : "unresolved"; } if (fenceRootsCanonical === "unresolved") { refusedDomain ??= `"${raw}" (this task's root ${rootPath} could not be resolved, so a structural domain that may cover it grants nothing)`; continue; } const swallowed = swallowedFenceRoot(dir.key, fenceRootsCanonical); if (swallowed !== undefined) { refusedDomain ??= `"${raw}" (resolves to ${dir.key}, which contains this task's root ${swallowed})`; continue; } } (structural ? admitted.configured : admitted.accepted).push(dir.key); if (admits()) return { action: "allow" }; } } if (opts.isExempt) { let exempt = false; try { exempt = (await opts.isExempt(req.toolName, canon.key, req.face !== undefined ? { capabilityId: req.face.capabilityId, contractId: req.face.contractId, shapeDigest: req.face.shapeDigest } : undefined)) === true; } catch { exempt = false; } if (exempt) { return { action: "allow", message: `write to "${path}" allowed by session exemption (previously approved for this session)`, decisionReason: "rule", }; } } if (defaultWrite === "allow") return { action: "allow" }; const note = refusedDomain === undefined ? undefined : ` — note: the configured write domain ${refusedDomain} was not honored; a domain that holds the root would exempt every write under it`; if (opts.defaultAsk !== undefined) return opts.defaultAsk(path, note); return ask(`approve write to "${path}"?${advisory}${note ?? ""}`); }, }; }