原始内容
iota
One policy file for every coding agent.
Every agent harness reinvents the same decision — what may this agent read, write, run, and fetch — in its own proprietary, non-portable config. iota is a portable declaration (agents.yaml) plus enforcement that travels with it. We don't build another enforcer; we build the format, the engine, and the shells that plug it into each harness natively.
# agents.yaml — lives in your repo like .gitignore
version: 0.1
fs:
read:
allow: ["**"]
deny: ["**/.env*", "secrets/**"]
write:
allow: ["src/**", "test/**", "*.md"]
delete: ask
exec:
actions:
test: "npx vitest run <paths?>"
deps: "npm install <pkg?>"
patterns:
- match: "git *"
verdict: allow
best_effort: true
default: ask
net:
allow: ["registry.npmjs.org", "*.github.com"]
default: deny
Packages: which one do I install?
| Package | What it is | Who installs it |
|---|---|---|
@iota-policy/pi-extension |
The pi package: intercepts tool calls, bless prompts, decision log | You, via pi install — this is the only package pi users touch |
@iota-policy/core |
The engine library: agents.yaml parser + verdict evaluator, pure functions, no harness hooks |
Harness/tool authors, as a normal npm dependency. Not a pi extension — pi will report it invalid if you try to install it, and the pi-extension already pulls it in automatically |
Use it today with pi
pi ships no permission layer by design. iota is that layer — in-process, ~0.2 ms per decision, zero extra tokens:
pi install npm:@iota-policy/pi-extension
pi # inside pi, run /iota init to scaffold agents.yaml
# (iota nudges you if none exists; first governed call prompts a bless)
Prefer to scaffold before launching pi (or in CI)? npx -p @iota-policy/pi-extension iota-pi init writes the same starter policy from the shell.
Package docs: @iota-policy/pi-extension
Policy controls
Every verdict is allow, deny, or ask. All defaults are fail-closed.
| Control | What it does |
|---|---|
fs.read / fs.write / fs.delete |
Glob allow/deny lists per operation, or a bare verdict. Deny always wins; anything matching no allow glob is denied. * matches within a path segment, ** any depth, {a,b} alternation; dotfiles are matched (.env is excludable) |
| Workspace confinement | Absolute paths, ~, and .. escapes are denied unconditionally — no policy can grant access outside the workspace root |
| Self-protection | Writes/deletes to agents.yaml and harness config paths (.claude/, .codex/) are always denied, non-overridable. A policy that can authorize edits to its own enforcement is not a policy |
exec.actions — named actions |
Map intent to an exact program + frozen flags: test: "npx vitest run <paths?>". Refuses to pattern-match, so refuses to be pattern-evaded — sh -c, pipes, $(…), env prefixes all fall through to default |
| Typed argument slots | <paths> (workspace-relative, no flags/absolute/..), <pkg> (npm-name-shaped), <word> (single safe token); ? variants allow empty. Validated by type, never by user-supplied regex |
exec.patterns — escape hatch |
Glob over the raw command string, must carry best_effort: true — the weakness is confessed in the file, resurfaced at bless time and in every log entry |
exec.default |
Catches every unmapped command (default ask) |
net |
Domain allowlist; *.github.com matches subdomains but never the apex; default deny. (Enforced at compile targets; in pi, egress rides exec — see honesty notes) |
ask semantics |
Interactive → human prompt. CI/headless → deny, always logged. No timeout-allow. No config option to change this — deliberately |
| Layering | org → user → repo, tighten-only, strictest wins: a repo policy can narrow your baseline, never widen it. A malicious repo's policy is structurally inert |
| Bless | Repo policies govern nothing until you approve them: hash-pinned, shows every grant + best-effort entry. Any file change re-prompts with a diff; rejections persist until the file changes |
| Absent policy | No agents.yaml = not governed. No implicit policy, ever. But a present-and-invalid file fails closed — everything blocks until it parses |
| Decision log | Every evaluation → one JSONL line: verdict, matched rule, reason, best-effort flag, what actually happened, timing |
Sample policies
Node/TypeScript development — day-to-day work, tests and deps allowed by name:
version: 0.1
fs:
read:
allow: ["**"]
deny: ["**/.env*", "**/*.pem", "**/id_*", ".git/config"]
write:
allow: ["src/**", "test/**", "*.md", "package.json"]
delete: ask
exec:
actions:
test: "npx vitest run <paths?>"
lint: "npx eslint ."
build: "npm run build"
deps: "npm install <pkg?>"
patterns:
- match: "git *"
verdict: allow
best_effort: true
default: ask
net:
allow: ["registry.npmjs.org"]
default: deny
Docs-only contributor — the agent edits prose, nothing else:
version: 0.1
fs:
read:
allow: ["**"]
deny: ["**/.env*", "secrets/**"]
write:
allow: ["docs/**", "*.md"]
delete: deny
exec:
default: deny
net:
default: deny
Read-only investigator — audits and CI analysis; can look, run named checks, change nothing (ask auto-denies headless):
version: 0.1
fs:
read:
allow: ["**"]
deny: ["**/.env*", "**/*.pem"]
exec:
actions:
test: "npm test"
typecheck: "npx tsc --noEmit"
default: ask
net:
default: deny
Tight user baseline — put this in ~/.config/iota/agents.yaml; no repo policy can widen it:
version: 0.1
fs:
read:
allow: ["**"]
deny: ["**/.env*", "**/*.pem", "**/id_*"]
write:
allow: ["src/**", "test/**", "docs/**", "*.md", "package.json"]
net:
allow: ["registry.npmjs.org", "*.github.com"]
default: deny
Design: one engine, three shells
One pure-TypeScript engine (@iota-policy/core) answers evaluate(operation) → allow | deny | ask — synchronous, no I/O, no daemon. Three shells wrap it: a compiler to native harness configs (Claude Code settings.json, Codex approval policies — roadmap), an embeddable library (how the pi extension works today), and an MCP server (last resort, documented as such). When a compile target can't express a rule, compilation fails closed and emits a machine-readable gap report — never a silent weakening. A language-neutral conformance suite makes "supports agents.yaml" a verifiable claim.
| Target | Status |
|---|---|
| pi | shipped — @iota-policy/pi-extension |
| Claude Code (compile to settings.json/hooks) | roadmap |
| OpenAI Codex (compile to approval policy + sandbox) | roadmap |
audit / test (attack scenarios → per-harness enforcement matrix) |
roadmap |
| MCP shell | roadmap (last resort) |
What we claim, exactly
The embedded library governs tool-call intent, not execution — an allowed bash command's child processes aren't constrained by an in-process interceptor. The compiled OS/harness config is the enforcement layer; recommended deployment is always both. best_effort rules are best-effort and permanently visible. Repo policy binds agents whose harness runs iota — "enforced where installed, auditable everywhere." The full adversary analysis, including the self-escalation and lobbying-the-human attempts we've already caught live, is in THREAT-MODEL.md.
Documents
SPEC.md — the normative format spec (the actual product) · THREAT-MODEL.md · CONTRIBUTING.md · SECURITY.md · CHANGELOG.md
License
Apache-2.0. Embeddability is strategic: vendor it in.