/** * Standardized denial messaging for tool calls blocked by Ory Permissions. * * All harness plugins format their deny reason / system message via these * helpers so the human-facing text is consistent: tells the user which * subject lacks which permission, and points them at their Ory Keto admin. * * For harnesses whose plugin contract has no native "block" signal * (currently OpenCode), throw `OryDenialError` from the pre-tool hook — * that aborts execution and surfaces `error.message` to the agent. */ import type { McpToolIdentifier } from "./mcp.js"; export interface DenialContext { tool: string; subjectId: string; namespace?: string; mcp?: McpToolIdentifier; /** * Shell decomposition (issue #76): the denied command word (`curl`) inside a * `Bash` call. When set, the message names the word and the * `ShellTool:#use` permission instead of the tool. */ shellWord?: string; /** * Every denied command word when more than one was blocked in the same shell * call. The message lists them all so the user can grant them in one pass; * {@link shellWord} remains the primary (first) word for back-compat. */ shellDeniedWords?: string[]; /** Set when a shell command was blocked because it could not be safely parsed. */ shellTooComplex?: boolean; /** Namespace for shell-command sub-tools (default `ShellTool`). */ shellNamespace?: string; /** * True when the deny was an *explicit block* (a `blocked` relation matched * the subject) rather than a missing grant. Changes the wording from "not * authorized" to "explicitly blocked" and points the user at removing the * block rather than granting a permission. Sourced from a decision's * `activityAttributes.blockReason === "explicit_block"`. */ blocked?: boolean; } /** * Activity attributes that mark a denial as a security alert. * * Spread these onto the existing `tool.block` activity attributes whenever an * actual deny comes back from Ory. The shape is stable so downstream * alerting rules can hook on `attributes.alert === true` and filter by * `attributes.severity` or `attributes.alertKind` without string matching. * * The `blocked` field distinguishes harnesses whose plugin contract can * stop the tool (claude-code/codex/gemini-cli exit-2, openclaw `{ block: * true }`) from those that can only advise (opencode throws and relies on * the harness to honor the exception). When `blocked === false` the agent * may still execute the tool, so the alert is the only enforcement signal. */ export interface AlertAttributes { alert: true; severity: "high"; alertKind: "permission_denied"; blocked: boolean; } export declare function alertAttributes(blocked: boolean): AlertAttributes; /** * Human-facing denial reason for an alert that the harness cannot enforce * (advisory mode). Prepends an explicit "ORY SECURITY ALERT TRIGGERED" * banner and notes that the activity event is the alert signal, so the agent * — and any human reading transcripts — can't miss that a security event * was emitted even though the tool was allowed to proceed. */ export declare function formatAlertMessage(ctx: DenialContext): string; /** * Short alert summary suitable for compact UI fields (Gemini's * systemMessage, etc.) when the denial cannot be enforced. */ export declare function formatAlertSummary(ctx: DenialContext): string; /** * Extract the shell-decomposition denial fields (issue #76) from a permission * decision's activity attributes, for spreading into a {@link DenialContext}. Reads * defensively (the outcome union includes non-shell members) so harness * handlers can write `...shellDenialFields(decision)` without narrowing. */ export declare function shellDenialFields(decision: { activityAttributes?: unknown; }): Pick; /** * Human-facing denial reason. Shown in agent UIs (Claude Code's `decision` * reason, Gemini CLI's systemMessage, OpenClaw's blockReason, OpenCode's * thrown error message). */ export declare function formatDenialMessage(ctx: DenialContext): string; /** * Short denial summary suitable for compact UI fields (e.g. Gemini's * systemMessage which appears inline in the agent's TUI). */ export declare function formatDenialSummary(ctx: DenialContext): string; /** * Thrown by plugins whose hook contract has no native block signal and * which fall back to aborting tool execution via an exception (OpenCode * pattern). Because the harness may or may not honor the throw, the * message uses `formatAlertMessage()` so the agent — and any human * reading transcripts — sees explicit "ORY SECURITY ALERT TRIGGERED" * text. The `code` field stays stable for callers/tests. */ export declare class OryDenialError extends Error { readonly code: "ory_permission_denied"; readonly tool: string; readonly subjectId: string; readonly namespace?: string; readonly mcp?: McpToolIdentifier; constructor(ctx: DenialContext); }