Developer GuidesFoundations

Security and Access Control

How to think about identity, permissions, and trust in your operating model.

Access control in Poliglot follows a simple principle: everything is denied by default. Every action, every graph operation, every secret requires explicit permission. This guide covers how to think about designing that permission model for your matrix.

Why This Matters

Think of a context as a shared programming runtime. You've loaded packages from different sources (your own matrices, third-party vendor matrices, system matrix) into a single environment where the state is persistent and searchable. Every actor in that runtime can potentially query, invoke, and interact with everything else.

This is incredibly powerful. It allows all systems to act in unison within a single context, reasoning across domains and composing operations from multiple matrices. But it also means that without explicit access control, a poorly designed or malicious matrix could access sensitive data or invoke actions it shouldn't. The permission model exists to give you fine-grained control over what each actor can see and do in this shared environment.

Agents as Process Identities

An agent is not an autonomous actor. It's a process identity: the security principal under which your matrix's internal operations execute. When RARS runs an action from your matrix, the resulting process runs under your agent's identity. This is analogous to a service account in cloud infrastructure.

You declare the agent in matrix.ttl and assign it as the matrix's proprietor:

tasks:TaskAgent
    a rars-iam:Agent ;
    rdfs:label "Task Agent" ;
    skos:definition "Security principal for task management operations." .

tasks:
    a rars-mtx:Matrix ;
    rars-mtx:proprietor tasks:TaskAgent .

The agent needs permissions to do its work: reading and writing statements for its domain resources, resolving secrets, and invoking actions. When a script handler internally calls other actions from the same matrix, the agent is the caller for those sub-invocations and needs the appropriate permissions. External callers (users, RARS, other matrix agents) who invoke your top-level actions are a separate concern controlled by resource policies (covered below).

Requested vs Granted Permissions

The roles and policies you define in your matrix spec are requested permissions. They declare what your matrix needs to operate. But the workspace administrator ultimately controls what is actually granted.

When a matrix is installed in a workspace, the admin can review, modify, or revoke any of the requested roles and policies. This means a matrix can't grant itself unchecked access just by declaring broad policies in its spec. The admin has final authority.

This is similar to how mobile apps request permissions at install time. The matrix declares what it needs, the workspace admin decides what to allow. If the admin tightens permissions after deployment, the matrix's operations will fail where they no longer have access, and RARS will surface those failures clearly.

Two Kinds of Policies

The platform uses a dual-policy model. Two separate checks must both pass for an operation to succeed:

Identity policies are attached to roles. They define what holders of that role can do. Think of them as outbound permissions: "principals with this role are allowed to perform these actions."

Resource policies are attached to resources (actions, secrets, named graphs). They define who can access the resource. Think of them as inbound permissions: "only principals with these roles can invoke this action."

Both must allow for access to be granted. An identity policy that allows invoking an action is not enough if the action's resource policy doesn't also allow the caller's role. This separation lets you control permissions from both directions.

Designing Roles

A matrix typically defines at least two roles with different purposes:

An internal role for the agent's process operations. This role gets identity policies for reading and writing statements scoped to the matrix's own resources. The agent needs this to do its work at runtime.

One or more external roles for callers who invoke the matrix's actions. These roles get identity policies for action invocation, scoped to specific actions. Users and agents from other matrices can be assigned these roles.

Example: Separating internal and external access

# Internal: the agent's process identity needs graph access
tasks:TaskAgentRole
    a rars-iam:Role, owl:NamedIndividual ;
    rdfs:label "Task Agent Role" ;
    rars-iam:hasPolicy tasks:StatementAccessPolicy .

tasks:StatementAccessPolicy
    a rars-iam:IdentityPolicy ;
    rars-iam:effect rars-iam:Allow ;
    rars-iam:action rars-os:ReadStatement, rars-os:WriteStatement ;
    rars-iam:condition [
        a rars-iam:Condition ;
        rars-iam:scope rars-iam:Resource ;
        rars-iam:hasValue tasks:
    ] .

tasks:TaskAgent rars-iam:hasRole tasks:TaskAgentRole .

# External: callers who can invoke task actions
tasks:TaskManagerRole
    a rars-iam:Role, owl:NamedIndividual ;
    rdfs:label "Task Manager" ;
    rars-iam:hasPolicy tasks:InvokeTaskActionsPolicy .

tasks:InvokeTaskActionsPolicy
    a rars-iam:IdentityPolicy ;
    rars-iam:effect rars-iam:Allow ;
    rars-iam:action rars-act:InvokeAction ;
    rars-iam:resource tasks:ListTasks, tasks:CreateTask .

The agent role grants statement access scoped to the tasks namespace. The manager role grants action invocation for specific actions. These are separate concerns: the agent needs to read and write task data, while managers need to trigger operations. A user assigned the TaskManagerRole can invoke actions, but the graph operations happen under the agent's identity.

Situational Access Control

Policies without conditions are static: they allow or deny based on the caller's role and nothing else. Conditions make policies situational: they evaluate the current state of the graph, the authorization context, or the target resource before making a decision.

This is where the security model becomes powerful. Because the graph is a live, queryable representation of your operating model's state, conditions can express access rules like "allow this action only when the work order is in draft status" or "deny access to financial data outside of business hours" or "allow termination only if the employee's manager has signed off." The situation determines the access, not just the identity.

Namespace scoping

The most common pattern scopes a policy to resources from a specific matrix:

rars-iam:condition [
    a rars-iam:Condition ;
    rars-iam:scope rars-iam:Resource ;
    rars-iam:hasValue tasks:
] .

This ensures the policy only applies to resources defined by the tasks matrix. Without it, a ReadStatement policy would grant access to statements from every matrix in the workspace.

Situational conditions with SPARQL

For conditions that depend on the live state of the graph, use SPARQL ASK queries. The condition's scope is bound to ?scope in the query, and you can query any data in the graph to make the authorization decision.

# Only allow approving a work order if a risk assessment has been completed
wo:RequiresAssessmentCondition
    a rars-iam:Condition ;
    rars-iam:scope rars-iam:Resource ;
    rars-iam:sparql [
        rars-os:ask """
            ASK {
                ?scope a wo:WorkOrder .
                ?scope wo:riskAssessment ?assessment .
                ?assessment wo:status wo:Completed .
            }
        """
    ] .

wo:ApproveWorkOrderPolicy
    a rars-iam:IdentityPolicy ;
    rars-iam:effect rars-iam:Allow ;
    rars-iam:action rars-act:InvokeAction ;
    rars-iam:resource wo:ApproveWorkOrder ;
    rars-iam:condition wo:RequiresAssessmentCondition .

In this example, the ApproveWorkOrder action can only be invoked when the work order's risk assessment is completed. The condition queries the graph at authorization time, so it reflects the current state, not a static configuration. If someone tries to approve a work order before the assessment is done, the policy evaluation fails regardless of their role.

This pattern enables governance rules that are impossible in traditional RBAC systems. The graph is the situation, and the conditions query it in real time.

Delegation depth

You can also scope based on the authorization context itself, such as how deep in the delegation chain the current operation is:

rars-iam:condition [
    a rars-iam:Condition ;
    rars-iam:scope rars-iam:AuthorizationContext ;
    rars-iam:sparql [
        rars-os:ask """
            ASK {
                ?scope rars-iam:delegationDepth ?depth .
                FILTER(?depth <= 2)
            }
        """
    ]
] .

This restricts the policy to operations within two levels of delegation depth, preventing deep cross-matrix call chains from inheriting permissions.

Resource Policies

Resource policies are the other half of the dual-policy model. They're attached directly to resources and control who can access them. While the most common use is controlling action invocation, resource policies can be attached to any resource that needs inbound access control.

On actions

Control who can invoke specific actions:

tasks:TaskActionsResourcePolicy
    a rars-iam:ResourcePolicy ;
    rars-iam:effect rars-iam:Allow ;
    rars-iam:action rars-act:InvokeAction ;
    rars-iam:role tasks:TaskManagerRole .

tasks:ListTasks rars-iam:hasPolicy tasks:TaskActionsResourcePolicy .
tasks:CreateTask rars-iam:hasPolicy tasks:TaskActionsResourcePolicy .

When no resource policy exists on an action, the identity check alone is sufficient. Add resource policies when you need to explicitly restrict who can call specific actions.

On secrets

Control who can resolve secret values. Secrets are required to have at least one resource policy (enforced by SHACL at deployment), so you can never accidentally expose a secret by omitting its policy:

tasks:TaskAPIKeyPolicy
    a rars-iam:ResourcePolicy ;
    rars-iam:effect rars-iam:Allow ;
    rars-iam:action rars-scrt:ResolveSecret ;
    rars-iam:role tasks:TaskAgentRole .

tasks:TaskAPIKey rars-iam:hasPolicy tasks:TaskAPIKeyPolicy .

On named graphs

Control who can read from or write to specific named graphs. This is how you protect sensitive data segments:

people:SalaryGraphReadPolicy
    a rars-iam:ResourcePolicy ;
    rars-iam:effect rars-iam:Allow ;
    rars-iam:action rars-os:ReadGraph ;
    rars-iam:role people:HRManager, people:HRAdmin .

people:SalaryGraphWritePolicy
    a rars-iam:ResourcePolicy ;
    rars-iam:effect rars-iam:Allow ;
    rars-iam:action rars-os:WriteGraph ;
    rars-iam:role people:HRManager, people:HRAdmin .

people:SalaryGraph rars-iam:hasPolicy
    people:SalaryGraphReadPolicy,
    people:SalaryGraphWritePolicy .

In this example, salary data lives in a separate named graph with its own access policies. Only HR managers and admins can read or write it, even if other roles have broad statement access to the people matrix.

Deny Always Wins

A single explicit Deny anywhere in the policy chain blocks access, regardless of how many Allow policies exist. Use Deny policies sparingly and intentionally:

# Block a specific role from a sensitive action
tasks:BlockExternalAccessPolicy
    a rars-iam:ResourcePolicy ;
    rars-iam:effect rars-iam:Deny ;
    rars-iam:action rars-act:InvokeAction ;
    rars-iam:role tasks:ReadOnlyRole .

tasks:DeleteTask rars-iam:hasPolicy tasks:BlockExternalAccessPolicy .

Deny is useful for creating exceptions to broad Allow policies without restructuring your entire role hierarchy.

Trust Policies

Trust policies control which matrices can assume an agent. By default, only the matrix that defines the agent can use it. If another matrix needs to operate under your agent's identity, it must be explicitly trusted.

tasks:TaskAgent
    rars-iam:hasTrustPolicy tasks:TrustedConsumers .

tasks:TrustedConsumers
    a rars-iam:TrustPolicy ;
    rars-iam:effect rars-iam:Allow ;
    rars-iam:consumer reporting:ReportingMatrix .

This allows the reporting matrix to assume the task agent's identity for operations that require task graph access. Trust policies are the mechanism for controlled cross-matrix collaboration.

Secrets

Secrets are E2E encrypted credentials managed per workspace. They're declared in your matrix spec, and workspace administrators set the actual values through the console.

Secret values never exist in the context graph. When a secret is referenced in a service integration, RARS writes a marker ({{secret:<uri>}}) into the graph instead of the actual value. During request materialization (when RARS constructs an outbound HTTP request), the markers are resolved off-graph to the real values. The AI only ever sees the marker, never the credential itself. The only way a secret value could enter the AI's context is if a user manually pastes it into the conversation or the credential exists outside of Poliglot's secret management system.

Two types:

  • Managed secrets: long-lived credentials like API keys and database passwords. Stored and encrypted by the platform.
  • Temporary secrets: short-lived tokens like session cookies, provided at runtime by the caller.

Secrets must have explicit resource policies. This is enforced by SHACL validation at deployment. You can't accidentally expose a secret by omitting its policy.

tasks:TaskAPIKey
    a rars-scrt:ManagedSecret ;
    rdfs:label "Task API Key" ;
    rars-scrt:description "API key for the task management backend." ;
    rars-iam:hasPolicy tasks:TaskAPIKeyPolicy .

tasks:TaskAPIKeyPolicy
    a rars-iam:ResourcePolicy ;
    rars-iam:effect rars-iam:Allow ;
    rars-iam:action rars-scrt:ResolveSecret ;
    rars-iam:role tasks:TaskAgentRole .

This restricts secret resolution to the agent's role only. Even if another principal has a broad identity policy, they can't resolve this secret unless their role is listed in the resource policy.

When a service integration constructs a request, the materialized request in the graph looks like this:

# What exists in the graph (visible to the AI)
_:request a rars-http:Request ;
    rars-http:method "POST" ;
    rars-http:hasHeader [
        rars-http:headerName "X-Api-Key" ;
        rars-http:headerValue "{{secret:https://example.org/spec/tasks#TaskAPIKey}}"
    ] .

The marker {{secret:...}} is all the AI ever sees. When RARS sends the actual HTTP request to your service, it resolves the marker to the real API key off-graph and injects it into the outbound request. The resolved value never touches the context graph.

See Connecting Systems of Record for how secrets are declared and used in service integration request headers.

Escalation

When an agent's identity check fails for an action, RARS doesn't immediately reject the request. It checks whether the origin principal (the user who started the conversation) has the required permission. If the user does, RARS presents an escalation request to the user for approval.

The user sees the action being attempted, the subject it's operating on, and the payload fields, giving them full context to make an informed decision. This is a native human-in-the-loop mechanism built into the authorization model.

The flow:

  1. The agent's identity check fails for an action
  2. The platform evaluates the same action and resource against the origin user's roles
  3. If the origin user also lacks permission, the request is rejected (no one to escalate to)
  4. If the origin user has permission, the invocation is suspended and an escalation request is sent
  5. The user reviews the action, subject, and payload, then approves or rejects
  6. If approved, the action proceeds. If rejected or timed out, the authorization error propagates.
  7. An rars-iam:Escalation entity is created as a permanent audit trail of the decision

Escalation only applies to action invocation at runtime. Explicit Deny policies on the origin user still block escalation (deny always wins). Escalation doesn't increase anyone's permissions. It bridges the gap between what the agent can do and what the user has already been granted.

Cross-Matrix Access

When Matrix A's actions invoke actions from Matrix B, there is no permission inheritance. Matrix B's operations execute under Matrix B's agent identity, and Matrix B's policies are evaluated independently. The delegation chain is tracked for audit purposes, but permissions don't flow through.

This means:

  • Importing a matrix grants vocabulary access (types and properties), not capability access
  • To invoke another matrix's actions, the caller needs explicit permission from both identity and resource policies
  • Each matrix is a security boundary

Designing for Least Privilege

A few principles for designing your IAM:

  • Separate internal from external roles: the agent's process role and the external invocation role serve different purposes and should have different permissions
  • Be specific with resource policies: list the exact roles that can invoke each action rather than omitting the role (which means any role)
  • Require resource policies on secrets: RARS enforces this, but think about it during design. Every secret should have an explicit access policy.
  • Use Deny sparingly: prefer structuring roles and policies so that permissions are naturally scoped, rather than relying on Deny to patch gaps

Summary

  • Everything denied by default: access requires explicit Allow from both identity and resource policies
  • Agents are process identities: they execute operations, they don't make autonomous decisions about permissions
  • Requested vs granted: the matrix declares what it needs, the workspace admin controls what's actually allowed
  • Dual-policy model: identity policies (on roles) and resource policies (on resources) must both allow
  • Situational access control: conditions query the live graph state at authorization time. Access decisions can depend on the current situation, not just the caller's identity.
  • Deny always wins: one Deny blocks access regardless of other policies
  • Secrets require explicit policies: enforced at deployment, no accidental exposure
  • Cross-matrix is explicit: importing grants vocabulary, not capabilities. Each matrix is a security boundary.
  • Escalation is native human-in-the-loop: when the agent lacks permission but the origin user has it, the user sees the full action context and decides whether to allow it

On this page