Pulp EngineDocument Rendering
Get started

Access control

Pulp Engine supports tenant, folder and template grants in file, PostgreSQL and SQL Server storage. Grants add permissions; there are no deny rules. Named users, OIDC users and database API keys have stable principals. A shared editor session’s actor is an audit label and cannot acquire a named user’s grants or ownership.

Roles and actions

Role Permissions
viewer template:read, render:run for published labels
author / editor template:read, template:write, render:preview, asset:read, asset:write, ai:use, folder:write
publisher Author plus label:promote, template:restore, render:run, render:draft
scheduler template:read, render:run, schedule:write-own
auditor template:read, audit:read, usage:read, schedule:read
admin All 22 actions
none No base grant; explicit grants and ownership still apply
Legacy render render:run, render:draft
Legacy preview render:preview

The full action catalogue is:

Area Actions
Templates template:read, template:write, template:delete, template:restore
Publication label:promote
Rendering render:run, render:draft, render:preview
Assets and AI asset:read, asset:write, ai:use
Folders folder:write, folder:delete
Schedules schedule:read, schedule:write, schedule:write-own
Governance audit:read, audit:purge, usage:read, user:admin, role:admin, ops:admin

Asset, AI, audit, usage, user, role and operations actions, and schedule:read, require tenant grants. Assets have no per-resource ACL in this release. Putting one of these actions in a folder or template binding does not grant it globally.

Inheritance and ownership

A user’s base role applies throughout their tenant. Explicit bindings may target the tenant (resourceId: "*"), a folder ID or a template key. Folder grants include descendants: /fin matches /fin/invoices, and does not match /finance. Tenant-only actions are ignored below tenant scope.

RBAC_OWNER_ROLE=author implicitly allows a named user to edit and preview their own template, including a root template. Only template, rendering and publication actions can come from ownership. Ownership never grants global assets or AI, or folder administration. Set RBAC_OWNER_ROLE=none to disable implicit ownership. Other supported owner roles are viewer and publisher.

Unreadable templates return 404. An action denied on a readable template returns 403. List totals and pagination include only readable templates. Render batches check every item before doing any render work. A viewer needs a published label; HEAD, pinned versions and @latest require render:draft. An omitted label can use the configured default live label.

Schedulers see and manage their own schedules. Auditors can read every schedule; schedule:write grants management of all schedules. Schedule create, update and trigger also require render:run on the template. Scheduled executions do not re-check the creator’s grants at fire time in this release.

Purging a template, recreating a deleted key, or deleting a folder removes its bindings. Moving a template checks both its source and destination permissions. Folder grants follow the current folder hierarchy.

Administration and discovery

GET /auth/me requires authentication and returns the principal, legacy scope, base role, effective bindings, tenant actions and owner role. It omits tenantId. Use expand=permissions on template detail or list reads for effective actions.

The admin console has a Roles page for custom roles. The editor’s folder menu and template details offer Manage access to users with role:admin. Inherited grants show their source and must be removed at that source.

/admin/roles supports GET and POST; /admin/roles/:name supports PUT and DELETE. /admin/role-bindings supports GET and POST; /admin/role-bindings/:id supports DELETE. Binding lists accept principal/resource filters and includeInherited. All these operations require role:admin. Built-in roles are immutable. A custom role in use cannot be deleted. Successful mutations write rbac_mutation audits. Database changes and their audit insert commit in one transaction. File mode is single-process: atomic file replacement plus synchronous audit append rolls back ordinary audit failures; it cannot provide a transaction across two files after a process or disk failure. Resource cleanup revokes grants before deleting files.

TypeScript: client.auth.me(), client.admin.roles, client.admin.roleBindings. Python: client.auth.me(), client.admin.roles, client.admin.role_bindings. The APIs accept built-in user roles plus none; generated clients that assumed only admin | editor must regenerate their types.

Credentials, OIDC and cache

Existing unmodified API-key scopes retain the previous route matrix. The new role engine applies when a key has an explicit different role, a binding or a static restrictTo restriction. API_KEYS_JSON can restrict a scope to folder paths or template keys, for example:

[{"name":"invoices","key":"replace-me","scope":"render","tenantId":"default","restrictTo":{"folders":["/Finance"],"templates":["public-summary"]}}]

Restricted static keys cannot mint editor or console sessions. Named-user and OIDC sessions use the user’s personal signing key. Existing mixed-mode named sessions signed with a shared key must sign in again to regain named grants. Moving a user to another tenant invalidates their previous tenant session.

OIDC_ROLE_MAPPINGS_JSON (or _FILE) maps exact group names to roles and optional folder paths. Stored group bindings also apply. Unknown folders are logged and grant nothing. Current groups are persisted on every login. OIDC_ROLE_SYNC=true also updates the base role from the legacy admin/editor group rules on each login. Its default is false for compatibility; hardened OIDC deployments warn until synchronization is enabled. With synchronization disabled, a previous base admin role can survive removal from the admin group.

Role/binding writes invalidate the local resolver cache. Other replicas converge within RBAC_CACHE_TTL_MS (default 10000; range 0–300000). Zero disables caching. User groups and credentials have their own registry refresh behavior. The editor refreshes permissions on focus and every 10 seconds; the API always enforces them. The editor falls back to legacy scope controls only when /auth/me returns 404.