Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,14 @@ jobs:
- run: npm run lint
- run: npm test
- run: npm run build

docs:
name: Documentation Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -r docs/requirements.txt
- run: mkdocs build --strict
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ dist/
.env
.env.*
coverage/
site/
13 changes: 13 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: 2

build:
os: ubuntu-24.04
tools:
python: "3.12"

mkdocs:
configuration: mkdocs.yml

python:
install:
- requirements: docs/requirements.txt
48 changes: 24 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,27 +58,27 @@ const result = await generateText({

## Features

| Feature | Description | Requirement |
|---------|-------------|-------------|
| **Policy engine** | Rule-based allow/deny/require-approval with glob patterns, risk levels, priorities, and async conditions | #1 |
| **External policy backends** | Adapter interface for OPA/Rego, Cedar, or custom ABAC engines | #1 |
| **Decision records** | Structured audit output for every evaluation (matched rules, risk category, attributes, redactions) | #2 |
| **Dry-run / simulation** | Evaluate policies across recorded traces without executing tools | #3 |
| **Conversation-aware policies** | Policies can incorporate session risk score, prior failures, recent approvals | #4 |
| **Approve with edits** | Approval handler can patch arguments before execution | #5 |
| **Approval correlation** | Payload-hash tokens with TTL prevent mismatch between request and resolution | #6 |
| **Argument guards** | Zod schemas, allowlists, denylists, regex, PII scanning per field | #8 |
| **Injection detection** | Heuristic prompt-injection detector that can deny or downgrade to approval | #9 |
| **Output filtering** | Secrets stripping, PII redaction, custom filters on tool results | #10 |
| **Rate limiting** | Sliding-window rate limits + concurrency caps with reject or queue backpressure | #11 |
| **OpenTelemetry** | Opinionated spans for policy eval, approval wait, tool execution, redaction | #12 |
| **MCP drift detection** | SHA-256 schema fingerprinting, drift detection, actionable remediation | #15 |
| Feature | Description |
|---------|-------------|
| **Policy engine** | Rule-based allow/deny/require-approval with glob patterns, risk levels, priorities, and async conditions |
| **External policy backends** | Adapter interface for OPA/Rego, Cedar, or custom ABAC engines |
| **Decision records** | Structured audit output for every evaluation (matched rules, risk category, attributes, redactions) |
| **Dry-run / simulation** | Evaluate policies across recorded traces without executing tools |
| **Conversation-aware policies** | Policies can incorporate session risk score, prior failures, recent approvals |
| **Approve with edits** | Approval handler can patch arguments before execution |
| **Approval correlation** | Payload-hash tokens with TTL prevent mismatch between request and resolution |
| **Argument guards** | Zod schemas, allowlists, denylists, regex, PII scanning per field |
| **Injection detection** | Heuristic prompt-injection detector that can deny or downgrade to approval |
| **Output filtering** | Secrets stripping, PII redaction, custom filters on tool results |
| **Rate limiting** | Sliding-window rate limits + concurrency caps with reject or queue backpressure |
| **OpenTelemetry** | Opinionated spans for policy eval, approval wait, tool execution, redaction |
| **MCP drift detection** | SHA-256 schema fingerprinting, drift detection, actionable remediation |

## Architecture

```
┌─────────────────────────────────────────┐
createToolGuard(options) │
│ createToolGuard(options)
└──────────────┬──────────────────────────┘
┌──────────────────────┼──────────────────────┐
Expand All @@ -90,7 +90,7 @@ const result = await generateText({
┌─── Execution Pipeline ───┐ │
│ │ │
│ 1. Injection detection │ ┌──────────┴─────┐
│ 2. Argument validation │ │ PolicyBackend
│ 2. Argument validation │ │ PolicyBackend │
│ 3. Policy evaluation ◄──┼─────┤ (OPA, Cedar) │
│ 4. Approval flow │ └────────────────┘
│ 5. Rate limiting │
Expand Down Expand Up @@ -565,14 +565,14 @@ try {
} catch (err) {
if (err instanceof ToolGuardError) {
switch (err.code) {
case "policy-denied": // Policy rule blocked the call
case "approval-denied": // Human denied approval
case "no-approval-handler": // Approval required but no handler set
case "policy-denied": // Policy rule blocked the call
case "approval-denied": // Human denied approval
case "no-approval-handler": // Approval required but no handler set
case "arg-validation-failed": // Argument guard failed
case "injection-detected": // Prompt injection suspected
case "rate-limited": // Rate limit exceeded
case "output-blocked": // Output filter blocked the result
case "mcp-drift": // MCP schema drift detected
case "injection-detected": // Prompt injection suspected
case "rate-limited": // Rate limit exceeded
case "output-blocked": // Output filter blocked the result
case "mcp-drift": // MCP schema drift detected
}
console.log(err.toolName); // Which tool
console.log(err.decision); // Full DecisionRecord (if available)
Expand Down
156 changes: 156 additions & 0 deletions docs/api/approval.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Approval — `ai-tool-guard/approval`

The approval module manages the lifecycle of human-in-the-loop approval requests.
It creates correlation tokens, enforces TTL expiry, supports argument patching
("approve with edits"), and delegates the actual approval decision to a
caller-supplied handler.

```ts
import { ApprovalManager } from "ai-tool-guard/approval";
import type { ApprovalFlowResult } from "ai-tool-guard/approval";
```

The related types `ApprovalToken`, `ApprovalResolution`, and `ApprovalHandler` are
defined in `ai-tool-guard/types` and re-exported from the root path.

```ts
import type {
ApprovalToken,
ApprovalResolution,
ApprovalHandler,
} from "ai-tool-guard";
```

---

## Classes

### `ApprovalManager`

Manages the full lifecycle of approval tokens: creation, handler invocation, TTL
enforcement, and resolution.

#### Constructor

```ts
new ApprovalManager(handler: ApprovalHandler, defaultTtlMs?: number)
```

| Parameter | Type | Required | Description |
|---|---|---|---|
| `handler` | `ApprovalHandler` | Yes | Async callback invoked with the approval token; must return an `ApprovalResolution` |
| `defaultTtlMs` | `number` | No | Token time-to-live in milliseconds. Default: `300000` (5 minutes) |

#### Methods

##### `requestApproval`

```ts
async requestApproval(ctx: PolicyContext): Promise<ApprovalFlowResult>
```

Create an approval token for a tool call and invoke the handler. The token
includes a SHA-256 hash of the call payload for correlation. Tokens are
automatically removed from the pending set after the handler resolves.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `ctx` | `PolicyContext` | Yes | Policy context of the tool call requiring approval |

**Returns** `Promise<ApprovalFlowResult>`

The result indicates whether the call was approved, the final arguments to use
(original or patched), and optional metadata from the approver.

##### `getPendingTokens`

```ts
getPendingTokens(): ReadonlyArray<ApprovalToken>
```

Return a read-only snapshot of currently pending approval tokens. Useful for
rendering an approval UI.

**Returns** `ReadonlyArray<ApprovalToken>`

---

## Interfaces

### `ApprovalFlowResult`

The complete result of a single approval flow cycle returned by
`requestApproval()`.

| Field | Type | Required | Description |
|---|---|---|---|
| `approved` | `boolean` | Yes | Whether the tool call was approved |
| `tokenId` | `string` | Yes | The approval token ID for correlation and auditing |
| `args` | `Record<string, unknown>` | Yes | The final arguments to pass to the tool (original or patched by the approver) |
| `patchedFields` | `string[]` | No | Names of argument fields that were modified by the approver |
| `approvedBy` | `string` | No | Identity of the approver, if provided by the handler |
| `reason` | `string` | No | Human-readable reason for denial, if the call was not approved |
| `error` | `string` | No | Error message if the approval flow itself failed (e.g., token not found or expired) |

---

## Types (from `ai-tool-guard`)

### `ApprovalToken`

Correlation token sent to the `ApprovalHandler`. Contains a snapshot of the
original arguments and a payload hash for tamper detection.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | Yes | Randomly generated unique token ID |
| `payloadHash` | `string` | Yes | SHA-256 hash of the canonical `{ toolName, args }` payload |
| `toolName` | `string` | Yes | Name of the tool awaiting approval |
| `originalArgs` | `Record<string, unknown>` | Yes | Snapshot of the tool arguments at request time |
| `createdAt` | `string` | Yes | ISO-8601 timestamp of token creation |
| `ttlMs` | `number` | No | Token time-to-live in milliseconds |

---

### `ApprovalResolution`

The response returned by the `ApprovalHandler` callback.

| Field | Type | Required | Description |
|---|---|---|---|
| `approved` | `boolean` | Yes | Whether the tool call is approved |
| `patchedArgs` | `Record<string, unknown>` | No | Partial argument overrides; merged with `originalArgs` when provided |
| `approvedBy` | `string` | No | Identity of the approver for audit purposes |
| `reason` | `string` | No | Reason for denial when `approved` is `false` |

---

### `ApprovalHandler`

```ts
type ApprovalHandler = (token: ApprovalToken) => Promise<ApprovalResolution>;
```

Callback type the consumer implements to handle approval requests. The handler
receives the token, presents it to a human approver (or automated system), and
resolves with the decision.

**Example**

```ts
const handler: ApprovalHandler = async (token) => {
const decision = await showApprovalModal({
toolName: token.toolName,
args: token.originalArgs,
});

return {
approved: decision.confirmed,
approvedBy: decision.userId,
patchedArgs: decision.edits,
reason: decision.reason,
};
};

const guard = createToolGuard({ onApprovalRequired: handler });
```
Loading