iota-policy-pi-extension

内容来源:README.md(说明文档) · 原始地址 · 查看安装指南

原始内容

iota

One policy file for every coding agent.

CI npm npm License: Apache-2.0

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.