# Execution modes

> Dev (plaintext credentials), local (AES-256-GCM keyring + one-shot session key), and proxy (`ATI_PROXY_URL`) modes: credential placement, auto-detection in `ati run`, and threat-model trade-offs.

- Repository: Parcha-ai/ati
- GitHub: https://github.com/Parcha-ai/ati
- Human docs: https://grok-wiki.com/public/docs/parcha-ai-ati-a9d4398f11fa
- Complete Markdown: https://grok-wiki.com/public/docs/parcha-ai-ati-a9d4398f11fa/llms-full.txt

## Source Files

- `docs/SECURITY.md`
- `src/cli/call.rs`
- `src/proxy/client.rs`
- `src/core/keyring.rs`
- `src/security/memory.rs`
- `src/security/sealed_file.rs`

---

---
title: "Execution modes"
description: "Dev (plaintext credentials), local (AES-256-GCM keyring + one-shot session key), and proxy (`ATI_PROXY_URL`) modes: credential placement, auto-detection in `ati run`, and threat-model trade-offs."
---

`ati run` routes every tool call through one of two runtime paths: **local** (decrypt credentials in-process and call upstream APIs directly) or **proxy** (forward `POST /call` to an external `ati proxy` that holds keys). Credential storage is a separate axis—plaintext `~/.ati/credentials`, encrypted `keyring.enc`, or no keys in the sandbox at all. The same CLI command and manifest layout work in all combinations; only environment variables and on-disk artifacts change.

## Mode matrix

| Dimension | Dev credentials | Local (sandbox) | Proxy (`ATI_PROXY_URL`) |
|-----------|-----------------|-----------------|-------------------------|
| **Where API keys live** | `~/.ati/credentials` (JSON, mode `0600`) or `ATI_KEY_*` env on the proxy | `~/.ati/keyring.enc` + session key, or same `credentials` fallback | Proxy host only (`keyring.enc`, `credentials`, or `ATI_KEY_*` via `--env-keys`) |
| **Session decrypt key** | N/A (plaintext) | One-shot `/run/ati/.key` (deleted after read) or persistent `~/.ati/.keyring-key` | Not in agent sandbox |
| **Who calls upstream** | Local `ati` process | Local `ati` process | Proxy server |
| **Typical use** | Laptop setup, `ati key set` | Orchestrator-provisioned sandboxes | Untrusted / multi-tenant sandboxes |
| **Scope enforcement** | Unrestricted if JWT keys unset locally; strict if `ATI_JWT_*` configured | Same as dev for local CLI | JWT on proxy when configured; dev passthrough if not |

<Note>
**Dev** names two related behaviors: (1) plaintext credential files via `ati key set`, and (2) **unrestricted scopes** when JWT validation is not configured (`ScopeConfig::unrestricted()`). Production sandboxes should use encrypted keyrings or proxy mode plus JWT issuance—not plaintext files with wildcard scopes.
</Note>

## Auto-detection in `ati run`

Dispatch is env-driven. Agents do not pass a `--mode` flag.

```mermaid
sequenceDiagram
    participant Agent
    participant ATI as ati (cli/call.rs)
    participant Keyring as load_keyring
    participant Upstream as HTTP / MCP / CLI
    participant Proxy as ati proxy

    Agent->>ATI: ati run tool_name [--arg val]
    alt file_manager:* tool
        ATI->>ATI: execute_file_manager (local or proxy branch)
    else ATI_PROXY_URL set
        ATI->>Proxy: POST /call + Bearer JWT
        Proxy->>Upstream: inject auth, enforce scopes
        Proxy-->>ATI: { result, error }
    else local path
        ATI->>Keyring: cascade keyring.enc → credentials → empty
        ATI->>Upstream: generic / mcp / cli handlers
    end
    ATI-->>Agent: formatted stdout
```

| Check | Result |
|-------|--------|
| Tool name is `file_manager:download` or `file_manager:upload` | `execute_file_manager` — client-side file I/O, then local keyring or proxy `/call` |
| `ATI_PROXY_URL` is set (any non-empty value) | `execute_via_proxy` → `proxy/client.rs` `POST {url}/call` |
| Otherwise | `execute_local` → manifests from `ATI_DIR` or `~/.ati/manifests`, keyring cascade, direct upstream |

`file_manager` tools follow the same proxy/local split: if `ATI_PROXY_URL` is set, payload goes to the proxy; otherwise the local keyring is loaded.

## Dev credentials (plaintext)

Development stores secrets in a JSON object at `{ATI_DIR}/credentials` (default `~/.ati/credentials`).

<Steps>
<Step title="Store a key">
```bash
ati key set github_token ghp_xxxxxxxx
ati key list    # values masked in output
```
`ati key set` writes pretty-printed JSON and sets Unix permissions to `0600`.
</Step>
<Step title="Run a tool">
```bash
ati run github:search_repositories --query "ati"
```
With no `ATI_PROXY_URL` and no decryptable `keyring.enc`, `load_keyring` falls through to `Keyring::load_credentials`.
</Step>
</Steps>

Additional dev inputs:

| Mechanism | Behavior |
|-----------|----------|
| `@file:/path/to/secret` in `credentials` | Value replaced with trimmed file contents at load time (Kubernetes/Docker secret mounts) |
| `ATI_KEY_<NAME>` env vars | Proxy startup with `ati proxy --env-keys` scans env, lowercases the suffix into keyring names (e.g. `ATI_KEY_FINNHUB_API_KEY` → `finnhub_api_key`) |

Plaintext credentials never use `mlock` or the sealed-key path; they are appropriate only where disk confidentiality matches your threat model.

## Local mode (encrypted keyring)

Production sandboxes provision an **AES-256-GCM** blob plus a **one-shot session key**. Format: `[12-byte nonce][ciphertext + 16-byte GCM tag]`. Rust uses the `aes-gcm` crate; decrypted JSON becomes a `HashMap<String, String>` in a `Keyring` struct.

### Key delivery lifecycle

```text
Orchestrator                         Sandbox (ATI_DIR)
────────────                         ─────────────────
random 256-bit session key    →      /run/ati/.key  (tmpfs, mode 0400)
encrypt keys → keyring.enc    →      ~/.ati/keyring.enc (or ATI_DIR)
manifests/*.toml              →      ~/.ati/manifests/

First ati invocation:
  1. open /run/ati/.key → unlink path → read fd (TOCTOU-safe)
  2. decrypt keyring.enc
  3. mlock + MADV_DONTDUMP on decrypted buffer
  4. session key zeroed; keys only in locked heap until Drop
```

<ParamField body="ATI_KEY_FILE" type="string">
Overrides the default session key path `/run/ati/.key` (tests and custom supervisors).
</ParamField>

### Persistent keyring (proxy host / long-lived VM)

When `/run/ati/.key` is absent, `Keyring::load_local` reads `~/.ati/.keyring-key` (base64-encoded 32-byte key), decrypts `keyring.enc`, and sets `ephemeral: false`. The key file is **not** deleted—used by `ati proxy` and edge deployments that SIGHUP-reload the keyring.

### Memory protections

On Linux, after decryption:

- `mlock()` — best-effort; warns if `RLIMIT_MEMLOCK` blocks swap exclusion
- `madvise(MADV_DONTDUMP)` — exclude from core dumps
- `Zeroize` on `Drop` for all key strings and raw JSON bytes
- `munlock()` using lengths captured before zeroization

Non-Linux builds log and continue without failing the call.

### Local keyring load cascade

`load_keyring(ati_dir)` in `cli/call.rs` tries, in order:

1. `keyring.enc` + sealed key from `read_and_delete_key()` (`security/sealed_file.rs`)
2. `keyring.enc` + `ati_dir/.keyring-key` (persistent)
3. `credentials` plaintext JSON
4. `Keyring::empty()` — auth-required tools fail until keys are provisioned

Tracing labels these paths as `keyring.enc (sealed key)`, `keyring.enc (persistent key)`, `credentials (plaintext)`, or empty.

## Proxy mode (`ATI_PROXY_URL`)

Set `ATI_PROXY_URL` (for example `http://proxy-host:8090`) in the sandbox. The agent process holds **no** `keyring.enc`, session key, or API secrets—only manifests, optional scope metadata, and the proxy base URL.

### Sandbox → proxy request

| Field | Source |
|-------|--------|
| URL | `POST {ATI_PROXY_URL}/call` |
| Body | `{ "tool_name", "args", "raw_args"? }` — `raw_args` preserves CLI positional tokens |
| `Authorization` | `Bearer` JWT from `ATI_SESSION_TOKEN`, `<NAME>_FILE`, or default file `/run/ati/session_token` |
| `X-Ati-Upstream-Url` | Optional per-manifest `mcp_url_env` override (proxy validates against allowlist) |

Per-provider audience separation: if the manifest sets `auth_session_token_env` (for example `PARCHA_TOOLS_SESSION_TOKEN`), the client reads that env (with the same file fallback chain) and falls back to `ATI_SESSION_TOKEN` if unset—avoiding silent unauthenticated requests.

Proxy timeout: **120 seconds** per HTTP client (`PROXY_TIMEOUT_SECS`).

### What stays out of the sandbox

| Present | Absent |
|---------|--------|
| `ati` binary, `manifests/*.toml` | `keyring.enc` |
| `ATI_PROXY_URL`, `ATI_SESSION_TOKEN` (or file) | `/run/ati/.key` |
| Scope-filtered tool visibility via JWT | Raw API keys |

<Warning>
`ATI_PROXY_URL` is visible in the environment—it is not a secret. Security relies on JWT scope validation (when enabled), network placement, and TLS to the proxy—not hiding the URL.
</Warning>

### Proxy server credential loading

`ati proxy` uses the same cascade as local CLI, plus:

| Flag / env | Effect |
|------------|--------|
| `--ati-dir PATH` | Manifests and key material root (default `ATI_DIR` / `~/.ati`) |
| `--env-keys` | `Keyring::from_env()` only—no disk keyring |
| SIGHUP | `reload_keyring` hot-swaps decrypted keys and sig-verify secrets |

Startup logs `keyring.enc (sealed key)`, `keyring.enc (persistent key)`, `credentials (plaintext)`, or `env-vars (ATI_KEY_*)`.

## Scopes and JWT across modes

Scope behavior splits on whether JWT validation is configured—not on proxy vs local alone.

| JWT config | Local CLI (`load_local_scopes_from_env`) | Proxy (`auth_middleware`) |
|------------|------------------------------------------|---------------------------|
| **Not configured** | Unrestricted dev scopes | Unauthenticated requests allowed (unless `Ati-Key` header present) |
| **Configured** (`ATI_JWT_PUBLIC_KEY` or `ATI_JWT_SECRET`) | Requires valid `ATI_SESSION_TOKEN`; scopes from JWT `scope` claim | Requires `Authorization: Bearer`; 401 if missing/invalid |

When JWT is enabled, enforcement applies consistently to `ati run`, `POST /call`, `POST /mcp`, `/tools`, `/skills`, `/help`, and SkillATI routes. Discovery endpoints are scope-filtered so agents cannot enumerate tools outside their grant.

## Threat-model trade-offs

| Attack / concern | Dev (plaintext) | Local (encrypted) | Proxy |
|------------------|-----------------|-------------------|-------|
| `cat ~/.ati/credentials` | **Exposes secrets** | N/A if only keyring used | N/A in sandbox |
| `cat ~/.ati/keyring.enc` | N/A | Ciphertext only; session key gone after first read | N/A in sandbox |
| `cat /run/ati/.key` | N/A | File unlinked on read; brief race window | N/A |
| Secrets in `env` / `printenv` | Possible if operator exports keys | ATI avoids putting API keys in env | Only proxy URL + JWT in sandbox |
| `ptrace` / memory dump | Full process access if sandbox allows | Mitigated: mlock, DONTDUMP, seccomp in hardened sandboxes; not cryptographic isolation | No keys in agent process |
| Proxy compromise | N/A | N/A | Central credential store—same class as Vault |
| Network MITM to proxy | N/A | N/A | Use HTTPS on `ATI_PROXY_URL` |
| Operational complexity | Lowest | Medium (orchestrator must provision key + blob) | Highest (run proxy, issue JWTs, monitor health) |

<Info>
Honest local-mode limitations (from `docs/SECURITY.md`): without seccomp, `ptrace` can read ATI memory during execution; `mlock` may silently fail on some kernels; orchestrator mis-provisioning leaves tools failing with empty keyring rather than leaking keys.
</Info>

## Choosing a configuration

| Scenario | Recommendation |
|----------|----------------|
| Local laptop, quick provider tests | Dev `credentials` + no JWT |
| Standard agent sandbox (trusted VM) | Local encrypted keyring + JWT scopes |
| Regulated / multi-tenant / untrusted code | Proxy mode + JWT + TLS |
| Air-gapped sandbox | Local (no proxy dependency) |
| Network-restricted sandbox, GCS skills | `ATI_SKILL_REGISTRY=proxy` with `ATI_PROXY_URL` (skills via `/skillati/*`, zero GCS creds in sandbox) |

Switching runtime path is one variable—commands stay identical:

```bash
# Local
ati run finnhub:quote --symbol AAPL

# Proxy (same invocation)
export ATI_PROXY_URL=http://127.0.0.1:8090
export ATI_SESSION_TOKEN="$(ati token issue --sub agent-1 --scope 'tool:finnhub:*' --expires 3h)"
ati run finnhub:quote --symbol AAPL
```

## Verification signals

| Signal | Healthy state |
|--------|---------------|
| `RUST_LOG=debug ati run ...` | `mode: local` or `mode: proxy` plus keyring source line |
| Local sealed provisioning | `/run/ati/.key` absent after first `ati run`; tool auth succeeds |
| Proxy sandbox | `env \| grep ATI_PROXY_URL` set; no `keyring.enc` in sandbox tree |
| JWT enabled locally | Missing `ATI_SESSION_TOKEN` → clear error requiring token |
| Proxy health | `curl $ATI_PROXY_URL/health` reports tool counts and JWT status |

## Related pages

<CardGroup>
<Card title="Overview" href="/overview">
Runtime surfaces (`ati run`, catalog, proxy) and the shortest init-to-call path.
</Card>
<Card title="Configure JWT and keys" href="/configure-jwt-and-keys">
`ati key`, `ati token issue`, `ati init --proxy`, and session token file rotation.
</Card>
<Card title="Deploy proxy server" href="/deploy-proxy-server">
`ati proxy` flags, binding, `--env-keys`, persistence, and production systemd patterns.
</Card>
<Card title="Environment variables" href="/environment-variables">
`ATI_PROXY_URL`, `ATI_SESSION_TOKEN`, `ATI_DIR`, `ATI_KEY_FILE`, and JWT-related env contract.
</Card>
<Card title="Security and production" href="/security-and-production">
Hardening checklist, SSRF, sig-verify, audit, and edge keyring rotation.
</Card>
<Card title="Agent harness sandbox" href="/agent-harness-sandbox">
Orchestrator provisioning of scoped env, keyring blobs, and proxy-mode agent loops.
</Card>
</CardGroup>
