Policies & Fine-Grained Authorization

Bulwark's policy engine combines scope-based authorization (what an agent can do) with relationship-based FGA (what resources it can access).

Two Layers of Authorization

Layer 1: Scopes (What)

Scopes define the categories of operations an agent can perform:

read:customers     — read customer records
write:tickets      — create/update support tickets
credential:github  — use the GitHub credential proxy
approve:payments   — trigger CIBA payment approvals

Scopes are declared at agent registration and enforced on every request.

Layer 2: FGA (Which)

FGA (Fine-Grained Authorization) defines which specific resources an agent can access:

// Agent can view document:doc_abc
await bulwark.fga.write({
  writes: [{ user: "agent:agent_01j", relation: "viewer", object: "document:doc_abc" }],
});

// Check at runtime
const { allowed } = await bulwark.fga.check({
  user: "agent:agent_01j",
  relation: "viewer",
  object: "document:doc_abc",
});

Relationship Model

FGA uses a simple (user, relation, object) triple model:

user:usr_01j   —[owner]→   document:doc_abc
agent:agent_01j —[viewer]→  document:doc_abc
team:eng       —[member]→  user:usr_01j

Relations can be inherited through parent relationships:

// If a user is an owner, they're also a viewer
viewer from owner

Policy Evaluation

When an agent makes a request, Bulwark evaluates:

1. Does the agent's Biscuit token include the required scope?
   → If no, reject with 403 INSUFFICIENT_SCOPE

2. Does the agent have the required FGA relation on the resource?
   → If no, reject with 403 FORBIDDEN

3. Are there any CIBA requirements for this action?
   → If yes, initiate human approval flow

Common Patterns

Resource ownership

// User creates a resource — set as owner
await bulwark.fga.write({
  writes: [{ user: `user:${userId}`, relation: "owner", object: `doc:${docId}` }],
});

// Share with an agent
await bulwark.fga.write({
  writes: [{ user: `agent:${agentId}`, relation: "viewer", object: `doc:${docId}` }],
});

Team-based access

// Add team members
await bulwark.fga.write({
  writes: [
    { user: `user:${userId}`, relation: "member", object: `team:engineering` },
    { user: `team:engineering`, relation: "viewer", object: `project:bulwark` },
  ],
});

Filter a list

// Return only docs the agent can view
const { allowed } = await bulwark.fga.filter({
  user: `agent:${agentId}`,
  relation: "viewer",
  type: "doc",
  objects: allDocIds.map((id) => `doc:${id}`),
});

Trust Level Policies

Trust levels can gate specific FGA relations automatically:

| Trust Level | Can access | |-------------|------------| | low | Public resources only | | medium | Resources explicitly shared with agent | | high | Resources owned by the delegating user | | critical | All resources (subject to CIBA) |