UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

96 lines • 4.79 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import { getAdmission, lineRunning, redirectTargetJudged } from "../../context/session_context.js"; import { decide } from "../match/decide.js"; import { opRuling, posixLevel } from "../match/rule.js"; import { SESSION_SCOPED } from "../mixin.js"; import { Outcome, } from "../types.js"; /** * The profile's `commands` rules, enforced. * * Seeded by the workspace after `MountRootPolicy` (POSIX messages still * win) and before user policies, so a document rule speaks before a * coded one when both match. It reads the session's compiled rules * through the narrow `SessionCommandsQuery` by the session id the entry point * put in the context, never through ambient state: an explicit fact * survives the async hop that drops a store. Verdicts render through * the one outcome table (`renderDeny`), so an agent cannot tell a * document deny from a coded one. * * `preCommand` renders one `decide` call, which is where the law lives: * the allow list first (a line it does not cover is refused whole, * though its head was visible), then the winning rule, refused whole or * per operand by whether it names paths, or taken to the approval ledger * when it asks. `preVfs` reads the rules that are pure paths, deny and ask * alike (`opRuling`), so FUSE, programmatic ops and the warm cache cannot * bypass a path the profile protects. A path rule that asks is a question only where no * line is running (a file tool, the host's facade), which the entry point puts * to the approval ledger keyed by rule and path; inside a line it * refuses, since the line was admitted without it. */ export class PermissionsPolicy { [SESSION_SCOPED] = true; sessions; constructor(sessions) { this.sessions = sessions; } preCommand(ctx) { const decision = decide(ctx, this.sessions.commandsOf(ctx.sessionId ?? '')); if (decision.outcome === Outcome.ALLOW) return null; const rule = decision.rule; if (rule === null) { const program = (ctx.program !== undefined && ctx.program.length > 0 ? ctx.program : [ctx.command]).join(' '); return { kind: 'deny', reason: `${program} is not allowed` }; } if (decision.outcome === Outcome.ASK) { return { kind: 'ask', reason: rule.reason, rule, rules: decision.asks }; } if (decision.matchedPath === null) return { kind: 'deny', reason: rule.reason, rule }; return { kind: 'deny', reason: rule.reason, path: decision.matchedPath, rule }; } preVfs(ctx) { if (redirectTargetJudged(ctx.path.virtual)) return null; // The grants belong to the line, not the session: a once grant is // spent as the command is admitted, so by the time its own walk // reaches the dispatcher the session holds nothing and only the // command's gate still remembers the nod: the one its op carries (none, // when its command was admitted without one), or the bound one for an op // that says nothing about its command. const gate = ctx.gate === undefined ? getAdmission() : ctx.gate; const ruled = opRuling(this.sessions.commandsOf(ctx.sessionId ?? ''), ctx, gate?.granted ?? []); if (ruled === null) return null; const [rule, asks] = ruled; if (asks && gate === null && !lineRunning()) return { kind: 'ask', reason: rule.reason, rule }; return { kind: 'deny', reason: rule.reason, rule }; } /** * Whether this session's rules speak at `method`: always at command * admission, and at the dispatcher only through a pure path rule, the one kind * an op can meet (`opRuling`). */ wantsFor(method, sessionId) { if (method !== 'preVfs') return Promise.resolve(true); const rules = this.sessions.commandsOf(sessionId); if (rules === null) return Promise.resolve(false); return Promise.resolve([...rules.deny, ...rules.ask].some(posixLevel)); } } //# sourceMappingURL=permissions.js.map