Skip to main content

Overview

The policy configuration file (policy.yml) defines role-based access control (RBAC) including role definitions, inheritance hierarchies, reusable templates, and collection-specific permissions with field-level masking.

Configuration Structure

version: "1.0"
roles:
  # Role definitions with inheritance
templates:
  # Reusable policy templates
policies:
  # Collection-specific policies
defaults:
  # Default policy settings

Role Definitions

Define roles with descriptions and inheritance relationships.
roles.<role_name>.description
string
Human-readable description of the role’s purpose.
roles.<role_name>.inherits
array
List of role names this role inherits permissions from. Supports multiple inheritance.

Example

roles:
  admin:
    description: "Full system access"
  
  manager:
    description: "Can manage users and documents within their tenant"
    inherits: ["user"]
  
  user:
    description: "Regular user with limited access"
    inherits: ["viewer"]
  
  viewer:
    description: "Read-only access"

Role Inheritance

Roles can inherit permissions from multiple parent roles:
roles:
  super_manager:
    description: "Manager with admin privileges"
    inherits: ["manager", "admin"]
Inheritance is resolved recursively, preventing circular dependencies.

Policy Templates

Define reusable permission templates that can be applied to multiple collections.
templates.<template_name>.<role_name>
object
Policy definition for a specific role within the template.

Common Templates

Tenant Isolation

templates:
  tenant_isolation:
    admin:
      actions: [create, read, update, delete]
    manager:
      actions: [create, read, update, delete]
      when: "resource.tenant_id == user.tenant_id"
    user:
      actions: [create, read, update]
      when: "resource.tenant_id == user.tenant_id"
    viewer:
      actions: [read]
      when: "resource.tenant_id == user.tenant_id"

Owner Access

templates:
  owner_access:
    admin:
      actions: [create, read, update, delete]
    manager:
      actions: [create, read, update, delete]
      when: "resource.tenant_id == user.tenant_id"
    user:
      actions: [create, read, update, delete]
      when: "resource.owner_id == user._id || resource.tenant_id == user.tenant_id"
    viewer:
      actions: [read]
      when: "resource.tenant_id == user.tenant_id"

Collection Policies

Define permissions for specific collections. Policies can use templates or define custom rules.
policies.<collection>.<role>.template
string
Name of the template to apply. Template settings can be overridden by other fields.
policies.<collection>.<role>.actions
array
required
List of allowed actions for this role on this collection.Valid actions:
  • create - Create new documents
  • read - Read existing documents
  • update - Modify existing documents
  • delete - Soft-delete documents
  • restore - Restore soft-deleted documents
  • aggregate - Run aggregation queries
policies.<collection>.<role>.when
string
Expression that must evaluate to true for the permission to apply. Uses MongoDB query syntax with access to user and resource context variables.
policies.<collection>.<role>.fields
object
Field-level access control configuration.

Example

policies:
  users:
    admin:
      actions: [create, read, update, delete]
    
    manager:
      actions: [read, update]
      when: "resource.tenant_id == user.tenant_id && resource.role != 'admin'"
      fields:
        deny_write: ["role", "tenant_id"]
    
    user:
      actions: [read, update]
      when: "resource._id == user._id"
      fields:
        deny_write: ["role", "tenant_id", "email"]
    
    viewer:
      actions: [read]
      when: "resource.tenant_id == user.tenant_id"
      fields:
        deny: ["email"]
        mask:
          name: partial

Field-Level Permissions

Control access to specific fields within documents.
fields.allow
array
Whitelist of fields the role can access. If specified, all other fields are denied.
fields.deny
array
Blacklist of fields the role cannot read. These fields are removed from query results.
fields.deny_write
array
Fields the role cannot modify. Read access is still allowed.
fields.mask
object
Field masking rules. Keys are field names, values are mask types.Mask types:
  • email - Mask email addresses (e.g., j***@example.com)
  • phone - Mask phone numbers (e.g., ***-***-1234)
  • partial - Show first and last characters only (e.g., J***n)

Examples

Allow-list fields

fields:
  allow: ["name", "email", "role"]

Deny sensitive fields

fields:
  deny: ["ssn", "salary", "bank_account"]

Read-only fields

fields:
  deny_write: ["created_at", "created_by", "tenant_id"]

Field masking

fields:
  mask:
    email: email
    phone: phone
    ssn: partial

Permission Expressions

The when field supports MongoDB query expressions with context variables:

Context Variables

  • user - Current user from JWT token (contains _id, role, tenant_id, etc.)
  • resource - Document being accessed

Expression Examples

Tenant isolation

when: "resource.tenant_id == user.tenant_id"

Owner-based access

when: "resource.owner_id == user._id"

Complex conditions

when: |
  (resource.owner_id == user._id || 
   resource.tenant_id == user.tenant_id) && 
  resource.status != 'archived'

Role-based restrictions

when: "resource.role != 'admin' && resource.tenant_id == user.tenant_id"

Using Templates

Apply a template and override specific settings:
policies:
  documents:
    manager:
      template: owner_access
      # Override template's when clause
      when: "resource.tenant_id == user.tenant_id && resource.status != 'archived'"
      # Add field restrictions
      fields:
        deny_write: ["created_by", "created_at"]

Default Settings

defaults.deny_all
boolean
default:"true"
Default deny all access unless explicitly granted. Set to false for permissive mode.
defaults.audit_log
boolean
default:"true"
Enable audit logging for all policy evaluations.

Example

defaults:
  deny_all: true
  audit_log: true

Complete Example

version: "1.0"

# Role definitions with inheritance
roles:
  admin:
    description: "Full system access"
  
  manager:
    description: "Can manage users and documents within their tenant"
    inherits: ["user"]
  
  user:
    description: "Regular user with limited access"
    inherits: ["viewer"]
  
  viewer:
    description: "Read-only access"

# Reusable policy templates
templates:
  tenant_isolation:
    admin:
      actions: [create, read, update, delete]
    manager:
      actions: [create, read, update, delete]
      when: "resource.tenant_id == user.tenant_id"
    user:
      actions: [create, read, update]
      when: "resource.tenant_id == user.tenant_id"
    viewer:
      actions: [read]
      when: "resource.tenant_id == user.tenant_id"

  owner_access:
    admin:
      actions: [create, read, update, delete]
    manager:
      actions: [create, read, update, delete]
      when: "resource.tenant_id == user.tenant_id"
    user:
      actions: [create, read, update, delete]
      when: "resource.owner_id == user._id || resource.tenant_id == user.tenant_id"
    viewer:
      actions: [read]
      when: "resource.tenant_id == user.tenant_id"

# Collection-specific policies
policies:
  users:
    admin:
      actions: [create, read, update, delete]
    
    manager:
      actions: [read, update]
      when: "resource.tenant_id == user.tenant_id && resource.role != 'admin'"
      fields:
        deny_write: ["role", "tenant_id"]
    
    user:
      actions: [read, update]
      when: "resource._id == user._id"
      fields:
        deny_write: ["role", "tenant_id", "email"]
    
    viewer:
      actions: [read]
      when: "resource.tenant_id == user.tenant_id"
      fields:
        deny: ["email"]
        mask:
          name: partial

  documents:
    admin:
      actions: [create, read, update, delete, restore]
    
    manager:
      actions: [create, read, update, delete, restore]
      when: "resource.tenant_id == user.tenant_id"
    
    user:
      actions: [create, read, update, delete]
      when: "resource.owner_id == user._id"
    
    viewer:
      actions: [read]
      when: "resource.tenant_id == user.tenant_id && resource.status == 'published'"

# Default settings
defaults:
  deny_all: true
  audit_log: true

Environment Variables

Policy configuration supports environment variable substitution:
policies:
  users:
    admin:
      actions: [create, read, update, delete]
      when: "resource.tenant_id == '${ENV.ADMIN_TENANT_ID}'"

Loading Policy Configuration

Load the policy configuration at server startup:
permission-mongo --policy=/path/to/policy.yml
Or use the PM_POLICY environment variable:
export PM_POLICY=/path/to/policy.yml
permission-mongo

Validation

The policy configuration is validated on load:
  • All roles referenced in policies must be defined
  • All templates referenced must exist
  • All actions must be valid
  • Circular role inheritance is detected and rejected
  • Field mask types must be valid

Build docs developers (and LLMs) love