UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

163 lines • 10.2 kB
import type { ByteSource } from '../../io/types.ts'; import type { AdmissionRules, Ask, Claimant, CommandContext, CommandRule, Deny, Pending } from '../../policy/index.ts'; import { type EntryGate } from '../../policy/types.ts'; import type { TSNodeLike } from '../../shell/types.ts'; import { PathSpec, type Refusal } from '../../types.ts'; import type { NamespaceLinks } from '../../view/types.ts'; import type { Namespace } from '../mount/namespace/namespace.ts'; import type { MountRegistry } from '../mount/registry.ts'; import type { SessionState } from '../session/session.ts'; import { type Word } from './inner_lines.ts'; /** * What the command plane prints when a line does not get to run: 126 for * a whole-command refusal or an unanswered ask, the operand code (1, tar * 2) for a refusal about a path or carrying an error. `refusal` is the * record the result carries beside stderr, null for the filesystem's own * error. Mirrors the Python `Refused`. */ export interface Refused { readonly stderr: Uint8Array; readonly exitCode: number; readonly refusal: Refusal | null; } /** * A command the gate let through, and what its own I/O may touch. * * The gate judged the paths the line names; a walk below them reaches * entries no rule has seen, so the dispatcher binds this to the session * context for the command's run and the commands tier asks it before each * read, write or listing (`EntryGate`). The paths the gate already judged * pass, since the line was admitted on them; every other entry is judged * by `ioRefusal` under the same precedence the gate applied to the line, * and a refusal is the dispatcher's `PolicyDenied` (EACCES, the path, the * reason on its record), which every command renders as GNU's * `Permission denied`. * `granted` holds the ask rules the line runs under a grant for: the one * the entry point answered for this line, and the session's standing ones. * `ctx` is what `preCommand` judged the command on, which `postCommand` * extends with what came back. */ export declare class Admitted implements EntryGate { readonly rules: AdmissionRules | null; readonly ctx: CommandContext; readonly judged: ReadonlySet<string>; readonly granted: readonly CommandRule[]; constructor(init: { rules: AdmissionRules | null; ctx: CommandContext; judged: ReadonlySet<string>; granted: readonly CommandRule[]; }); check(virtual: string): void; scopes(virtual: string): boolean; refusal(virtual: string): string | null; } /** * The paths a path-pattern guard reads for a line: the operands as * typed and the values of path-valued flags, a command tree's group and * leaf options among them (`treeFlags`, from `treePathScopes`), then, for * a command that * follows links, the targets they resolve to. `cat /data/link` reads * `/data/secret`, so a rule protecting the target has to see it, and a * command-scoped rule never runs at the dispatcher where the resolved path * would otherwise be checked. The links are followed as the command's own * session sees them (`links`, `Namespace.sessionLinks`), as the router * follows them: on the browser fallback the ambient view folds every live * session's hides, and a peer hiding the link must not keep its target from * the rule; null where no link stands. The follow policy is the command's own * (`follows`, whether `LineFacts.follows` is not NONE: rm, mv, ln, stat, * tar ... act on the link itself, `-L` turns following back on), the same * one the router applies to the operands before the handler runs, so a rule * sees exactly the path the command will touch. A loop is left to that later * step to report; here the typed paths stand. Then the operand a bare * `ls`/`find`/`du`/`tree`/`grep -r` implies, the working directory, * which the executor injects after the gate and which a rule on that * directory has to see. Last come the statement's redirect targets: * `cat < /data/secret` reads the file and `echo x > /data/secret` * truncates it, on the shell's own fds outside the admitted command's * gate window, so the admission is the one place a rule can see them. A * redirect always dereferences (the shell opens the target), so its * link targets ride along whatever the command's own follow policy * says. */ export declare function policyScopes(name: string, args: readonly string[], operands: readonly (string | PathSpec)[], follows: boolean, links: NamespaceLinks | null, cwd: string, implied?: PathSpec | null, redirects?: readonly PathSpec[], treeFlags?: readonly PathSpec[]): PathSpec[]; /** * The command plane's admission of one command: the policy chain, then * the approval ledger. The one gate every command class passes through, * in the tree (`runArgv`, once the words are expanded) and for a line a * runtime takes whole (`admitLine`, per parsed command). A word the * session's allow list leaves out is the profile's Deny, like any other * rule's; a path the session cannot see is * dropped before any method, so a rule never names it and the dispatcher * answers ENOENT; a Deny renders in the outcome table's voice; an Ask * is answered by the entry point from the session's grants or the host. * `stdin` decides whether a bare `rg` reads the working directory. */ /** * One command's literal words, classified the way the runtime would * classify them, so the gate and the run name the same paths. */ export declare function classifiedWords(name: string, args: readonly string[], session: SessionState, registry: MountRegistry): Promise<(string | PathSpec)[]>; /** * The paths a statement's redirect targets name. * * Shared by admission and by the dry run, because a rule reads a * redirect the same way in both: a target only the runtime can expand * names no path here, and one that is not path-shaped is not a file. */ export declare function redirectPaths(words: readonly Word[], registry: MountRegistry, cwd: string): PathSpec[]; /** * Everything the gate decides about one command before anything is * spent on it: the classified context, and the policy chain's answer (the * first Deny, else the first Ask) with the answers it came from. * * Split out of `admit` so a dry run can have the answer without the * consequences. Nothing here records a request, consumes a grant or * reaches the host, which is what makes it safe for `explain`; * `admit` adds exactly those and renders. * * `intrinsic` keeps shell-provided operations subject to tool allow lists * even when a function shadows their policy name. `unread` holds the * virtual paths a reader of the line's text cannot vouch for: what a word * only the runtime expands names, or a relative word after a `cd` it * could not follow. No policy is shown them; the per-command gate reads * the real ones and passes none. `every` asks every policy past a Deny, * for `explain`; the gate's own answers end at the first Deny. * `rulesOnly` asks the profile's rules alone, for a pass that judges a line * before it runs; a policy's own `preCommand` is asked once, by the gate * that runs the command. `line` is the line the command was parsed from, * empty outside a line. */ /** * Whether the allow list refuses a CLI that reaches a service. Such a CLI * (an account or script CLI) reaches its service by id, past every hide, so * under a profile that hides paths it runs only where a pattern starting * with its own name matches the line. `*` and a pattern for another of its * verbs do not count. Mirrors the Python `unlisted_service`. */ export declare function unlistedService(ctx: CommandContext, registry: MountRegistry, session: SessionState): boolean; export declare function gate(name: string, args: readonly string[], operands: readonly (string | PathSpec)[], session: SessionState, registry: MountRegistry, namespace: Namespace | null, stdin?: ByteSource | null, redirects?: readonly PathSpec[], intrinsic?: boolean, unread?: ReadonlySet<string>, every?: boolean, rulesOnly?: boolean, line?: string): Promise<[CommandContext, Deny | Ask | null, (Deny | Ask)[]]>; /** * The chain's answer once the ledger has had its say. An Ask is the chain's * answer only after every Deny had its say; the ledger answers it from the * session's records or the host, so an answer never re-opens a deny. A wait * the run's kill cut short is the kill landing late, not a ruling, so it * throws as every other abandoned wait does rather than being rendered as a * refusal the document never made. Mirrors the Python `resolved`. */ export declare function resolved(ctx: CommandContext, asked: Deny | Ask | null, registry: MountRegistry, signal: AbortSignal | undefined, claimant: Claimant | null): Promise<Deny | Pending | null>; export declare function admit(name: string, args: readonly string[], operands: readonly (string | PathSpec)[], session: SessionState, registry: MountRegistry, namespace: Namespace | null, stdin?: ByteSource | null, redirects?: readonly PathSpec[], signal?: AbortSignal, claimant?: Claimant | null, intrinsic?: boolean, unread?: ReadonlySet<string>, rulesOnly?: boolean): Promise<Refused | Admitted>; /** * Admit a `[ ]` test, or one `[[ ]]` leaf, as the command it is: the shell * evaluates it itself, so no command gate saw its words, and a rule, an allow * list or a policy answers it by its own name as it answers `test` on the * same words. `name` is `[` or `[[`; `words` what it tests, expanded, * operators included; `signal` the run's abort signal; `claimant` the test * and its line, null outside a line. Throws `CondRefused` where the gate * refused it. Mirrors Python's admit_test. */ export declare function admitTest(name: string, words: string[], session: SessionState, registry: MountRegistry, namespace: Namespace | null, signal?: AbortSignal, claimant?: Claimant | null): Promise<Admitted>; /** Mark redirected input without opening or expanding its source. */ export declare function statementStdin(node: TSNodeLike): Uint8Array | null; export declare function statementRedirects(node: TSNodeLike, home: string | null): Word[]; //# sourceMappingURL=admission.d.ts.map