Policies and Approvals
Capability policies, approval flows, and where they live.
Policies are enforced at tool dispatch and, by default, at the sandbox boundary. Definition-level policies act as a security floor: invocation code may narrow access but cannot replace allowlists or remove denials.
Available controls
- 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).
The capability policy block can be set on a finite job, init(), a session, or one prompt:
import { init, policiedFetch, type CapabilityPolicy } from '@fabric-harness/sdk';
const policy: CapabilityPolicy = {
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 },
};
const fabric = await init({ policy });Network policy
The network block is enforced only for HTTP routed through the SDK's policiedFetch helper or a
connector that uses it. It does not monkey-patch global fetch, Axios, third-party SDKs, raw
sockets, DNS, or subprocess traffic. A custom tool using one of those paths can bypass the
application policy unless you explicitly route it through the policy-aware transport.
Wrap every custom fetch-like transport:
const safeFetch = policiedFetch(fetch, policy);
await safeFetch('https://evil.example.com');
// → throws FabricError { code: 'POLICY_DENIED', ... }For Axios, supply an adapter backed by a policy-aware Fetch implementation, or put the entire agent
behind an egress proxy. For production workloads, pair application checks with a container, cluster,
or cloud-provider network boundary and call assertEnforceableNetworkPolicy() during startup. See
private networking and egress.
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 prompt —
session.prompt(text, { commands, policy }). - Per session —
agent.session(id, { policy }). - Per finite agent —
defineAgent({ policy, run }); this is a security floor forrun()calls toinit(). - Per persistent agent — return
{ policy }fromcreateAgent(({ id }) => ({ ... })). - Shared application default — pass
policytoinit()or merge a policy module from.fabricharness/policies/in your own config.
See Enterprise controls for a complete definition-level example with tools, approvals, and cost budgets.
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"
# Deployed application: discover across the authenticated tenant
fh approvals --url "$FABRIC_HARNESS_APP_URL/api" --token-env FABRIC_HARNESS_REMOTE_TOKENWhen a model provider omits its own tool-call id, Harness creates a unique id for each logical call and reuses it across approval binding, lineage, tool context, and session history. Two calls to the same tool in one session therefore cannot share an approval grant or idempotency identity by accident.
See Approvals and fh approvals.
Security hardening
Complete production hardening guidance for secrets, policy, network boundaries, sandboxes, HTTP ingress, builds, snapshots, and audit evidence.
Agent Events
Subscribe to a typed event stream from any Fabric Harness agent — text deltas, tool calls, shell commands, compaction, approvals, tasks, errors. Available from both import entrypoints.