FabricFabricHarness
Reference

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. write and edit honor 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 the hosts glob list are permitted (default when hosts is 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 callsession.prompt(text, { commands, capabilities }).
  • Per sessionagent.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.