Iddio Docs
Everything you need to deploy, configure, and operate Iddio as a security gateway for your AI agents. From first install to production policy management.
Get Iddio running in under five minutes. This guide covers installation, daemon setup, policy configuration, and your first proxied command.
1. Install
There are two ways to run Iddio locally: a desktop app with a GUI, or the CLI.
Desktop app (macOS)
A native macOS app with a visual policy editor, approval dialogs, and observe mode.
brew install --cask leonardaustin/tap/iddio-desktop
Or grab the latest .dmg from GitHub Releases.
The desktop app manages the daemon for you. Once it is open and the daemon is running, skip to Step 3 to configure your policy.
CLI (macOS and Linux)
A single binary you run in a terminal. Lightweight and scriptable.
brew install leonardaustin/tap/iddio
Linux users without Homebrew can download a .deb or .AppImage from GitHub Releases.
2. Install and start the daemon
The CLI installs iddio-daemon as an OS service (launchd on macOS, systemd on Linux) and seeds the default policy.
iddio install
Verify the daemon is running:
iddio status
# daemon up
# pid 12345
# uptime 4s
# build_version v26.0603.1148
# schema_version 2
# proxy_mode enforce
# policy loaded
# proxy_listen 127.0.0.1:9443
iddio install is idempotent — re-running it on an already-installed host
re-renders the service unit and is a no-op otherwise.
3. Route your shell through the proxy
Print and source a shell snippet that sets HTTPS_PROXY and the matching CA-bundle env vars:
# bash / zsh — add to ~/.bashrc or ~/.zshrc
eval "$(iddio shell-init)"
# fish — add to ~/.config/fish/config.fish
iddio shell-init fish | source
The snippet is gated on iddio status --quiet, so a stopped daemon never leaves your CLIs pointing at a closed listener.
The proxy intercepts HTTPS traffic via a local MITM CA it generates at install
time. On macOS, iddio install trusts this CA in the system keychain
automatically. On Linux you may need to add ~/.iddio/ca.crt to your system
trust store. Run iddio ca info to check trust state.
4. Review the policy
The default policy ships with rules for managed Kubernetes clusters (EKS, GKE, AKS), Google Cloud, and Fly.io. Open it to understand what is configured:
iddio policy show
Edit it interactively:
iddio policy edit
Or edit the file directly — changes hot-reload within a second:
$EDITOR ~/.iddio/policy.yaml
A minimal policy that allows read operations and escalates writes for a local cluster:
kubectl:
inspect:
- "127.0.0.1:*" # local cluster endpoint
- "192.168.*.1:*"
default: escalate
groups:
read:
tiers: [observe]
mutate:
tiers: [modify]
delete:
tiers: [sensitive]
admin:
tiers: [break-glass]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, delete, admin]
See Config Reference for the full policy schema.
5. Run a proxied command
With the shell snippet sourced, run any supported CLI tool normally. Read operations pass through without any prompt. Write and delete operations block until you approve them.
# Read — passes through instantly (observe tier)
kubectl get pods -n payments
# Write — blocks for approval (modify tier)
kubectl apply -f deployment.yaml
When a request is held for approval, you will see it listed in the desktop app or via the CLI:
iddio approval list
# ID TIER HOST METHOD PATH
# a1b2c3d4 modify my-cluster.example.com POST /apis/apps/v1/deployments
Approve or deny:
iddio approval allow a1b2c3d4
iddio approval deny a1b2c3d4 --reason "not during business hours"
6. Check the audit log
Every request is appended to ~/.iddio/audit.jsonl. View recent entries:
iddio audit show
Follow new entries in real time:
iddio audit tail
Something wrong? See Troubleshooting.
Every request that flows through Iddio passes through a pipeline in the daemon's proxy handler. The proxy operates as a transparent HTTPS MITM — agents point their CLIs at HTTPS_PROXY=http://127.0.0.1:9443 and the daemon intercepts, classifies, and enforces policy before forwarding traffic to the real upstream.
The daemon generates its own ECDSA CA and per-host leaf certificates at
install time. When a CLI connects, the proxy terminates TLS, presents a leaf
cert signed by its CA, then opens a fresh TLS connection to the real
upstream host. Agents see a valid certificate chain because the daemon's CA
is trusted in the system keychain. Hosts on the user pinned list (via iddio pin) are spliced through without MITM. Hosts that match no policy inspect
route are a transparent tunnel: spliced through untouched, with no audit
row or record of any kind.
The HTTP method, host, and path are dispatched to a protocol-specific
classifier. Registered classifiers cover kubectl, gcloud, aws (AWS CLI),
github (gh), terraform/tofu, helm, cloudflare/wrangler, and fly/flyctl.
Unrecognised hosts that match the generic: inspect list are tiered from
the HTTP method alone. The result is one of four risk tiers: observe,
modify, sensitive, or break-glass. Unknown or unclassifiable requests
fail closed (escalate or deny).
The classification and request context are evaluated against
~/.iddio/policy.yaml. Policy is protocol-keyed (kubectl:, gcloud:,
fly:, generic:), with scope-sugar blocks (namespaces:, projects:,
apps:) that are first-match-wins. Each block resolves to an action:
allow, escalate, or deny. If no rule matches, the protocol's
default: applies (typically escalate). Policy is hot-reloaded via
fsnotify — edits take effect within a second without dropping in-flight
requests.
An allow decision forwards the request immediately. A deny returns an
error to the agent. An escalate decision blocks the agent's connection and
queues an approval request in the daemon's in-process broker. The desktop
app and CLI both surface pending approvals (iddio approval list). A human
approves (iddio approval allow <id>) or denies (iddio approval deny <id>) within the configured timeout (default ~30 s). The agent's HTTP
connection is held open throughout — from the agent's perspective, the
upstream is simply slow to respond.
Every request is appended to ~/.iddio/audit.jsonl as a best-effort
append-only JSON line. Each row carries a timestamp_ms, an op field, an
opaque random hash used only for pagination and deduplication, and a
fields map with request context. The log is not hash-chained and carries
no tamper-detection guarantees. View it with iddio audit show or iddio audit tail.
Approval flow
When a request is escalated, the daemon's in-process broker holds the agent's connection open until a human responds. There is no y/N terminal prompt, no webhook, and no Slack integration — approvals are handled through the desktop app or the iddio approval CLI commands.
Agent CLI iddio daemon Operator (desktop / CLI)
│ │ │
├── HTTPS request ─────►│ │
│ ├── classify → modify │
│ ├── policy → escalate │
│ (connection held) ├── approval queued ───────►│
│ │ iddio approval list
│ │ iddio approval allow <id>
│ │◄── approved ─────────────┤
│ ├── forward to upstream │
│◄── response ─────────┤ │
│ ├── audit row written │
Proxy modes
The daemon operates in one of three modes, switchable at runtime via iddio set-proxy-mode:
| Mode | Behaviour |
|---|---|
off / passthrough | Traffic is spliced through without classification or enforcement. |
observe | Dry-run: classify policy-matched requests and record would-be decisions to the observe log; never block or escalate; the audit log is not written. |
enforce | Full policy evaluation; block on deny, hold on escalate. |
Component architecture
| Component | Location | Purpose |
|---|---|---|
| MITM Proxy | daemon/internal/proxy/proxy.go | TLS intercept, cert generation, request routing |
| Classifier Registry | daemon/internal/classifier/registry.go | Protocol dispatch to per-CLI classifiers |
| Policy Engine | daemon/internal/policy/ | YAML rule evaluation, hot-reload via fsnotify |
| Approval Broker | daemon/internal/approvals/broker.go | In-process hold/resume with timeout |
| Audit Logger | daemon/internal/audit/ | Best-effort append-only JSONL writer |
| IPC Server | daemon/internal/runtime/ | Unix socket server; CLI and desktop connect here |
Iddio ships as a single iddio binary. All state lives in ~/.iddio/. Every command that talks to the running daemon dials ~/.iddio/iddio.sock.
iddio install
Register iddio-daemon with the OS supervisor (launchd on macOS, systemd on Linux), copy the daemon binary to ~/.iddio/bin/, and seed a default policy.yaml.
iddio install [flags]
# Flags:
# --system Install as a system service (Linux + sudo only)
# --no-service Place binary + seed state only; skip supervisor registration
# --no-trust-ca Skip the macOS CA keychain trust step
# --daemon-path Override the source iddio-daemon binary to copy
# --wait duration How long to wait for the daemon socket after install (default 10s)
# --print-unit Render the service unit / plist to stdout and exit (read-only)
Idempotent — safe to re-run after an upgrade.
iddio status
Show daemon liveness, version, proxy mode, and the locations of its socket and log files.
iddio status [--json] [--quiet]
# Flags:
# --json Emit raw JSON instead of the table view
# --quiet Print nothing; signal daemon health via exit code only (0=up, 1=down)
iddio shell-init
Print a shell-rc snippet that exports HTTPS_PROXY and CA-bundle env vars so CLIs route through the local iddio proxy. The snippet is gated on iddio status --quiet.
iddio shell-init [bash|zsh|fish] [--listen-addr <addr>]
# Add to ~/.bashrc or ~/.zshrc:
eval "$(iddio shell-init)"
# Add to ~/.config/fish/config.fish:
iddio shell-init fish | source
iddio policy
Inspect and edit the daemon's active policy.
iddio policy show # Print the active policy.yaml
iddio policy edit # Open in $EDITOR and persist on save
iddio policy reload # Force re-read from disk
iddio policy validate --path <file> # Validate a candidate file without persisting
iddio approval
Inspect and respond to pending escalations. Escalated requests block the agent's connection until responded to or timed out.
iddio approval list [--json] # List pending approvals
iddio approval allow <id> [--reason text] # Approve a pending request
iddio approval deny <id> [--reason text] # Deny a pending request
iddio approval watch # Stream approval events (follow mode)
iddio audit
Query the audit log (~/.iddio/audit.jsonl).
# Show recent entries (server-side filtered)
iddio audit show [flags]
# --op string Filter by op (e.g. mitm, pin_splice, block)
# --host string Filter by exact host
# --last uint Show at most N rows (default 50)
# --since string Only rows newer than this duration (e.g. 5m, 24h)
# --json Emit JSON
# Stream rows from disk (no daemon required)
iddio audit tail [flags]
# --lines N Print the last N matching rows then exit (0 = follow mode)
# --filter key=val AND-filter rows by field value (repeat for multiple)
# --json Emit NDJSON instead of human-readable rows
iddio observe
Manage the daemon's observe-mode session. In observe mode the daemon classifies requests to hosts matching the policy's inspect routes and records what policy would do in enforce mode — without blocking anything, and without writing to the audit log (records go to observe.jsonl). To observe traffic to every domain, add "*" under the policy's generic.inspect block.
iddio observe start [--duration 30m] # Start a bounded observe session
iddio observe stop # Stop the active session
iddio observe status [--json] # Show session status
iddio observe show [flags] # List recent observe records
# --limit uint Max records to return (default 100)
# --offset uint Pagination offset
# --host string Filter by exact host
# --op string Filter by would-be-decision (allow|escalate|deny)
# --json Render as JSON
iddio observe clear --force # Truncate the observe log
iddio set-proxy-mode / get-proxy-mode
Change or query the daemon's proxy mode at runtime.
iddio set-proxy-mode <off|passthrough|observe|enforce>
iddio get-proxy-mode
iddio classify
Synthesise a request and print the daemon's classification.
iddio classify http <method> <url>
iddio classify cli <argv>...
# Examples:
iddio classify http GET https://my-cluster.eks.amazonaws.com/api/v1/pods
iddio classify cli kubectl delete pod my-pod -n production
iddio decide
Run the classifier and policy engine together and print the resulting decision.
iddio decide <method> <host> <path> [--json]
iddio inspect
View or edit the MITM inspect list per protocol without editing policy.yaml directly.
iddio inspect summary [--json] # Print per-protocol inspect entries
iddio inspect add <protocol> <entry> # Add a host / suffix / CIDR
iddio inspect remove <protocol> <entry> # Remove an entry
iddio pin / unpin / pinned
Manage the user pinned (never-MITM) list. Pinned hosts are spliced through without TLS interception.
iddio pin <host> [--note text] # Add a host to the never-MITM list
iddio unpin <host> # Remove a host from the list
iddio pinned # List all pinned hosts
iddio detector
Inspect and manage the daemon's auto-detected pin cache. Hosts where the upstream cert fails iddio's verification are auto-pinned temporarily.
iddio detector list [--json] # List active detector pins
iddio detector clear # Wipe the detector pin cache
iddio detector promote <host> # Move a detector pin to the permanent user list
iddio events
Stream daemon-emitted events.
iddio events [--follow] [--json]
# Common event tags: proxy_mode_changed, policy_reloaded, mitm, splice,
# block, allow, escalate, approval_requested, approval_replied,
# approval_timed_out, host_pinned, host_unpinned
iddio ca
Inspect the daemon's dev CA.
iddio ca info [--json] # Show path, fingerprint, validity, and trust state
iddio settings
Read and mutate persisted daemon settings (settings.yaml).
iddio settings get # Print current settings
iddio settings set <key=value> # Mutate one setting
iddio settings reload # Confirm hot-reload and print current proxy_listen
iddio doctor
Run a read-only diagnostic over the iddio install. Reports state-dir permissions, the supervisor unit, and the daemon process. Use iddio repair to fix anything doctor flags.
iddio doctor [--system] [--json]
iddio repair
Diagnose and fix a damaged iddio install.
iddio repair [--system] [--dry-run]
iddio repair --trust-ca # macOS: (re-)trust the dev CA in the admin domain
sudo iddio repair --untrust-ca # macOS: remove dev CA certs and orphaned trust settings
iddio uninstall
Remove the iddio-daemon service, binary, and state directory. The audit log is preserved by default.
iddio uninstall [--system] [--keep-data] [--yes]
iddio upgrade
Download and apply the latest iddio-daemon release from GitHub.
iddio upgrade [--check] [--dry-run] [--system]
iddio update
Manage the daemon-owned self-update state machine.
iddio update check [--force] # Force an immediate check against GitHub Releases
iddio update status [--json] # Show current update state-machine snapshot
iddio update apply # Apply the staged update and restart the daemon
iddio gateway
Start the iddio daemon in foreground (useful for testing without a supervisor).
iddio gateway [--start]
Under a normal iddio install setup the daemon is managed by launchd or
systemd. Use iddio gateway only for development or debugging.
Iddio classifies every intercepted request into one of four risk tiers. The classification happens in real-time with sub-millisecond latency and drives the policy engine's allow / escalate / deny decision.
OBSERVE
Read-only operations.
HTTP Methods: GET, HEAD, OPTIONS
Examples across supported CLIs:
kubectl get pods·kubectl describe node·kubectl logs deploy/apiaws ec2 describe-instances·gcloud compute instances listgh repo view·terraform show·helm list
The default policy allows observe-tier requests without a prompt. Operators can override this per-namespace, per-project, or per-app in policy.yaml.
MODIFY
Standard write operations.
HTTP Methods: POST, PUT, PATCH
Examples:
kubectl apply -f manifest.yaml·kubectl patch deploymentaws s3 cp·gcloud compute instances createfly deploy·terraform apply
Modify-tier requests escalate by default and block until a human approves them via the desktop app or iddio approval allow <id>.
SENSITIVE
Irreversible or high-impact operations.
HTTP Methods: DELETE, plus any request that touches a high-value resource regardless of method. For kubectl, reading Secrets is also elevated to sensitive because Secrets carry credentials.
Examples:
kubectl delete pod/x·kubectl get secretaws s3 rm·gcloud projects deletefly apps destroy
Sensitive-tier requests escalate by default.
BREAK-GLASS
Highest-risk operations. The default policy denies these outright. Operators must explicitly allow or escalate them in policy.yaml.
For kubectl: dangerous subresources (exec, attach, portforward, proxy, debug) and mutations on privilege-escalation resources (RBAC roles/bindings, webhooks, CRDs, namespaces, PersistentVolumes).
For other CLIs: IAM mutations, credential issuance, infrastructure teardown.
Examples:
kubectl exec -it pod -- sh·kubectl port-forwardaws iam create-user·gcloud iam service-accounts createfly machines api-proxy
Fail-closed behaviour
Unknown commands, unrecognised CLI tools, and classifiers that cannot determine a tier all fail closed — the request is treated as escalate or deny, never silently allowed. This prevents a novel or unexpected command from bypassing enforcement by accident.
The generic: block in policy.yaml covers any host on the inspect list that has no dedicated classifier. Its tier assignment is purely HTTP-method-based: GET/HEAD/OPTIONS → observe, POST/PUT/PATCH → modify, DELETE → sensitive. Paths containing destructive-looking prefixes (delete, destroy, drop, purge, terminate, evict) are escalated to at least sensitive regardless of method.
Supported CLI classifiers
The following CLI tools have dedicated classifiers with command-level tier resolution:
| CLI tool | Inspect protocol key | CLI aliases recognised |
|---|---|---|
| kubectl | kubectl | kubectl, kustomize |
| Google Cloud CLI | gcloud | gcloud |
| AWS CLI | aws | aws |
| GitHub CLI | github | gh |
| Terraform / OpenTofu | (CLI only) | terraform, tofu |
| Helm | (CLI only) | helm |
| Cloudflare Wrangler | cloudflare | wrangler, flarectl |
| Fly.io | fly | flyctl, fly |
Any host not matched by a dedicated classifier falls through to the generic: block (HTTP-method tier only).
Use iddio classify cli <argv> or iddio classify http <method> <url> to
see exactly which tier the daemon would assign to any specific request before
putting it in front of a real workload.
Policy rules live at ~/.iddio/policy.yaml. The file is hot-reloaded
by fsnotify — save it and the new policy takes effect within a second
without restarting the daemon. If the YAML is invalid, the daemon logs
a warning and keeps the previous working policy.
Schema overview
The top level is keyed by protocol (kubectl, gcloud, fly,
generic, …). Each protocol block contains:
| Key | Purpose |
|---|---|
inspect: | Hostnames, globs, IPs, or CIDRs the daemon should MITM for this protocol |
default: | Fallback action (allow / escalate / deny) when no rule matches |
groups: | Named selectors — match requests by tiers: or commands: |
<scope>: | First-match-wins scope-sugar list (namespaces, projects, regions, zones, orgs, apps) |
rules: | Flat allow/escalate/deny fallback, evaluated after scope blocks |
Actions: allow (forward immediately), escalate (block until a human approves or denies), deny (return 403 immediately).
The four risk tiers
| Tier | Typical operations |
|---|---|
observe | Read-only / GET; API discovery |
modify | Creates, updates, patches |
sensitive | Deletes, secret reads, credential operations |
break-glass | IAM grants, cluster-admin, destructive platform ops |
There are four tiers in V2 (observe, modify, sensitive,
break-glass). There is no Tier-1 "operate" or runbook tier.
Annotated example — kubectl
kubectl:
# Hosts to intercept. Add your own cluster URLs here.
inspect:
- "*.eks.amazonaws.com" # EKS
- "*.gke.goog" # GKE
- "*.azmk8s.io" # AKS
# Default: escalate anything that doesn't match a rule.
default: escalate
# Named groups — match by tier string or exact command label.
groups:
read:
tiers: [observe]
mutate:
tiers: [modify]
delete:
tiers: [sensitive]
admin:
tiers: [break-glass]
exec:
# commands: use the classifier's canonical "verb resource" form
# (visible in ~/.iddio/iddio.log on `classified:` lines).
commands: [exec, attach, port-forward, cp, debug, proxy]
# Scope-sugar: first-match-wins across the list.
namespaces:
- names: [prod-*, production]
rules:
escalate:
groups: [mutate, delete, admin]
commands:
- configmaps get
- configmaps list
allow:
groups: [read]
deny:
groups: [exec]
- names: ["*"]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, delete, admin]
deny:
groups: [exec]
# Flat fallback — hits when no namespaces: entry matched
# (cluster-scoped requests, API discovery, auth handshakes).
rules:
allow:
groups: [read]
escalate:
groups: [mutate, delete, admin]
deny:
groups: [exec]
Annotated example — gcloud
gcloud:
inspect:
- "*.googleapis.com"
default: escalate
groups:
read:
tiers: [observe]
mutate:
tiers: [modify]
destructive:
tiers: [sensitive]
iam:
tiers: [break-glass]
projects:
- names: ["*"]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, destructive, iam]
# Flat fallback for OAuth token refreshes, cross-service IAM,
# and discovery calls that carry no project label.
rules:
allow:
groups: [read]
escalate:
groups: [mutate, destructive, iam]
Annotated example — fly
fly:
inspect:
- "api.fly.io"
- "api.machines.dev"
- "*.fly.dev"
default: escalate
groups:
read:
tiers: [observe]
mutate:
tiers: [modify]
destructive:
tiers: [sensitive]
admin:
tiers: [break-glass]
apps:
- names: ["*"]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, destructive, admin]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, destructive, admin]
generic: — arbitrary hosts
The generic: block MITMs any host listed under inspect: and tiers
requests from the HTTP method alone (GET/HEAD/OPTIONS → observe,
POST/PUT/PATCH → modify, DELETE → sensitive). The wildcard *
under inspect: is legal only in the generic: block.
generic:
inspect: [] # add hosts here; empty ships by default
default: escalate
groups:
read:
tiers: [observe]
mutate:
tiers: [modify]
destructive:
tiers: [sensitive]
admin:
tiers: [break-glass]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, destructive, admin]
Scope-sugar keyword reference
| Keyword | Protocol | Matched label |
|---|---|---|
namespaces: | kubectl | namespace |
projects: | gcloud | project |
regions: | gcloud | region |
zones: | gcloud | zone |
orgs: | gcloud | organization |
apps: | fly | app |
orgs: | fly | org |
Scope blocks are first-match-wins down the list. Each entry needs
a names: glob list and a rules: block.
Glob patterns
| Pattern | Matches |
|---|---|
"payments" | Exact match |
"staging-*" | Any name starting with staging- |
"*" | Everything (catch-all) |
Policy decisions
| Decision | Behavior |
|---|---|
allow | Request forwarded immediately |
deny | Request rejected with 403 |
escalate | Request blocked; operator must approve or deny via desktop app or iddio approval allow <id> / iddio approval deny <id> |
default: escalate is the safe default. Without an explicit rule, unknown
commands surface an approval prompt rather than being silently allowed or
denied.
Changes to policy.yaml are hot-reloaded automatically — no daemon restart
needed. If the new YAML is invalid, the daemon logs a warning and keeps the
previous working policy.
Iddio is a transparent MITM proxy. Agents present no identity to the daemon — there are no per-agent kubeconfigs to generate, no client certificates to mint, and no tokens to manage. You point the agent's tool at the proxy and the daemon intercepts and classifies traffic automatically.
How agents reach the proxy
The daemon listens on a local HTTPS port. Route an agent's CLI tool through it by setting the appropriate environment variable or config option before starting the agent.
HTTPS_PROXY (generic)
Most CLI tools and HTTP clients respect HTTPS_PROXY:
export HTTPS_PROXY=http://127.0.0.1:9443
# (9443 is the default; check `iddio status` for proxy_listen)
The easiest way to set this up is iddio shell-init, which prints a
shell-rc snippet exporting HTTPS_PROXY/HTTP_PROXY plus the CA bundle
variables (SSL_CERT_FILE, REQUESTS_CA_BUNDLE, NODE_EXTRA_CA_CERTS,
AWS_CA_BUNDLE, GIT_SSL_CAINFO) pointing at $HOME/.iddio/ca-bundle.pem
(system roots + the iddio CA).
Tools that use HTTPS_PROXY automatically: curl, many Go and Python
HTTP clients, AWS CLI, Fly CLI, and others.
kubectl — KUBECONFIG
kubectl (client-go) respects HTTPS_PROXY, so with the shell-init
environment active your existing kubeconfig keeps pointing at the real
cluster — requests are routed through the daemon transparently.
One caveat: because the proxy re-signs TLS, kubectl must trust the iddio
CA. If your kubeconfig pins the cluster CA with
certificate-authority-data, replace it with the iddio bundle:
clusters:
- name: my-cluster
cluster:
server: https://my-cluster.example.com # the REAL cluster URL
certificate-authority: ~/.iddio/ca-bundle.pem
kubectl get pods # observe-tier read — passes through, logged
kubectl performs TLS verification. Export the daemon's CA and add it to
certificate-authority-data (base64-encoded) so kubectl trusts the proxy's
certificates.
gcloud / Fly / other CLIs
For CLIs that use environment-level HTTPS proxying, set HTTPS_PROXY
before launching the agent process. The daemon intercepts the TLS
connection and applies policy from the matching protocol block in
~/.iddio/policy.yaml.
Selecting which hosts to intercept
The daemon only MITMs hosts listed under inspect: in the protocol
block for that tool. If a host is not listed, the daemon passes the
connection through without inspection.
Example — adding a private EKS cluster endpoint:
kubectl:
inspect:
- "*.eks.amazonaws.com" # managed EKS
- "my-private-cluster.corp" # private cluster
...
See Policy Configuration for the full schema.
Trusting the daemon's CA
The daemon generates a local ECDSA P-256 CA on first run and stores it
at ~/.iddio/ca.crt (public) and ~/.iddio/ca.key (private, 0600).
For tools that perform TLS verification independently of HTTPS_PROXY:
# Show CA path, fingerprint, validity, and trust state
iddio ca info
# Path to the CA certificate
~/.iddio/ca.crt
Add ~/.iddio/ca.crt to the tool's trusted CA bundle, or export it as
certificate-authority-data in a kubeconfig.
Checking proxy status
# Show daemon status and listen address
iddio status
# Run diagnostic checks
iddio doctor
Policy applies to all intercepted traffic
Once an agent's tool is routed through the daemon, all requests
matching an inspect: entry are classified and evaluated against
~/.iddio/policy.yaml. Read-only requests in the observe tier pass
immediately (per the default policy). Write, sensitive, or break-glass
requests escalate for human approval unless the policy explicitly allows
them.
There is no per-agent identity in the current release. All traffic through the daemon is governed by the same policy file. If you need different policies for different agents, use separate daemon instances or wait for the per-user identity feature planned for a future release.
Every request through Iddio is logged to a best-effort append-only JSONL
file at ~/.iddio/audit.jsonl. The writer is mutex-protected so
concurrent goroutines can append without racing the file. Each row is
fsynced before the function returns, so a row that commits survives a
power loss.
The audit log is best-effort and not tamper-evident. There is no hash
chain, no prev_hash linking, and no integrity-verification command. The
hash field in each row is an opaque random row ID used only for pagination
and deduplication — it is not derived from row content and does not form a
chain.
Log entry format
Each entry is a single JSON line with four top-level fields:
{
"timestamp_ms": 1736947921003,
"op": "mitm",
"hash": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"fields": {
"host": "my-cluster.eks.amazonaws.com",
"method": "GET",
"path": "/api/v1/namespaces/payments/pods",
"tier": "observe",
"decision": "allow",
"status": 200
}
}
An escalated request that was approved:
{
"timestamp_ms": 1736947965112,
"op": "mitm",
"hash": "f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3",
"fields": {
"host": "my-cluster.eks.amazonaws.com",
"method": "DELETE",
"path": "/api/v1/namespaces/payments/pods/api-7d4b",
"tier": "sensitive",
"decision": "allow",
"outcome": "approved"
}
}
Row fields
| Field | Type | Description |
|---|---|---|
timestamp_ms | number | Unix epoch in milliseconds |
op | string | Operation type (e.g. mitm, pin_splice, block, control) |
hash | string | Opaque random 128-bit row ID (hex) — used for pagination/dedup only |
fields | object | Op-specific payload (host, method, path, tier, decision, status, …) |
The hash field is a random identifier, not a content hash and
not part of a chain. Two identical events appended in the same
millisecond will have different hash values.
Only hosts that match a policy inspect route are audited, and only in
enforce mode. A host that matches no route is a transparent tunnel and
leaves no trace; observe mode is a dry-run that records to the observe
log (observe.jsonl) instead of the audit log. To audit everything,
add "*" under the policy's generic.inspect block.
Viewing logs
# Show the last 50 rows (default)
iddio audit show
# Show the last 20 rows
iddio audit show --last 20
# Filter by host
iddio audit show --host my-cluster.eks.amazonaws.com
# Filter by op
iddio audit show --op mitm
# Show rows from the last hour
iddio audit show --since 1h
# Emit JSON for scripting
iddio audit show --json
# Stream new rows in real time (Ctrl-C to stop)
iddio audit tail
# Show last 10 rows then exit
iddio audit tail --lines 10
# Filter the tail stream
iddio audit tail --filter host=my-cluster.eks.amazonaws.com
# For advanced filtering, pipe the raw JSONL through jq
jq 'select(.op == "block")' ~/.iddio/audit.jsonl
jq 'select(.fields.tier == "sensitive")' ~/.iddio/audit.jsonl
jq 'select(.fields.decision == "allow")' ~/.iddio/audit.jsonl
File permissions
The audit log file is created with 0600 permissions (owner read/write
only). It is append-only in normal operation; the daemon never
overwrites or truncates it.
There is no iddio audit verify command. The audit log provides an
operational record of what happened, not a cryptographic guarantee. For
forensic integrity requirements, forward rows to an external log-aggregation
system in real time.
Iddio is a transparent MITM HTTPS proxy. It intercepts TLS traffic between AI agent CLI tools and their upstream APIs, classifies each request into a risk tier, applies a YAML policy, and audit-logs everything. The agent never knows the proxy exists — from its perspective it is talking directly to the target API.
How it works
- The agent's CLI tool is pointed at the Iddio daemon via
HTTPS_PROXYor a per-tool config (e.g.KUBECONFIG). - The daemon terminates TLS using a locally-generated ECDSA CA, classifies the request, and evaluates the policy.
- If the policy says
allow, the request is forwarded. Ifdeny, a 403 is returned. Ifescalate, the connection blocks until a human approves or denies via the desktop app or CLI. - Every request is audit-logged to
~/.iddio/audit.jsonl.
There is no system-wide packet capture (pf / nftables). Reach is
entirely through HTTPS_PROXY / per-tool configuration.
Key properties
- Local-first — the daemon runs on the operator's machine. No cloud control plane is required.
- Fail-closed — unknown commands, unrecognized CLIs, and internal
errors default to
escalate(the policy default). Nothing silently passes. - Four risk tiers —
observe(read-only),modify(writes),sensitive(deletes, secrets),break-glass(IAM, admin). There is no fifth tier. - Blocking approvals — escalated requests block the agent's HTTP connection until a human responds. No polling; no retries needed.
- Best-effort audit — every request is logged. The log is not tamper-evident; there is no hash chain.
- ECDSA CA — the daemon generates a local ECDSA P-256 CA and per-connection leaf certificates. Tools that verify TLS must trust this CA.
Transparent proxy model
Agents present no identity to Iddio. The proxy does not issue per-agent kubeconfigs, client certificates, or bearer tokens to agents. The agent's existing credentials are used as-is; the daemon intercepts the connection before it reaches the upstream.
This means:
- No per-agent enrollment step.
- No token management or rotation.
- Policy is applied uniformly to all traffic that passes through the daemon, regardless of which agent sent it.
File permissions
All sensitive files are created with restricted permissions:
| File | Permissions | Description |
|---|---|---|
~/.iddio/ca.key | 0600 | CA private key |
~/.iddio/policy.yaml | 0600 | Access policy |
~/.iddio/audit.jsonl | 0600 | Audit log |
~/.iddio/ | 0700 | Config directory |
What Iddio does NOT provide
Be explicit about limits:
- No tamper-evident audit — the audit log is best-effort
append-only JSONL. There is no
prev_hashchain and noiddio audit verifycommand. Forward rows to an external SIEM for forensic-grade integrity. - No agent identity — there is no mTLS / SPIFFE identity model, no bearer-token agent auth, and no per-agent kubeconfig or client certificates in the current release.
- No JIT credentials — credential injection (short-lived ServiceAccount tokens per request) is not implemented.
- No session recording — exec/attach sessions are not recorded.
- No enterprise control plane — there is no PostgreSQL, OIDC, or gRPC control plane in the current release. See the Deployment page.
JIT credentials, session recording, and enterprise control plane are on the roadmap. This page will be updated as features ship.
Threat model
If an agent is compromised: its requests are still proxied through Iddio and subject to classification and policy. Write and sensitive operations require human approval. Read-only operations pass through (or can be denied by policy). The audit log records what was attempted.
Blast radius limits: policy can deny entire tiers or specific
commands. Setting default: deny at the protocol level means any
unmatched request is blocked.
Trust boundary: the daemon runs as the operator's OS user and holds
the operator's cloud credentials. An attacker who can write
~/.iddio/policy.yaml or kill the daemon process can bypass policy.
Protect the config directory with standard OS-level access controls.
All Iddio state lives under ~/.iddio/ (per-user install) or /var/lib/iddio/ and /etc/iddio/ (system install). Every file is created with 0600 permissions; directories are 0700.
Directory layout
~/.iddio/
├── ca.crt # Dev CA certificate (ECDSA, generated at install)
├── ca.key # Dev CA private key (0600)
├── ca-bundle.pem # System roots + dev CA — used by SSL_CERT_FILE exports
├── policy.yaml # YAML policy rules (hot-reloaded via fsnotify)
├── settings.yaml # Daemon settings (hot-reloaded)
├── audit.jsonl # Best-effort append-only audit log (0600)
├── observe.jsonl # Observe-mode session log (0600)
├── iddio.sock # Unix domain socket — CLI and desktop connect here
├── iddio.log # Daemon log
├── install_mode # "per_user" or "system" marker written by iddio install
├── ca_trusted # Boolean marker: dev CA trusted in the OS keychain
└── bin/
└── iddio-daemon # Daemon binary copied in by iddio install
There is no tokens.yaml, no agents/ directory, and no per-agent
kubeconfig. Iddio is a transparent MITM proxy — agents set HTTPS_PROXY and
present no identity to the proxy. All authentication to the real upstream uses
the operator's existing credentials.
policy.yaml
Defines protocol-keyed policy rules. Hot-reloaded by the daemon via fsnotify — changes take effect within a second without dropping in-flight requests. If the reload fails (invalid YAML), the daemon logs the error and enforces the last-good policy.
Top-level structure
<protocol>:
inspect: [<hostname-glob>, <ip>, <cidr>, ...] # Hosts to MITM-decrypt
default: escalate # Fallback action
groups: # Named tier groups
<group-name>:
tiers: [<tier>, ...]
# or:
commands: [<command-string>, ...]
namespaces: # (kubectl only) scope-sugar; first-match-wins
- names: [<glob>, ...]
rules:
<action>:
groups: [<group>, ...]
commands: [<command>, ...]
projects: # (gcloud only) scope-sugar
- names: [...]
rules: ...
apps: # (fly only) scope-sugar
- names: [...]
rules: ...
rules: # Flat fallback — consulted after scope-sugar blocks
<action>:
groups: [<group>, ...]
Valid actions: allow, escalate, deny.
Valid tiers: observe, modify, sensitive, break-glass.
Scope-sugar blocks (namespaces:, projects:, apps:) are first-match-wins on names: globs. The flat rules: block is consulted only when no scope-sugar entry matched.
protocol keys
| Key | Covers |
|---|---|
kubectl | Kubernetes API traffic (kubectl, kustomize) |
gcloud | Google Cloud API traffic (gcloud CLI) |
fly | Fly.io API traffic (flyctl, fly) |
aws | AWS API traffic (aws CLI) |
github | GitHub API traffic (gh CLI) |
cloudflare | Cloudflare API traffic (wrangler, flarectl) |
generic | Any other host — tiered by HTTP method only |
Example: kubectl with namespace scoping
kubectl:
inspect:
- "*.eks.amazonaws.com"
- "*.gke.goog"
- "*.azmk8s.io"
default: escalate
groups:
read:
tiers: [observe]
mutate:
tiers: [modify]
delete:
tiers: [sensitive]
admin:
tiers: [break-glass]
exec:
commands: [exec, attach, port-forward, cp, debug, proxy]
namespaces:
- names: [prod-*, production]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, delete, admin]
deny:
groups: [exec]
- names: ["*"]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, delete, admin]
deny:
groups: [exec]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, delete, admin]
deny:
groups: [exec]
Example: generic block for arbitrary hosts
generic:
inspect:
- "api.internal.example.com"
default: escalate
groups:
read:
tiers: [observe]
mutate:
tiers: [modify]
destructive:
tiers: [sensitive]
admin:
tiers: [break-glass]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, destructive, admin]
settings.yaml
Daemon settings. Managed via iddio settings get|set. The file is hot-reloaded automatically on write.
| Setting | Description |
|---|---|
proxy_listen | Address and port the proxy listens on |
log_level | Logging verbosity |
default_mode | Default proxy mode at startup |
telemetry_opt_in | Whether anonymous telemetry is enabled |
Environment variables (shell-init snippet)
The iddio shell-init snippet exports the following when the daemon is up:
| Variable | Value | Purpose |
|---|---|---|
HTTPS_PROXY | http://127.0.0.1:9443 | Routes HTTPS traffic through the iddio proxy |
HTTP_PROXY | http://127.0.0.1:9443 | Routes HTTP traffic |
SSL_CERT_FILE | $HOME/.iddio/ca-bundle.pem | System roots + dev CA for Go/C toolchains |
REQUESTS_CA_BUNDLE | $HOME/.iddio/ca-bundle.pem | Python requests library |
NODE_EXTRA_CA_CERTS | $HOME/.iddio/ca-bundle.pem | Node.js |
AWS_CA_BUNDLE | $HOME/.iddio/ca-bundle.pem | AWS CLI |
GIT_SSL_CAINFO | $HOME/.iddio/ca-bundle.pem | git |
The proxy address defaults to http://127.0.0.1:9443. Override with iddio shell-init --listen-addr <addr>.
Common issues and their solutions.
Daemon is not running
iddio status
# Error: dial unix /Users/you/.iddio/iddio.sock: connect: no such file or directory
The daemon is not running. Start it:
# Reinstall (idempotent) and wait for the socket to appear
iddio install
# Or if already installed, kick the OS supervisor:
# macOS
launchctl kickstart -k gui/$(id -u)/dev.iddio.daemon
# Linux (per-user)
systemctl --user restart iddio-daemon
Run iddio doctor for a full diagnostic of permissions, the service unit, and the daemon process. Run iddio repair to fix anything doctor flags.
TLS certificate errors
Your CLI tool is rejecting the iddio dev CA certificate.
Check trust state:
iddio ca info
# trust: untrusted ← needs fixing
macOS — re-trust the CA:
sudo iddio repair --trust-ca
Linux — add the CA to the system trust store:
# Debian / Ubuntu
sudo cp ~/.iddio/ca.crt /usr/local/share/ca-certificates/iddio-dev-ca.crt
sudo update-ca-certificates
# RHEL / Fedora
sudo cp ~/.iddio/ca.crt /etc/pki/ca-trust/source/anchors/iddio-dev-ca.crt
sudo update-ca-trust
Go-based tools and Python / Node / AWS CLI read the env vars set by iddio shell-init, not the system trust store. Ensure the snippet is sourced:
eval "$(iddio shell-init)"
This sets SSL_CERT_FILE, REQUESTS_CA_BUNDLE, NODE_EXTRA_CA_CERTS, AWS_CA_BUNDLE, and GIT_SSL_CAINFO to $HOME/.iddio/ca-bundle.pem (system roots merged with the iddio dev CA).
A CLI command hangs indefinitely
The request was classified as modify, sensitive, or break-glass and is waiting for approval. Check pending approvals:
iddio approval list
Approve or deny:
iddio approval allow <id>
iddio approval deny <id>
If approval list is empty, the proxy mode may be set to observe or off rather than enforce, or the policy for that request is set to deny (which returns immediately, not hang). Verify:
iddio status
# proxy_mode enforce ← must be enforce for policy to apply
Policy changes are not taking effect
Policy is hot-reloaded via fsnotify. If a reload fails (invalid YAML), the daemon logs an error and enforces the last-good policy. Check daemon status for parse errors:
iddio status
# policy loaded (operator policy.yaml failed to reload: ...)
Validate the candidate before saving:
iddio policy validate --path ~/.iddio/policy.yaml
Or view the policy the daemon is actually enforcing:
iddio policy show
Host is not being intercepted
The host is not in any inspect: list in policy.yaml, or the host is on the user pinned (never-MITM) list.
Check the inspect list:
iddio inspect summary
Check the pinned list:
iddio pinned
If the host appears in the pinned list and should not, remove it:
iddio unpin <host>
If the host appears in the detector pin cache (auto-detected cert-pinned hosts), it will splice through without MITM:
iddio detector list
HTTPS_PROXY is set but traffic is not going through iddio
The shell-init snippet sets HTTPS_PROXY only when the daemon is running. If you source the snippet before starting the daemon, the variable is unset. Re-source after the daemon starts:
iddio install
eval "$(iddio shell-init)"
Some tools ignore HTTPS_PROXY for localhost/127.0.0.1 targets by default. This is expected — traffic to the proxy listener itself should not loop through the proxy.
Commands are slow even for read operations
Classification and policy evaluation add sub-millisecond overhead. If reads are slow, the bottleneck is the upstream API server, not iddio. Verify by checking iddio is not in the critical path:
# Temporarily bypass (does not stop the daemon)
iddio set-proxy-mode passthrough
kubectl get pods # if this is fast, upstream was fine all along
iddio set-proxy-mode enforce
iddio doctor reports failures
Run iddio repair to fix most issues automatically:
iddio repair
For macOS CA trust issues specifically:
sudo iddio repair --trust-ca
Pass --dry-run to see what repair would do without making changes.
Still stuck? Open an issue on GitHub or reach out via the Pricing page to talk to engineering.
Coming soon. Session recording is on the roadmap and not yet available in the shipping release. This page describes the intended capability; commands and configuration are not final.
Session recording will capture the full byte stream of interactive kubectl exec, kubectl attach, and kubectl port-forward sessions as the proxy intercepts the HTTP upgrade. Each session will be stored as a JSONL file and linked to the corresponding audit log entry, giving operators a forensic-quality replay trail of what an AI agent sent and received during a live container session.
When a policy rule returns escalate, Iddio blocks the agent's HTTP
connection and waits for a human operator to approve or deny. Approvals
are handled by an in-process IPC broker inside the daemon.
How approvals work
The daemon's policy engine returns escalate. The proxy goroutine submits
the request to the approval broker and blocks the agent's connection.
The broker mints an approval ID and publishes an approval_requested IPC
event. The desktop app shows a banner; the CLI can list pending approvals.
The operator approves or denies via the desktop app or the iddio approval
CLI commands.
The broker receives the verdict and unblocks the proxy goroutine. The agent's connection is either forwarded to the upstream or returned a 403 — no retry or polling needed by the agent.
The default timeout is 30 seconds. If no decision is made within the timeout window, the request is automatically denied.
Desktop app
When the desktop app is running it shows a banner for each pending escalation. Click Allow or Deny to resolve it. The agent's blocked connection is released immediately.
CLI commands
# List pending approvals
iddio approval list
# Approve a specific request
iddio approval allow <id>
# Deny a specific request
iddio approval deny <id>
# Emit pending approvals as JSON (useful for scripts)
iddio approval list --json
# Include an operator-supplied reason in the audit record
iddio approval allow <id> --reason "scheduled maintenance window"
iddio approval deny <id> --reason "not expected at this time"
The <id> value is the approval ID shown by iddio approval list
(format: appr_<8 hex chars>).
Watching for approvals
# Stream approval events in real time
iddio approval watch
iddio approval watch streams approval_requested and
approval_replied IPC events as they arrive, useful for scripting
or monitoring in a terminal.
There is one approval broker per daemon process. All approvals share the same 30-second timeout window. Multiple pending approvals can be queued simultaneously — each is independently resolvable.
There are no webhook, Slack, PagerDuty, or HMAC callback approval modes in
the current release. All approvals go through the desktop app or the iddio approval CLI. External integrations are on the roadmap.
Coming soon. JIT credentials are on the roadmap and not yet available in the shipping release. This page describes the intended capability; commands and configuration are not final.
JIT (Just-In-Time) credential support will allow iddio to mint short-lived ServiceAccount tokens via the Kubernetes TokenRequest API instead of relying on a long-lived static kubeconfig. Tokens will be cached in memory, renewed automatically before expiry, and injected transparently into upstream requests — reducing the blast radius of a credential compromise to the token's TTL rather than the lifetime of the kubeconfig file.
Iddio runs as a local daemon on the operator's machine. The desktop app
(macOS) and the CLI (iddio) are the two supported deployment surfaces
in the current release.
Desktop app (macOS)
A native macOS app with a visual policy editor, approval banners, and audit log viewer.
Homebrew Cask:
brew install --cask leonardaustin/tap/iddio-desktop
DMG / pkg download — grab the latest release from GitHub Releases and drag Iddio Desktop to Applications.
The desktop app manages the daemon lifecycle. Launch the app and it starts the daemon automatically.
CLI (macOS / Linux)
A single binary. Lightweight and scriptable.
Homebrew:
brew install leonardaustin/tap/iddio
Linux (.deb / .AppImage): download from GitHub Releases.
First-run setup
# Install the daemon service (launchd / systemd)
iddio install
# Verify the daemon is running
iddio status
# Run diagnostics
iddio doctor
Starting / stopping the gateway
# Run the daemon in the foreground (debugging — `iddio install`
# registers it as a service for normal use)
iddio gateway
# Check status
iddio status
iddio install registers a launchd (macOS) or systemd (Linux) service so the
daemon starts automatically at login. Use iddio uninstall to remove it.
Updating
iddio upgrade # download and apply the latest release
iddio upgrade --check # just check whether a newer release exists
# Or drive the daemon's self-update state machine directly:
iddio update check # force an immediate check against GitHub Releases
iddio update status # show the current update state
iddio update apply # apply the currently-staged update and restart
Policy file location
The policy YAML is at ~/.iddio/policy.yaml on both platforms. Edit it
in the desktop app's built-in editor or in any text editor — the daemon
hot-reloads it automatically.
See Policy Configuration for the full schema.
Enterprise control plane — coming soon
Coming soon. A multi-user enterprise control plane (centralised policy management, OIDC/SSO, RBAC, team audit log) is on the roadmap. The current release is local-only: one daemon per developer machine, one policy file per user.
The planned enterprise tier will include:
- Centralised policy management and version control
- OIDC / SSO authentication
- Role-based access control
- Centralised audit log forwarding
No commands, configuration, or server addresses are final yet.
Iddio's policy engine supports rules scoped to multiple infrastructure
protocols. A single ~/.iddio/policy.yaml file governs agent access
across different systems.
Supported protocols
| Protocol | Classifier | Scope keys |
|---|---|---|
kubectl | Kubernetes API operations | namespaces |
gcloud | Google Cloud CLI / googleapis.com | projects, regions, zones, orgs |
fly | Fly.io CLI / api.fly.io | apps, orgs |
aws | AWS CLI operations | (inspect-only; no scope-sugar) |
github | GitHub CLI (gh) | (inspect-only; no scope-sugar) |
terraform | Terraform operations | (inspect-only; no scope-sugar) |
helm | Helm operations | (inspect-only; no scope-sugar) |
cloudflare | Cloudflare CLI | (inspect-only; no scope-sugar) |
generic | Any HTTPS host, tiered by HTTP method | none (flat rules: only) |
SSH is not yet supported. SSH interception is on the roadmap; there is no
ssh: policy block in the current release.
Policy format
The top level of policy.yaml is keyed by protocol name. Each block
follows the same structure: inspect: (hosts to MITM), default:
(fallback action), groups: (named tier/command selectors),
optional scope-sugar lists, and a flat rules: fallback.
kubectl:
inspect:
- "*.eks.amazonaws.com"
- "*.gke.goog"
- "*.azmk8s.io"
default: escalate
groups:
read:
tiers: [observe]
mutate:
tiers: [modify]
delete:
tiers: [sensitive]
admin:
tiers: [break-glass]
namespaces:
- names: ["*"]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, delete, admin]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, delete, admin]
gcloud:
inspect:
- "*.googleapis.com"
default: escalate
groups:
read:
tiers: [observe]
mutate:
tiers: [modify]
destructive:
tiers: [sensitive]
iam:
tiers: [break-glass]
projects:
- names: ["*"]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, destructive, iam]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, destructive, iam]
fly:
inspect:
- "api.fly.io"
- "api.machines.dev"
- "*.fly.dev"
default: escalate
groups:
read:
tiers: [observe]
mutate:
tiers: [modify]
destructive:
tiers: [sensitive]
admin:
tiers: [break-glass]
apps:
- names: ["*"]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, destructive, admin]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, destructive, admin]
Scope-sugar blocks
Scope-sugar lists filter rules to a specific project, namespace, app, or other label extracted from the request. Blocks are first-match-wins down the list.
kubectl — namespaces
kubectl:
...
namespaces:
- names: [prod-*, production]
rules:
escalate:
groups: [mutate, delete, admin]
allow:
groups: [read]
deny:
groups: [exec]
- names: ["*"]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, delete, admin]
gcloud — projects, regions, zones, orgs
gcloud:
...
projects:
- names: [my-prod-project]
rules:
escalate:
groups: [mutate, destructive, iam]
allow:
groups: [read]
- names: ["*"]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, destructive, iam]
fly — apps, orgs
fly:
...
apps:
- names: [my-prod-app]
rules:
escalate:
groups: [mutate, destructive, admin]
allow:
groups: [read]
- names: ["*"]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, destructive, admin]
generic: — arbitrary HTTPS hosts
The generic: block tiers requests from the HTTP method alone and
applies to any host listed under inspect:. The wildcard * is legal
under generic.inspect: only.
generic:
inspect: [] # add hosts here; ships empty so nothing is MITMd by default
default: escalate
groups:
read:
tiers: [observe]
mutate:
tiers: [modify]
destructive:
tiers: [sensitive]
admin:
tiers: [break-glass]
rules:
allow:
groups: [read]
escalate:
groups: [mutate, destructive, admin]
Glob patterns
All names: and inspect: values support glob patterns:
| Pattern | Matches |
|---|---|
"payments" | Exact match |
"staging-*" | Any name starting with staging- |
"*" | Everything (catch-all) |
SSH — coming soon
Coming soon. SSH interception is on the roadmap and not yet available in
the shipping release. There is no ssh: policy block; adding one has no
effect. This page will be updated when SSH support ships.
SSH gateway support will intercept SSH connections and apply tier-based policy to shell sessions and SCP transfers. No configuration is available yet.
Validation
The policy engine rejects documents that reference unknown groups,
contain empty rules: branches, or use * under a non-generic
inspect: list. Glob patterns are compiled at load time; malformed
patterns are a load error.
If a protocol block fails validation the entire policy reload is rejected. The daemon logs the error and keeps the previous working policy — no requests are dropped.
Coming soon. The enterprise control plane is on the roadmap and not yet available in the shipping release. This page describes the intended capability; commands and configuration are not final.
The enterprise control plane is a planned multi-tenant server that will manage fleets of iddio daemons across an organisation. Intended capabilities include PostgreSQL-backed storage, OIDC-authenticated operator access, role-based access control, centralised policy management with version history, centralised audit aggregation, and a REST API for programmatic management. Each tenant would receive isolated storage and subdomain routing. The server/ module has been removed from the current codebase; this work is roadmap-only.
Coming soon. The MCP gateway is on the roadmap and not yet available in the shipping release. This page describes the intended capability; commands and configuration are not final.
The MCP gateway will allow iddio to act as a policy-enforced intermediary between AI agents and MCP (Model Context Protocol) tool servers. Planned capabilities include classifying each tool call into a risk tier, evaluating calls against the YAML policy, blocking for human approval when required, and writing every invocation to the audit log — using the same classify-enforce-audit pipeline that today governs kubectl, aws, gcloud, and the other supported CLIs.
Need help?
Can't find what you're looking for? Reach out to the engineering team.