Policies and Approvals
Capability policies, approval flows, and where they live.
The full notes live at docs/policies-approvals.md. This page summarizes the model.
Today
- Scoped commands. Agents declare commands per call (
commands: [...]). Anything else is a capability error. - Capability-aware tools.
writeandedithonor any filesystem write scope on the session. - Approvals.
session.approval.request({ reason, risk, timeout })waits durably (Temporal target) or in-process (inline runtime).
Designed-for
The capability policy block:
import { init, policiedFetch } from '@fabric-harness/sdk';
const fabric = await init({
policy: {
filesystem: {
read: ['/workspace/**'],
write: ['/workspace/src/**', '/workspace/tests/**'],
writeDeny: ['/etc/**', '/usr/**'],
writeRequireApproval: ['**/.env*'],
},
commandPolicy: {
allow: ['npm test', 'git diff', 'git status'],
requireApproval: ['git push*', 'rm -rf*'],
},
network: {
mode: 'allowlist',
hosts: ['api.openai.com', '*.anthropic.com', 'api.github.com'],
protocols: ['https:'], // defaults to ['http:', 'https:']
},
approvals: { defaultTimeoutMs: 5 * 60_000 },
},
});Network policy
The network block is enforced by tools that route HTTP through the SDK's policiedFetch helper. Wrap any fetch-like:
const safeFetch = policiedFetch(fetch, fabric.policy);
await safeFetch('https://evil.example.com');
// → throws FabricError { code: 'POLICY_DENIED', ... }Modes:
'allowlist'— only hosts matching thehostsglob list are permitted (default whenhostsis set).'denylist'— listed hosts are blocked, everything else allowed.'none'— block all outbound network. Useful for sandboxed analysis agents.
Mount permissions
session.mount(mountAt, source, { mode: 'read' | 'write' }) (read by default) marks a mount as immutable. Write tools (write, edit, mkdir, rm) targeting paths under it are rejected by capability policy at the tool-call layer.
Sandbox-layer enforcement (v0.10+)
By default, init({ policy }) auto-wraps the session's SandboxEnv with policiedSandboxEnv() so that raw sandbox.exec / sandbox.writeFile / sandbox.readFile calls from inside agent code honor the same CapabilityPolicy as tool dispatch. Without this, agent code that called (await session.sandbox).exec('rm -rf /') would bypass the policy that would have blocked the equivalent session.shell() call.
Pass bypassPolicyEnforcement: true to init() if you have your own enforcement layered above (egress proxy, custom tools, etc.).
import { init, policiedSandboxEnv } from '@fabric-harness/sdk';
const fabric = await init({
policy: {
commandPolicy: { deny: ['rm -rf*'] },
filesystem: { writeDeny: ['/etc/**'] },
},
});
const session = await fabric.session();
const sandbox = await session.sandbox;
// This now throws COMMAND_DENIED — the same policy that gates session.shell().
await sandbox.exec('rm -rf /tmp/data');The decorator throws COMMAND_DENIED for exec violations and POLICY_DENIED for filesystem violations. requireApproval patterns at the sandbox layer surface as denials with details.approvalRequired: true — the sandbox layer doesn't auto-resolve approvals because it has no session context. Use session.shell() / tool calls when you want approvals gated through the full machinery.
Where to put policy
- Per call —
session.prompt(text, { commands, capabilities }). - Per session —
agent.session(id, { capabilities }). - Per agent — declared on the metadata
agent({...}). - Workspace-wide —
.fabricharness/policies/*.ts(designed-for; keep simple files for now).
Approvals from the CLI
fh approvals <session-id> --pending
fh approve <session-id> <approval-id> --actor preetham
fh reject <session-id> <approval-id> --actor preetham --reason "Wrong branch"See Approvals and fh approvals.