/** * EXPERIMENTAL prototype — typed, safe-by-construction harness GUARDS. * * Dogfood finding (real OSS hooks: our pre-edit.sh, superpowers/OMC session-start, * OMC keyword-detector): a "hook" today is an arbitrary shell command in * settings.json — which is BOTH the enforcement vehicle AND the RCE footgun * (CVE-2025-59536: a malicious repo's hook runs before the trust dialog). Arbitrary * hook safety is UNDECIDABLE (Rice). So this prototype inverts it: you don't WRITE a * hook, you DECLARE a guard from a closed, audited vocabulary, and vigiles GENERATES * the hooks block — whose command is vigiles's OWN gate (`vigiles hook-runtime guard`), never * user shell. Safe-by-construction, not safe-by-analysis. * * Covers the real PreToolUse patterns + the new ORDER axis: * - block: deny a tool call matching args (reproduces pre-edit.sh's intent) * - requireBefore: ORDER — deny a tool call until a prerequisite call has fired * (`terraform destroy` only after `terraform plan`; the moat) * - confine: deny a path-taking tool whose path escapes an allowlist (rm -rf /) * * EXPERIMENTAL — not on the public API. But the gate now RUNS end-to-end: the * `vigiles hook-runtime guard` CLI subcommand reads the live PreToolUse event, loads the * guard set (`.vigiles/guards.json`) + the session ledger (`.vigiles/guard-ledger.json`, * the reconstructed prior-call list `requireBefore` needs), runs `decideGuards`, and * blocks (exit 2 + reason) or records-the-allowed-call (so the next call sees it). The * pure `decideGuards` is the decision; `compileGuards` is the generator; the * serialization + ledger below are the IO seam the CLI calls. * See research/harness-protocol-flow-moat.md. */ import { type ArgMatcher } from "../arg-match.js"; /** A tool-call shape: a tool name + an optional argument matcher (the `when`). */ export interface ToolPattern { readonly tool: string; readonly when?: ArgMatcher; } export type Guard = { readonly kind: "block"; readonly target: ToolPattern; readonly reason: string; } | { readonly kind: "requireBefore"; readonly target: ToolPattern; /** The prerequisite call that must have fired earlier this session. */ readonly prerequisite: ToolPattern; readonly reason?: string; } | { readonly kind: "confine"; readonly tools: readonly string[]; /** Dot-path to the path argument (default `file_path`). */ readonly pathKey?: string; /** Allowed path prefixes (a call outside ALL of them is denied). */ readonly allow: readonly string[]; readonly reason?: string; }; /** Ergonomic builders for the closed vocabulary. */ export declare const guard: { block: (target: ToolPattern, reason: string) => Guard; requireBefore: (target: ToolPattern, prerequisite: ToolPattern, reason?: string) => Guard; confine: (tools: readonly string[], allow: readonly string[], reason?: string, pathKey?: string) => Guard; }; /** A tool call as seen at PreToolUse (and as recorded in the prior-call ledger). */ export interface ToolEvent { readonly tool: string; readonly input: unknown; } export interface GuardDecision { readonly allow: boolean; /** Set on a deny — the message surfaced to the agent. */ readonly reason?: string; } /** * The pure runtime gate: decide allow/deny for `event`, given the guards and the * calls that already fired this session (`prior`, oldest-first — the ledger the * `guard-hook` CLI reconstructs from the transcript). First match wins a deny. */ export declare function decideGuards(guards: readonly Guard[], event: ToolEvent, prior?: readonly ToolEvent[]): GuardDecision; /** Every tool name a guard set gates (the PreToolUse matcher union). */ export declare function guardedTools(guards: readonly Guard[]): string[]; /** A Claude Code settings `hooks` block (the generated artifact). */ export interface HooksConfig { readonly hooks: { readonly PreToolUse: readonly { readonly matcher: string; readonly hooks: readonly { readonly type: "command"; readonly command: string; }[]; }[]; }; } /** * Generate the hooks block for a guard set. The command is vigiles's OWN gate * (default `npx vigiles hook-runtime guard`), NOT user shell — so the generated * enforcement is safe-by-construction and a repo can't smuggle an arbitrary RCE * hook. The gate reads the same guard set + the live event and runs `decideGuards`. */ export declare function compileGuards(guards: readonly Guard[], gateCommand?: string): HooksConfig; /** Serialize a guard set for `.vigiles/guards.json` (RegExp matchers preserved). */ export declare function serializeGuards(guards: readonly Guard[]): string; /** Parse a guard set from JSON (tolerant — a malformed guard is skipped). */ export declare function parseGuards(json: string): Guard[]; /** Load the declared guard set from `.vigiles/guards.json` (absent → none). */ export declare function loadGuards(cwd: string): Guard[]; /** * The prior-call ledger — the calls already allowed this session, oldest-first. * `requireBefore` reads it to know whether a prerequisite ran. Claude Code doesn't * surface call history to a hook, so vigiles records each allowed call itself * (mirrors `.vigiles/active-agent.json`). */ export declare function readGuardLedger(cwd: string): ToolEvent[]; /** Append an allowed call to the session ledger. */ export declare function recordGuardCall(cwd: string, event: ToolEvent): void; /** Parse a PreToolUse event JSON (the hook's stdin) into a {@link ToolEvent}. */ export declare function parseGuardEvent(rawJson: string): ToolEvent | null; /** The outcome of running the gate against one event. */ export interface GuardHookOutcome { readonly decision: GuardDecision; /** True iff the allowed call was recorded to the ledger. */ readonly recorded: boolean; } /** * The runnable gate, decoupled from process/exit so it's testable. Decides the * event against the loaded guards + the prior-call ledger; on ALLOW it records the * call (so a later `requireBefore` sees it) and on DENY it records nothing (a * blocked call never happened). Malformed/absent event → allow, record nothing. */ export declare function runGuardHook(cwd: string, rawJson: string): GuardHookOutcome; //# sourceMappingURL=guards.d.ts.map