/** * Permission policy engine (P4b scope §3). * * Pure, synchronous, no I/O. {@link evaluate} resolves a tool call against a * rule set to one of `allow` / `ask` / `deny`. This is the single gate holding * back `write_file` / `edit_file` / `bash` (scope §1) — the engine itself does * not execute, prompt, or format display; the {@link PermissionGate} * (permission-gate.ts) orchestrates I/O around this decision. * * Rule ordering is **last-match-wins** (the opencode semantic — a later rule * overrides an earlier one; do not invert it). Sitting *under* the rules is a * **terminal deny floor**: a path that matches the floor is `deny` regardless * of any later `allow` rule. A permission engine without a deny floor is * theatre; the floor is the substance (scope §3). * * Two engine-level guarantees that no shipped rule set may violate: * - **`bash` never resolves to `allow`.** Even a trailing `allow **` rule * cannot promote bash past `ask` — the engine clamps it. This is the * "dangerous one" backstop (scope §9). * - **Paths outside `cwd` resolve to `deny`.** P4a's `resolveWithin` already * refuses traversal in the read tools; the policy layer agrees rather than * duplicating the check inconsistently (scope §3). * * Rules are a plain array literal here — no config file, no schema, no loader * (scope §3). Config-driven rules land when there is a second consumer. */ /** * Canonical path resolution (E001 security gate, findings 1–3). * * `path.resolve` is purely lexical: an in-cwd symlink to an outside-cwd * target passes every lexical check. This resolves the REAL path of the * longest existing ancestor and re-appends the (possibly not-yet-existing) * tail, so symlinks anywhere in the existing prefix are followed to their * true location before containment and floor checks run. Works for * `write_file` targets that do not exist yet. */ export declare function resolveCanonical(cwd: string, target: string): string; /** A policy decision for one tool call. */ export type PermissionDecision = "allow" | "ask" | "deny"; /** * One rule. A rule matches a tool call when its (optional) `tool` equals the * tool name AND its (optional) `path` glob matches the call's resolved path * (relative to cwd). Omitting `tool` matches any tool; omitting `path` matches * any path (including pathless tools like `bash`). Last-match-wins. */ export interface PermissionRule { /** Exact tool name to match, or `undefined` for any tool. */ tool?: string; /** Glob matched against the path relative to cwd, or `undefined` for any. */ path?: string; /** Decision emitted when this rule matches. */ decision: PermissionDecision; /** Optional human label surfaced in the `permission_request` `rule` field. */ label?: string; } /** * The shipped rule set. Empty by design: the per-tool defaults * (read → allow, write/edit → ask, bash → ask) plus the terminal deny floor * already encode the P4b posture. Tests inject rules to prove last-match-wins * ordering; production leaves this as-is (scope §3: "Rules are a plain array * literal in this slice — no config file, no schema, no loader"). */ export declare const DEFAULT_RULES: readonly PermissionRule[]; /** * Evaluate a tool call's policy decision (scope §3 contract). * * `rules` defaults to {@link DEFAULT_RULES}; tests inject rules to prove * last-match-wins ordering. Pure — no I/O, no side effects. */ export declare function evaluate(toolName: string, args: unknown, cwd: string, rules?: readonly PermissionRule[]): PermissionDecision; /** The decision plus a short human reason for the `permission_request` event. */ export declare function evaluateWithReason(toolName: string, args: unknown, cwd: string, rules?: readonly PermissionRule[]): { decision: PermissionDecision; reason: string; }; /** * Tool-layer floor check: canonicalise `target` against `cwd` and return the * matched deny-floor pattern, or `undefined` when the path is clean. The * floor is a hard boundary — it applies even to tools that never consult * the policy engine (the read-only trio; E001 security finding 2). */ export declare function floorDeny(cwd: string, target: string): string | undefined; //# sourceMappingURL=permissions.d.ts.map