Files
cleveragents-core/docs/reference/permissions.md
T
Luis Mendes 64af753aab
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / build (pull_request) Successful in 17s
CI / quality (pull_request) Successful in 19s
CI / security (pull_request) Successful in 32s
CI / typecheck (pull_request) Successful in 36s
CI / integration_tests (pull_request) Successful in 2m40s
CI / unit_tests (pull_request) Successful in 11m42s
CI / docker (pull_request) Successful in 40s
CI / benchmark-regression (pull_request) Successful in 21m35s
CI / coverage (pull_request) Successful in 45m10s
CI / lint (push) Successful in 12s
CI / quality (push) Successful in 18s
CI / build (push) Successful in 23s
CI / security (push) Successful in 32s
CI / typecheck (push) Successful in 57s
CI / benchmark-regression (push) Has been skipped
CI / integration_tests (push) Successful in 2m43s
CI / unit_tests (push) Successful in 12m13s
CI / benchmark-publish (push) Successful in 12m19s
CI / docker (push) Successful in 1m2s
CI / coverage (push) Successful in 1h26m6s
feat(security): add permission system
Implemented namespace/project/plan/skill permission model with role
bindings (owner/admin/editor/viewer) and default deny policy. Added
enforcement hooks at CLI/service boundaries that are server-only; local
mode returns permissive defaults. Includes role enums, permission check
service, role matrix documentation.

Includes Behave BDD scenarios, Robot integration tests, ASV benchmarks,
and reference documentation.

ISSUES CLOSED: #344
2026-02-28 20:21:18 +00:00

5.4 KiB

Permission System

Overview

CleverAgents implements a namespace/project/plan/skill permission model with role bindings and a default-deny policy. An enforce_permission decorator is available for gating function calls; however, the decorator is not yet wired into CLI or service call sites — that integration is deferred to a future pass.

Server-only enforcement: in local mode the permission system returns permissive defaults (all actions allowed). Server-side role evaluation uses the role bindings registered on the PermissionService.

Roles

Role Description
owner Full control — all actions on all scopes
admin Administrative access — all actions
editor Read, write, and execute
viewer Read-only access

OWNER and ADMIN currently grant the same set of actions. The distinction is intentional — OWNER designates the resource creator or namespace owner, while ADMIN is a delegated administrative role. Future server-mode enforcement may differentiate them (e.g. ownership transfer, namespace deletion) without requiring a schema change.

Role → Action Matrix

Action Owner Admin Editor Viewer
read yes yes yes yes
write yes yes yes
execute yes yes yes
delete yes yes
admin yes yes

Scopes

Permissions can be scoped to:

  • Namespace — top-level organisational boundary
  • Project — a project within a namespace
  • Plan — an execution plan within a project
  • Skill — a skill registered in the system

Scope Hierarchy (Known Limitation)

Scopes are currently evaluated with exact match semantics. A binding at namespace scope does not automatically cascade to project, plan, or skill scopes within that namespace. Hierarchical scope resolution (namespace > project > plan > skill) is deferred to a future server-mode release.

Role Bindings

A RoleBinding associates a principal (user or service identifier) with a PermissionRole at a specific PermissionScope and scope ID.

Duplicate bindings (same principal, role, scope, and scope_id) are rejected by add_binding().

from cleveragents.domain.models.core.permission import (
    PermissionRole,
    PermissionScope,
    RoleBinding,
)

binding = RoleBinding(
    principal="alice",
    role=PermissionRole.EDITOR,
    scope=PermissionScope.PROJECT,
    scope_id="my-project",
)

Permission Policy

The PermissionPolicy model controls the default-deny behaviour and optional allow-overrides:

from cleveragents.domain.models.core.permission import (
    PermissionPolicy,
    RoleBinding,
)

policy = PermissionPolicy(
    default_deny=True,
    allow_overrides=[binding],
)

Permission Service

PermissionService is the entry point for checking permissions:

from cleveragents.application.services.permission_service import (
    PermissionService,
)
from cleveragents.domain.models.core.permission import (
    PermissionAction,
    PermissionScope,
)

svc = PermissionService()
check = svc.check_permission(
    principal="alice",
    action=PermissionAction.WRITE,
    scope=PermissionScope.PROJECT,
    scope_id="my-project",
)
print(check.result)  # True in local mode

Module-Level Default Service

A module-level default PermissionService is available so that the enforce_permission decorator (and other call sites) share a single service instance with pre-registered bindings:

from cleveragents.application.services.permission_service import (
    get_default_permission_service,
    set_default_permission_service,
    PermissionService,
)

# During application startup:
svc = PermissionService(bindings=[...])
set_default_permission_service(svc)

# Later — the decorator (and any code that calls
# get_default_permission_service()) will use this instance.

Enforcement Decorator

The enforce_permission decorator is available for gating function calls. It is not yet applied to any CLI or service entry point — call sites will be wired in a future integration pass:

from cleveragents.application.services.permission_service import (
    enforce_permission,
)
from cleveragents.domain.models.core.permission import (
    PermissionAction,
    PermissionScope,
)

@enforce_permission(
    PermissionAction.WRITE,
    PermissionScope.PROJECT,
    "my-project",
)
def create_resource():
    ...

In local mode the decorator is a no-op. In server mode it raises PermissionError when the check fails. When no explicit service is passed, the decorator uses the module-level default service (see above).

Local Mode Behaviour

When CLEVERAGENTS_SERVER_MODE is unset (or not true), the system operates in local mode:

  • PermissionService.is_local_mode() returns True
  • All permission checks return result=True
  • get_role_bindings() returns a synthetic OWNER binding
  • The DEFAULT_LOCAL_ROLE_MAPPING maps "*"PermissionRole.OWNER

Audit Logging

Permission denials in server mode are logged as permission_denied events via structlog at the WARNING level. Binding additions and removals are logged as binding_added / binding_removed at INFO level.