/** * Tool guardrail chokepoint (E3 · T11407 · SG-PACKAGE-ARCH). * * A SINGLE deny-first validation layer wrapping every atomic primitive * ({@link ./fs.js} + {@link ./shell.js}). All side-effecting tool calls flow * through `createToolGuard()` so policy lives in ONE place: * - **fs** → path **allowlist** (deny-first: when `allowedRoots` is set, a path * must resolve under one of them, else it is denied). * - **shell** → command **denylist** (a command whose name matches the * denylist is rejected before spawning). * * Ships **warn-then-enforce**: the default `mode: 'warn'` logs a structured * warning and proceeds, so NO existing call site breaks when it adopts the * guard; `mode: 'enforce'` throws {@link GuardDeniedError} before any side * effect. The boundary lint (T11409) later makes the guarded surface the only * public one. * * ## Date-gated default flip (T11474 · AC4) * * The default posture is governed by {@link resolveDefaultGuardMode}, a * date-gated mechanism keyed on {@link GUARD_ENFORCE_DEADLINE}. Until that * deadline passes the default stays `warn`; on/after it the date-gate WOULD * yield `enforce`. * * The flip is, however, an **owner-gated decision** and is NOT auto-applied. * Per the owner-ratified self-shaping hard-gate (BRAIN `O-mpt8gjdx-0`, * 2026-05-30), flipping this default to `enforce` is one of THREE gates that * together unblock dynamic tool/skill forging; the other two — an approval-token * CONDUIT gate and a discretion rate-limit (`DiscretionEvaluator`) — are NOT yet * live. So the live default is held at `warn` behind * {@link GUARD_ENFORCE_FLIP_ENABLED} (currently `false`); the mechanism is * encoded but the flip awaits explicit owner confirmation that (a) the deadline * has passed AND (b) the full suite is green with `enforce` on. Flip by setting * `GUARD_ENFORCE_FLIP_ENABLED = true` once those conditions hold. * * @epic T11390 * @task T11407 * @saga T11387 */ import type { RunShellInput, RunShellResult } from '@cleocode/contracts/tools/agent-tools'; import type { ExecuteShellInput, ExecuteShellResult, PathExistsInput, PathExistsResult, ReadFileInput, ReadFileResult, RunGitInput, WriteFileInput, WriteFileResult } from '@cleocode/contracts/tools/atomic'; import { type ShellExecutor } from './shell.js'; /** Enforcement posture for a {@link ToolGuard}. */ export type GuardMode = 'warn' | 'enforce'; /** * Owner-set deadline (ISO-8601 date, UTC) after which the guard's DATE-GATE * would yield `enforce` as the default posture. * * This is the date leg of the warn→enforce flip (T11474 · AC4). It is a single * source of truth — change this one const to move the date. The flip is NOT * applied on this date alone: it is additionally held behind * {@link GUARD_ENFORCE_FLIP_ENABLED} (an owner-gated kill-switch), because the * owner-ratified self-shaping hard-gate (BRAIN `O-mpt8gjdx-0`) requires two * sibling systems (approval-token CONDUIT gate + discretion rate-limit) to be * live before `enforce` becomes the default. * * @remarks Placeholder owner-TBD value held one year out — the live default * stays `warn` regardless via {@link GUARD_ENFORCE_FLIP_ENABLED}. Replace with * the real ratified date when the owner sets it. */ export declare const GUARD_ENFORCE_DEADLINE = "2027-01-01T00:00:00.000Z"; /** * Owner-gated master switch for the warn→enforce default flip. * * When `false` (the current, held state), the live default is ALWAYS `warn` * regardless of {@link GUARD_ENFORCE_DEADLINE} — the date-gate is encoded but * inert. Set to `true` ONLY when the owner confirms (a) the deadline has passed, * (b) the full suite is green with `enforce` on, and (c) the sibling self-shaping * gates are live (BRAIN `O-mpt8gjdx-0`). This indirection keeps the mechanism in * source without a silent, date-triggered behavior change. */ export declare const GUARD_ENFORCE_FLIP_ENABLED = false; /** * Resolve the date-gated default {@link GuardMode}. * * Returns `enforce` only when BOTH the owner master switch * ({@link GUARD_ENFORCE_FLIP_ENABLED}) is on AND `now` is at/after * {@link GUARD_ENFORCE_DEADLINE}; otherwise `warn`. Callers that pass an * explicit `policy.mode` to {@link createToolGuard} bypass this entirely. * * @param now - The instant to evaluate the deadline against; defaults to the * current time. Injectable so unit tests can assert both sides of the gate * without clock manipulation. * @returns `'enforce'` once the gate fully opens, else `'warn'`. * * @example * ```ts * // Today (switch off): always 'warn' * resolveDefaultGuardMode(); // 'warn' * ``` */ export declare function resolveDefaultGuardMode(now?: Date): GuardMode; /** Deny-first policy for the tool guard. */ export interface ToolGuardPolicy { /** * Absolute roots that fs primitives may touch. When set, a path NOT resolving * under any root is a violation. When omitted, fs paths are unrestricted * (the guard still funnels them through the chokepoint for logging). */ readonly allowedRoots?: readonly string[]; /** * Command names (basenames) that shell primitives may NOT run, e.g. * `['rm', 'shutdown', 'mkfs']`. Matched against the command's last path * segment. */ readonly deniedCommands?: readonly string[]; /** * `'warn'` logs + proceeds; `'enforce'` throws before the effect. When * omitted, the default comes from the date-gated {@link resolveDefaultGuardMode} * (held at `'warn'` until the owner-gated flip — see {@link GUARD_ENFORCE_DEADLINE}). */ readonly mode?: GuardMode; } /** Thrown by an `enforce`-mode guard when a primitive call violates policy. */ export declare class GuardDeniedError extends Error { /** Machine-readable code. */ readonly code = "E_TOOL_GUARD_DENIED"; constructor(message: string); } /** The guarded primitive surface returned by {@link createToolGuard}. */ export interface ToolGuard { /** * The resolved enforcement posture this guard was constructed with. Exposed so * a security-sensitive consumer (e.g. the Pi adapter) can ASSERT it received an * `enforce`-mode guard before handing it untrusted work — in `warn` mode the * allowlist/denylist are advisory (log-and-proceed) and provide no boundary. */ readonly mode: GuardMode; readFileText(input: ReadFileInput): Promise; readJson(path: string): Promise; writeFileAtomic(input: WriteFileInput): Promise; pathExists(input: PathExistsInput): Promise; executeShell(input: ExecuteShellInput, executor?: ShellExecutor): Promise; /** * Run a command under a PTY (or a non-PTY spawn fallback) through the guard. * The command basename is checked against the denylist BEFORE any process is * spawned and the child env is scrubbed — same policy point as * {@link ToolGuard.executeShell}. */ executePty(input: RunShellInput): Promise; runGit(input: RunGitInput, executor?: ShellExecutor): Promise; } /** * Build a deny-first {@link ToolGuard} from a {@link ToolGuardPolicy}. Every * returned primitive validates against the policy, then (when allowed, or in * `warn` mode) delegates to the raw `core/src/tools` primitive. * * @example * ```ts * const tools = createToolGuard({ allowedRoots: [projectRoot], mode: 'enforce' }); * await tools.writeFileAtomic({ path: join(projectRoot, 'x'), content: '1' }); // ok * await tools.writeFileAtomic({ path: '/etc/passwd', content: 'x' }); // throws * ``` */ export declare function createToolGuard(policy?: ToolGuardPolicy): ToolGuard; //# sourceMappingURL=guard.d.ts.map