---
title: "Attribute-Based Access Control"
source: "https://docs.vertesiahq.com/security/abac"
markdown: "https://docs.vertesiahq.com/llms/security/abac.md"
---

# Attribute-Based Access Control

The [role model](/security/overview) grants access to **named people** on **named resources**. Attribute-Based
Access Control (ABAC) adds a second way to write rules: by **attributes**. Instead of "Alice can read this
collection," you write "anyone in the Finance department can read documents labelled Finance," or "users with
clearance 3 or higher can read documents classified Confidential or below."

Rules adapt as people and content change: a new hire who joins the Finance group is covered when their attributes
match. Principal attributes and matching rules are resolved when an access token is issued, so refresh the user's
session or issue a new API-key token before testing a policy change.

## The two kinds of attribute rules

ABAC rules are still Access Control Entries, but instead of pointing at a fixed principal or resource they
match by conditions:

- **Principal Set** — matches **who** by their attributes (a user's department, tags, clearance; whether the
  caller is a user or an API key). Evaluated when a token is issued: the match decides whether the rule even
  enters that person's session.
- **Resource Set** — matches **which content** by its attributes (a document's sensitivity, its properties,
  its type). Evaluated when content is queried: the match decides which documents the rule lets through.

A rule can combine both: a Principal Set on the "who" side and a Resource Set on the "what" side. That pairing
— *these principals* get *this role* on *content matching these conditions* — is the heart of attribute-based
and clearance-based access.

  An **empty** condition list is a wildcard. A Principal Set with no principal conditions matches **everyone**
  (including API keys); a Resource Set with no resource conditions matches **every** document. Wildcards are
  powerful — reach for them deliberately.

## Writing conditions

A condition is a field, an operator, and a value. Multiple conditions in one rule are combined with AND.

| Operator | Meaning | Supported in |
|----------|---------|--------------|
| `$eq` / `$ne` | equals / does not equal | Principal Sets and Resource Sets |
| `$gt` / `$gte` / `$lt` / `$lte` | greater / less than | Principal Sets and root-level numeric Resource Set fields |
| `$in` / `$nin` | is / is not one of a list | Principal Sets and Resource Sets |
| `$exists` | the field is present (or absent) | Principal Sets and Resource Sets |
| `$empty` | the field is empty (or non-empty) | Principal Sets and Resource Sets |
| `$like` | case-insensitive wildcard text match, where `*` matches anything | Principal Sets only |

Where the field name goes depends on what it is:

- **Principal fields** sit at the root of Principal Set conditions: `clearance`, `compartments`, `tags`, `email`,
  and `kind` (`user` or `apikey`).
- **Content classification fields** sit at the root of Resource Set conditions: `sensitivity`, `compartments`,
  and `tags`.
- **Custom attributes** sit under `properties.` — for example `properties.department`,
  `properties.region`.

### Referring to the person in a content rule

Inside a Resource Set, a value may reference the requesting person with the `$principal.` prefix. It is
resolved to that person's own value when their token is issued, then matched against each document.

```json
{ "sensitivity": { "$lte": "$principal.clearance" } }
```

Read this as "only show documents whose sensitivity is at most the viewer's own clearance" — the classic
"no read up" rule. `$principal.` works only inside Resource Set (content) conditions; it has no effect on the
principal side.

## Two kinds of roles

Roles are split into two partitions, and the platform always checks the built-in set first so custom roles
can never shadow it:

- **System roles** — the built-in roles from the [role model](/security/overview): `owner`, `admin`,
  `developer`, `reader`, and so on. These govern the platform as a whole.
- **ABAC content roles** — a separate, content-only set defined for attribute rules:

  | Role | Grants |
  |------|--------|
  | `content:reader` | read |
  | `content:writer` | read, write |
  | `content:manager` | read, write, delete |

When you build an attribute rule over content, you use a **content role**, not a system role. This keeps the
two worlds cleanly separated: system roles decide what someone can do across the platform; content roles
decide, attribute by attribute, which documents they reach.

### Document scope and collection scope

A content role applies to a **scope** — either `document` (the default) or `collection`. The same
`content:reader` / `content:writer` / `content:manager` roles serve both; the scope you choose on the rule
decides whether it governs documents or collections. This distinction matters for restriction, below.

## Classification: clearance, compartments, and sensitivity

ABAC has first-class support for a Bell-LaPadula (BLP) style classification model, so you can express
clearance-based access without hand-writing conditions.

**On people** (users, groups, and API keys):

- **Clearance** — a level from 0 to 4. The higher the clearance, the more sensitive the content the person
  may reach.
- **Compartments** — named need-to-know areas (for example `engineering`, `hr`, `legal`). A person only
  reaches content in compartments they hold.

**On content** (documents and collections):

- **Sensitivity** — the same 0–4 scale, labelled **Public, Internal, Confidential, Restricted, Secret**.
- **Compartments** — the areas a document belongs to.

  When a person belongs to several groups, their clearance is the **highest** of any group or their own
  (most permissive wins), and their compartments are the **union** of all of them. A document that belongs to
  several collections takes the **highest** sensitivity and the **union** of their compartments (most
  restrictive wins).

The two canonical rules, written as Resource Set conditions:

```json
{ "sensitivity": { "$lte": "$principal.clearance" } }
```
```json
{ "compartments": { "$in": "$principal.compartments" } }
```

Together: a person sees a document only when its sensitivity is within their clearance **and** they share at
least one of its compartments. For write access you invert the sensitivity comparison (`$gte`) — the "no write
down" rule — so classified information cannot be copied into a less-classified document.

### Advanced: unclassified content

Documents created before classification was enabled may have no `sensitivity` or `compartments` at all. So a
clearance rule does not silently hide them, the platform treats unclassified content as **public**: any rule
that references a classification field automatically also allows documents where that field is absent or
empty. When you roll classification out, plan to set a baseline sensitivity on existing content, or lean on
this fallback during the transition.

## Turning restriction on: the Restrict Mode preset

By default a project is **open**: its content is visible to members through the role model, and attribute
rules only *add* access. To make attributes the gate — nothing visible unless a rule grants it — you switch
the project into restrict mode.

The IAM permissions screen has a one-click **Restrict Mode** preset. It creates a single rule that pairs a
wildcard Principal Set (named *BLP Restrict Mode*, matching everyone) with a Resource Set (named *Deny All
(BLP)*) whose condition matches no real document. The effect: every person now carries a content-security
context, which **drops the `project:*` default**. A resource remains available when it directly names that user or
one of their groups, or when another ABAC rule grants access. Create and test the real granting rules first, apply
Restrict Mode, refresh test tokens, and then expand the rollout.

The companion **Open Mode** preset does the reverse — a wildcard rule granting everyone read access to all
content — to return a project to open.

  **Restrict Mode restricts documents, not collections.** The preset is document-scoped, and content lookups
  never fall back across scopes: a document-level restriction leaves the **collection** listing open. If you
  also need to lock down which collections people can see, add a **separate** rule with the scope set to
  **collection** (using the same `content:reader` / `content:writer` / `content:manager` roles). Restricting
  documents and restricting collections are two independent switches.

### Advanced: how restriction reaches the query

Attribute rules are resolved when a token is issued, not when content is queried. Each matched rule
contributes its role's verbs to a content-security block in the caller's session, keyed by scope — a
document-scope rule contributes bare `read` / `write` / `delete` keys, while a collection-scope rule
contributes `collection:read` / `collection:write` / `collection:delete`. At query time the content service
reads only the keys for the scope it is filtering and turns each rule's conditions into an OR filter; there is
no cross-scope fallback by design, which is exactly why a document restriction does not touch collections. If
no key exists for a scope, that scope stays in open mode. Because all of the "who can see what" logic is
settled at token time, the query path stays a simple, fast filter.

### Token lifecycle

Role assignments, group membership, principal attributes, and matching Principal Sets are captured when the access
token is issued. After changing any of them, refresh or restart the user's authenticated session. For an API key,
issue a new access token. Long-running agents inherit the effective user's conditions when their agent token is
created, so start a fresh run when validating an updated policy.

## Where to go next

- [Configure roles and memberships](/security/roles-memberships).
- [Set principal attributes](/security/principal-attributes) and [classify content](/security/content-security).
- [Create and test dynamic access rules](/security/dynamic-rules), including a rule-local dry run with Studio
  Assistant before saving the policy.
- For application and token-handling guidance, see the [developer guide](/security/developer-guide).