agent-tools-interface 0.7.8

Agent Tools Interface — secure CLI for AI agent tool execution
Documentation
# ATI Security Model

## Overview

ATI (Agent Tools Interface) is designed to give AI agents access to external tools **without exposing API keys**. It supports two execution modes with different security properties.

## Execution Modes

### Local Mode (default)

Keys are encrypted and provisioned into the sandbox. ATI decrypts them in process memory, makes API calls directly, and the keys never appear in environment variables or readable files.

**Trade-off:** Keys ARE in the sandbox (encrypted, memory-locked), but no external infrastructure needed.

### Proxy Mode (opt-in via `ATI_PROXY_URL`)

Zero credentials in the sandbox. ATI forwards tool calls to an external proxy server that holds the real API keys. The sandbox never sees any key material.

**Trade-off:** Stronger isolation, but requires running a proxy server and adds a network hop.

### Mode Auto-Detection

`ati run` checks the `ATI_PROXY_URL` environment variable:
- **Set** → proxy mode (forward to proxy server)
- **Not set** → local mode (use keyring)

The agent never needs to know which mode is active.

## Local Mode Security

### Key Delivery

1. **Orchestrator** generates a random 256-bit **session key** per sandbox
2. Orchestrator encrypts scoped API keys with session key → `keyring.enc`
3. Session key written to `/run/ati/.key` on tmpfs (mode 0400)
4. `keyring.enc` + tool manifests uploaded to sandbox

### First ATI Invocation

1. ATI reads `/run/ati/.key`, immediately `unlink()`s the file
2. ATI decrypts `keyring.enc` with AES-256-GCM
3. `mlock()` on decrypted memory (prevents swap-out)
4. Session key zeroed from stack
5. Keys exist **only** in ATI's locked heap

### Encryption Details

- **Algorithm**: AES-256-GCM (authenticated encryption with associated data)
- **Session key**: 256-bit random, unique per sandbox session
- **Nonce**: 96-bit random, prepended to ciphertext
- **Format**: `[12-byte nonce][ciphertext + 16-byte GCM tag]`
- **Rust crate**: `aes-gcm` v0.10

### Attack Surface (Local Mode)

| Attack Vector | Mitigation |
|--------------|------------|
| `env` / `printenv` | No secrets in environment variables |
| `cat /run/ati/.key` | File deleted after first read |
| `strings /usr/local/bin/ati` | Binary contains only encrypted blobs |
| `cat ~/.ati/keyring.enc` | Encrypted; session key is gone |
| `/proc/$(pgrep ati)/environ` | Clean — no secret env vars |
| `/proc/$(pgrep ati)/mem` | Requires `ptrace` — blocked by seccomp |
| `strace ati run ...` | Blocked by sandbox seccomp (no ptrace) |
| Memory dump / core dump | `madvise(DONTDUMP)` excludes secret pages |
| Swap file | `mlock()` prevents swap-out |
| Agent imports ATI internals | ATI is compiled Rust binary, not importable |

### Honest Limitations (Local Mode)

- If the sandbox has no seccomp profile, `ptrace` can read ATI's memory
- If the agent can run arbitrary code with sufficient privileges, it could theoretically extract keys from ATI's process memory during execution
- The session key exists in `/run/ati/.key` briefly before ATI reads it — a race condition is theoretically possible
- `mlock()` is best-effort on some kernels (may silently fail if RLIMIT_MEMLOCK is low)

## Proxy Mode Security

### How It Works

1. Orchestrator starts the proxy server: `ati proxy --port 8090 --ati-dir /path/to/ati`
2. Orchestrator sets `ATI_PROXY_URL` in the sandbox environment
3. ATI sends `POST /call` with `{tool_name, args}` to the proxy
4. Proxy server validates the request, injects API keys from its keyring, calls the upstream API
5. Proxy returns the result to ATI in the sandbox

The proxy server is built into the same `ati` binary — run `ati proxy` to start it. It loads manifests and keyring from its own ATI directory.

### What's in the Sandbox (Proxy Mode)

- ATI binary
- Tool manifests (`.toml` files) — tool definitions only, no secrets
- Scope config (`scopes.json`) — which tools are allowed
- `ATI_PROXY_URL` — URL of the proxy server

### What's NOT in the Sandbox (Proxy Mode)

- No `keyring.enc`
- No session key (`.key` file)
- No API keys in any form (encrypted or otherwise)

### Attack Surface (Proxy Mode)

| Attack Vector | Mitigation |
|--------------|------------|
| Any local key extraction | No keys exist in the sandbox |
| `ATI_PROXY_URL` leak | URL is not secret; proxy validates requests |
| Man-in-the-middle on proxy | Use HTTPS for proxy URL |
| Proxy server compromise | Same risk as any credential store (Vault, etc.) |
| Agent sends malicious requests | Proxy validates JWT scopes on every request |
| Agent enumerates tools/skills it shouldn't see | Discovery endpoints are scope-filtered |

### Honest Limitations (Proxy Mode)

- The proxy URL is visible via `printenv` — not a secret, but reveals infrastructure
- Proxy server is a single point of failure; if it's down, all tool calls fail
- Extra network hop adds latency (~10-50ms per call)
- Proxy server itself must be secured — it holds the real API keys

## Scope Enforcement

Scope behavior is intentionally split between production and development:

- **JWT configured**: ATI requires a valid `ATI_SESSION_TOKEN` and enforces scopes strictly.
- **No JWT configured**: ATI runs in unrestricted dev mode for local setup and testing.

When JWT validation is configured, scopes are enforced consistently across:

- `ati run` in local mode
- proxy `/call`
- proxy `/mcp` (`tools/list` and `tools/call`)
- proxy metadata and discovery endpoints (`/tools`, `/skills`, `/help`)

Other scope properties:

- Scopes are generated by the orchestrator and carried in the JWT `scope` claim
- Denied tools return clear authorization errors
- Scope expiry is enforced via JWT `exp`
- Wildcards such as `tool:github:*` are supported
- Help access requires the `help` scope when JWT auth is enabled

### Scope-Driven Discovery

Tool and skill listing is scope-filtered — callers only see what their JWT allows:

- `GET /tools` returns only tools the caller's scopes permit
- `GET /skills` returns only skills reachable from the caller's tool/provider/category scopes
- MCP `tools/list` returns only the scoped tool subset
- `ati assist` / `POST /help` builds its LLM context from visible tools and skills only

This prevents scope enumeration: an agent with `tool:web_search` cannot discover the existence of other tools, providers, or skills.

### Skill Scope Resolution

Skills are resolved transitively from tool scopes:

1. `skill:X` → load skill X directly
2. `tool:Y` → skills whose `tools` binding includes Y
3. Tool Y's provider → skills whose `providers` binding includes that provider
4. Provider's category → skills whose `categories` binding includes that category
5. Any loaded skill's `depends_on` → transitively load those dependencies

Legacy underscore JWT scopes (e.g. `tool:github_search_repos`) are normalized to their colon-namespaced equivalents (`tool:github:search_repos`) for scope checking, maintaining backward compatibility without weakening enforcement.

## SSRF Protection

ATI's HTTP executor optionally blocks Server-Side Request Forgery attacks via `ATI_SSRF_PROTECTION`:

| Mode | Behavior |
|------|----------|
| Not set (default) | No SSRF checking |
| `warn` | Log a warning but allow the request |
| `1` or `true` | Block requests to private/internal addresses |

When enabled, ATI resolves the target hostname and rejects requests to:

- Loopback addresses (`127.0.0.0/8`, `::1`)
- Private network ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `fc00::/7`)
- Link-local addresses (`169.254.0.0/16`, `fe80::/10`)
- Other IPv6 local ranges

DNS resolution is performed at check time to cover hosts that resolve to private IPs. This applies to hand-written HTTP tools; MCP and CLI tools are not currently in scope for SSRF checking.

## GCS Skill Registry Security

Two registry transports are supported via `ATI_SKILL_REGISTRY`:

**`ATI_SKILL_REGISTRY=gcs://bucket`** — direct GCS access:

- GCS credentials (`gcp_credentials` keyring key) are a full GCP service account JSON — treat them with the same care as any API key
- In local mode, credentials come from the ATI keyring (encrypted at rest)
- In proxy mode, credentials live only on the proxy server; agent sandboxes never see them
- Remote skill content is fetched on demand and cached in memory for the session; nothing is written to disk

**`ATI_SKILL_REGISTRY=proxy`** — fetch skills through the ATI proxy:

- Requires `ATI_PROXY_URL` to be set; `ATI_SESSION_TOKEN` provides auth
- The sandbox holds zero GCS credentials — skill content is served by the proxy's `/skillati/*` endpoints
- Useful for network-restricted sandboxes that cannot reach GCS directly but can reach the proxy
- Skill access is still JWT-scope-gated on the proxy side

Both transports: skill content is injected into LLM context via `ati assist` — operators should audit skills in their registry for prompt injection risks before deploying.

## Memory Security (Local Mode)

- Decrypted keys held in `HashMap<String, String>` with `Zeroize` on drop
- Raw JSON bytes also zeroized on drop
- `mlock()` prevents kernel from swapping secret pages
- `madvise(MADV_DONTDUMP)` excludes from core dumps
- All security functions degrade gracefully on non-Linux (warning, not error)

## Choosing a Mode

| Scenario | Recommended Mode |
|----------|-----------------|
| Standard sandbox deployment | **Local** — simpler, no extra infrastructure |
| High-security / regulated workloads | **Proxy** — zero key exposure |
| Air-gapped or offline sandboxes | **Local** — no network dependency |
| Multi-tenant with shared sandboxes | **Proxy** — strongest isolation |
| Development / testing | **Local** — easiest to set up |

## Verifying Your ATI Binary

Every release binary is cryptographically signed and auditable. Three levels of verification:

### GitHub Attestation (recommended)

Release binaries are attested by GitHub Actions using [artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds) (SLSA Level 3). This cryptographically proves the binary was built from a specific commit in the official repo by the official CI workflow.

```bash
gh attestation verify ./ati --repo Parcha-ai/ati
```

If this fails, **do not use the binary**. It was not built by our CI.

### SHA256 Checksum

Each release includes `.sha256` files alongside the binaries:

```bash
sha256sum -c ati-x86_64-unknown-linux-musl.sha256
```

### Inspecting Embedded Dependencies

ATI binaries are built with `cargo-auditable`, which embeds a compressed dependency manifest inside the binary itself. You can inspect what dependencies were compiled into any ATI binary:

```bash
cargo audit bin ./ati
```

If an attacker rebuilt the binary with injected dependencies, they show up here. This is forensic detection — the audit trail lives inside the binary, not in a separate file that could also be swapped.

## CI/CD Security

### Dependency Policy

ATI enforces a strict dependency policy via `cargo-deny` (checked on every push):

- **No unknown registries** — all crates must come from crates.io (blocks typosquatting via alt registries)
- **No git source dependencies** — prevents "point Cargo.toml at an evil fork" attacks
- **Known vulnerabilities denied** — checked against the RustSec advisory database
- **License allowlist** — only OSI-approved permissive licenses

### Pinned GitHub Actions

All CI/CD workflow actions are pinned to full commit SHAs, not mutable version tags. This prevents the class of attack demonstrated by CVE-2025-30066 (tj-actions/changed-files), where an attacker force-pushed malicious code to a version tag.