RBAC and Authorization

Enterprise reference for the current CodeGraph authentication, authorization, and machine-access runtime.

Scope and Source of Truth

This document describes the implementation currently present under src/.

Canonical code surfaces:

Area Source
Permission enum and role mapping src/api/auth/tokens/permissions.py
Request auth context and FastAPI auth dependencies src/api/auth/providers/middleware.py
JWT creation, validation, and blacklist src/api/auth/tokens/jwt_handler.py
User API-key helpers src/api/auth/tokens/api_keys.py
Machine credential catalog and authorization src/api/auth/machine/machine_access.py
Machine audit, project scope, and optional mTLS binding src/api/auth/machine/machine_runtime.py
API-key/service-account middleware src/api/middleware/scope_enforcement.py
Path validation middleware src/api/middleware/path_validation.py
Local, OAuth, LDAP, API-key, service-account routes src/api/routers/auth_suite/*
User, API-key, tenant, project-access, and service-account models src/api/database/models_*.py

Compatibility imports are intentionally preserved by src/api/auth/__init__.py, which extends the package path with machine, providers, and tokens. New references should prefer the physical module paths above.

Runtime Model

CodeGraph uses three related authorization layers:

Layer What it controls Main implementation
User RBAC Human user role permissions for route dependencies and application logic Permission, Role, ROLE_PERMISSIONS, has_permission()
Machine scopes API-key and service-account access to API/MCP routes MachinePrincipal, API_ROUTE_SCOPE_MAP, MCP_ROUTE_SCOPE_MAP, authorize_machine_request()
Tenant/project scope Group membership and selected project access in multi-tenant installations GroupRole, ProjectScopeMode, UserGroupAccess, UserProjectAccess, project repositories

Authentication methods produce either a user identity or a machine principal. Authorization then checks role permissions, raw scopes, interface allowlists, tenant/project restrictions, and middleware-specific security controls.

User Roles and Permissions

Roles

User roles are defined twice for separate boundaries:

Enum Source Use
Role src/api/auth/tokens/permissions.py Auth dependency permission checks
UserRole src/api/database/models_base.py Persisted user role in the application database

Both currently use the same values:

Role Value Meaning
VIEWER viewer Read-only product access
ANALYST analyst Viewer plus query/scenario execution, session writes, API-key creation, basic digital-employee task work
REVIEWER reviewer Analyst plus review integrations and higher-risk digital-employee operations
ADMIN admin Platform admin role

src/api/dependencies.py::require_role() treats this as an ordered hierarchy: viewer < analyst < reviewer < admin.

Permission Behavior

Permission currently contains 31 values, including admin:all.

Important runtime details:

  • ROLE_PERMISSIONS[Role.ADMIN] stores only {Permission.ADMIN_ALL}.
  • get_role_permissions(Role.ADMIN) returns that raw set; it does not expand to every permission.
  • has_permission(Role.ADMIN, ...) returns True for every requested permission.
  • Explicit scopes also grant permissions when the scope string matches the required Permission.value.
  • The admin:all scope grants all permission checks in has_permission() and bypasses machine scope checks.

Permission Catalog

Permission Enum name Viewer Analyst Reviewer Admin
scenarios:read SCENARIOS_READ x x x x
scenarios:execute SCENARIOS_EXECUTE x x x
query:execute QUERY_EXECUTE x x x
query:validate QUERY_VALIDATE x x x
review:execute REVIEW_EXECUTE x x
review:github REVIEW_GITHUB x x
review:gitlab REVIEW_GITLAB x x
sessions:read SESSIONS_READ x x x x
sessions:write SESSIONS_WRITE x x x
sessions:delete SESSIONS_DELETE x x x
history:read HISTORY_READ x x x x
history:export HISTORY_EXPORT x x x
users:read USERS_READ x
users:write USERS_WRITE x
users:delete USERS_DELETE x
api_keys:read API_KEYS_READ x x x
api_keys:write API_KEYS_WRITE x x x
api_keys:delete API_KEYS_DELETE x
stats:read STATS_READ x x x x
metrics:read METRICS_READ x
digital_employees:read DIGITAL_EMPLOYEES_READ x x x
digital_employees:task:write DIGITAL_EMPLOYEES_TASK_WRITE x x x
digital_employees:task:high_risk DIGITAL_EMPLOYEES_HIGH_RISK_TASK x x
digital_employees:handoff:write DIGITAL_EMPLOYEES_HANDOFF_WRITE x x
digital_employees:approval:write DIGITAL_EMPLOYEES_APPROVAL_WRITE x x
digital_employees:audit:write DIGITAL_EMPLOYEES_AUDIT_WRITE x x
digital_employees:external_publish:write DIGITAL_EMPLOYEES_EXTERNAL_PUBLISH x x
digital_employees:lifecycle:write DIGITAL_EMPLOYEES_LIFECYCLE_WRITE x
digital_employees:control_tower:repair DIGITAL_EMPLOYEES_CONTROL_TOWER_REPAIR x
digital_employees:admin DIGITAL_EMPLOYEES_ADMIN x
admin:all ADMIN_ALL x

ROLE_PERMISSIONS does not currently include USERS_*, API_KEYS_DELETE, METRICS_READ, the admin-only digital-employee permissions, or ADMIN_ALL for non-admin roles. They are effectively admin-only through Role.ADMIN or an explicit admin:all scope.

Permission Helpers

Use src.api.auth.permissions for role/scope checks:

from src.api.auth.permissions import (
    Permission,
    Role,
    get_default_scopes_for_api_key,
    get_role_permissions,
    has_all_permissions,
    has_any_permission,
    has_permission,
    validate_scopes,
)

Key behavior:

  • get_default_scopes_for_api_key() returns scenarios:read, scenarios:execute, query:execute, sessions:read, sessions:write, history:read.
  • validate_scopes() filters unknown strings against the Permission enum.
  • has_permission() considers role grants first and explicit scope strings second.

FastAPI Auth Context

AuthContext is defined in src/api/auth/providers/middleware.py.

Fields:

Field Meaning
user_id Human user id when available
subject_id Canonical subject id; user id or service-account id
subject_type user or service_account
username Display name or username
role Optional Role for user RBAC
scopes Raw permission/scope strings
auth_method jwt, api_key, service_account_api_key, iam, local_plugin, or none
group_id Optional tenant/group scope
project_id Optional project scope for service accounts
service_account_id Service-account id when authenticated as one
credential_id API key or service-account credential id
allowed_interfaces Interfaces allowed for the authenticated subject

The main dependency flow is:

  1. Reuse request.state.machine_principal if ScopeEnforcementMiddleware has already validated an API key.
  2. Check X-YC-IAM-Token if IAM is enabled.
  3. Check JWT bearer token and reject blacklisted JWT ids.
  4. Check X-API-Key as a machine credential.
  5. Return unauthenticated context when no valid credentials exist.

Use these dependencies for route code:

from src.api.auth.middleware import (
    get_auth_context,
    get_current_user,
    get_optional_user,
    require_admin,
    require_analyst,
    require_any_permission,
    require_auth,
    require_auth_or_localhost,
    require_permission,
    require_reviewer,
    require_role,
)

require_auth_or_localhost() allows local plugin traffic only when security.local_dev_unauthenticated_surfaces_enabled is true and the request is loopback. Mutating local-plugin requests must include X-Actor-Agent-Id and X-Task-Id, and generic agent ids such as codex or codex-headless are rejected.

src/api/dependencies.py also contains database-backed dependencies returning ORM User records. Auth-suite routes use those dependencies for account administration and API-key CRUD.

Authentication Methods

Local Username and Password

Routes are mounted under /api/v1/auth:

Route Purpose
POST /register Create a local user and return tokens
POST /login Login alias for token issuance
POST /token Primary username/password token endpoint
POST /refresh Rotate refresh token and issue new tokens
DELETE /logout Blacklist current bearer token
POST /oauth/token RFC 6749-compatible password and refresh-token grants

Local passwords are hashed with bcrypt. New self-registered users receive the analyst role.

JWT Bearer Tokens

JWT code lives in src/api/auth/tokens/jwt_handler.py.

TokenPayload fields:

Field Meaning
sub Subject user id
jti JWT id
exp Expiration
iat Issued-at time
type access or refresh
scopes Raw permission scope strings
role Optional user role string
group_id Optional group scope

Defaults:

  • Access token TTL: 30 minutes when expires_delta is omitted.
  • Refresh token TTL: 7 days.
  • Signing settings come from src/api/config.py (API_JWT_ALGORITHM, JWT secret resolution).

Revocation:

  • blacklist_token() stores a SHA-256 fingerprint of the JTI in memory and in the token_blacklist table.
  • is_token_blacklisted() checks memory first, then the database.
  • load_blacklist_cache() loads non-expired blacklist rows at startup.
  • _blacklist_sync_task() refreshes the in-memory cache periodically.

User API Keys

User API keys are created through /api/v1/me/api-keys and transported as X-API-Key.

Format:

rag_<8 hex chars>_<48 hex chars>

Implementation details:

  • generate_api_key() returns (full_key, prefix, key_hash).
  • The prefix starts with rag_.
  • The secret is secrets.token_hex(24).
  • The stored hash is SHA-256.
  • verify_api_key() uses secrets.compare_digest().
  • ApiKey.group_id optionally limits a key to one tenant group.
  • User API keys authenticate as MachinePrincipal(subject_type="user", auth_method="api_key").
  • User API keys allow api and mcp interfaces by default.
  • ApiKeyInfo is the secret-free response model.
  • ApiKeyWithSecret extends ApiKeyInfo and is returned only when the key is created.

API-key CRUD:

Route Purpose
POST /api/v1/me/api-keys Create a key for the current user
GET /api/v1/me/api-keys List current user’s keys without secrets
DELETE /api/v1/me/api-keys/{key_id} Revoke one owned key

The create request defaults to ["scenarios:read", "query:execute"]; the lower-level helper get_default_scopes_for_api_key() returns a broader legacy default list. Route behavior is the authoritative runtime behavior.

Service Accounts

Service accounts use the same X-API-Key transport with a distinct credential prefix:

svc_<8 hex chars>_<48 hex chars>

Service-account records are persisted in service_accounts; credentials are persisted in service_account_credentials.

Implemented controls:

  • Admin-only service-account management.
  • Policy templates: ci, portal, bot, agent.
  • Explicit scopes and allowed_interfaces.
  • Optional group_id or project_id restriction.
  • Account expiry and credential expiry.
  • Credential rotation without immediate old-credential revocation.
  • Active credential quota through security.service_account_max_active_credentials.
  • Revoke and deactivate flows.
  • Machine contract header: X-CodeGraph-Machine-Contract, currently 2026-03-v1.
  • Optional proxy-terminated mTLS binding for configured interfaces.
  • Unified machine audit events through AuditAction.MACHINE_ACCESS.

Admin routes:

Route Purpose
GET /api/v1/admin/identity/service-accounts/action-catalog Return route scopes, machine contract, policy templates, and security limits
POST /api/v1/admin/identity/service-accounts Create service account and initial credential
GET /api/v1/admin/identity/service-accounts List service accounts
GET /api/v1/admin/identity/service-accounts/{service_account_id} Inspect service account and credential metadata
POST /api/v1/admin/identity/service-accounts/{service_account_id}/rotate Issue a new credential
POST /api/v1/admin/identity/service-accounts/{service_account_id}/credentials/{credential_id}/revoke Revoke one credential
POST /api/v1/admin/identity/service-accounts/{service_account_id}/deactivate Disable account and revoke credentials

Policy templates:

Template Scopes Interfaces
ci query:execute, scenarios:read, mcp:access api, mcp
portal stats:read, history:read api
bot query:execute, scenarios:read, mcp:access api, mcp
agent query:execute, scenarios:execute, review:execute, mcp:access, digital_employees:read, digital_employees:task:write api, mcp

OAuth2/OIDC

OAuth code lives in src/api/auth/providers/oauth.py; routes live in auth_external_routes.py.

Supported provider implementations:

Provider Notes
github GitHub OAuth
gitlab GitLab OAuth, configurable server URL
google Google OIDC
keycloak Keycloak OIDC, configurable server and realm
sourcecraft Yandex ID based SourceCraft provider
gitverse GitVerse provider

Routes:

Route Purpose
GET /api/v1/auth/oauth/providers List configured providers and generated authorize URLs
GET /api/v1/auth/oauth/{provider} Start OAuth flow
GET /api/v1/auth/oauth/{provider}/callback Exchange code, create or find user, issue JWTs

Configured OAuth users receive JWTs using their persisted database role.

LDAP/Active Directory

LDAP code lives in src/api/auth/providers/ldap_auth.py; route integration exists in auth_external_routes.py.

Routes:

Route Purpose
POST /api/v1/auth/ldap Authenticate against LDAP/AD and issue JWTs
GET /api/v1/auth/ldap/status Report LDAP availability and connection status

Runtime caveat: LDAPAuthenticator.map_groups_to_role() reads config.group_role_mapping. Ensure the deployed LDAPConfig provides that attribute or all LDAP-created users fall back to analyst.

Yandex Cloud IAM

IAM validation lives in src/api/auth/providers/iam.py and is initialized from API settings when IAM is enabled.

Request header:

X-YC-IAM-Token: <token>

When the token validates, get_auth_context() returns:

  • role=Role.ANALYST
  • scopes=["scenarios:read", "query:execute", "review:execute"]
  • auth_method="iam"

Validation results are cached for the configured IAM token cache TTL.

Machine Scope Enforcement

ScopeEnforcementMiddleware is added in src/api/main.py when security.api_key_scope_enforcement is true. It checks requests with X-API-Key before the router executes.

Process:

  1. Validate the key as a user API key or service-account credential.
  2. Save MachinePrincipal on request.state.machine_principal.
  3. Resolve requested project scope from X-Project-Id.
  4. Validate optional machine contract header.
  5. Validate optional mTLS binding for service accounts.
  6. Run authorize_machine_request().
  7. Emit a machine audit event.
  8. Return 403 insufficient_scope on denial.

API route scope map excerpt:

Prefix Required scope
/scenarios scenarios:read
/query query:execute
/review review:execute
/sessions sessions:read
/history history:read
/stats stats:read
/metrics metrics:read
/security, /import, /documentation, /edit, /optimize, /standards, /composition, /changelog, /acp, /chat scenarios:execute
/gocpg, /context, /patterns query:execute
/deps scenarios:read
/dashboard stats:read
/digital-employees digital_employees:read
/groups, /projects admin:all
/webhooks, /health, /auth, /demo, /ws public for middleware scope purposes

MCP route scope map:

Prefix Required scope
/mcp mcp:access
/sse mcp:access
/messages mcp:access

For MCP only, mcp:access can be satisfied by aliases including query:execute, scenarios:read, scenarios:execute, review:execute, stats:read, history:read, and admin:all.

Explicit endpoint scope checks can use:

from src.api.auth.tokens.scope_enforcement import require_scope

@router.get("/example")
async def example(auth=Depends(require_scope("query:execute"))):
    ...

require_scope() checks raw AuthContext.scopes; admin:all bypasses it.

Tenant and Project Access

Tenant/project authorization is modeled separately from global user roles.

Enums and tables:

Surface Values or purpose
GroupRole viewer, editor, admin
ProjectScopeMode all, selected
project_groups Tenant/group records
user_group_access User membership and tenant role
user_project_access Selected project allowlist when membership uses selected mode
api_keys.group_id Optional tenant restriction for user API keys
service_accounts.group_id / project_id Optional tenant/project restriction for service accounts

Admin access-management routes:

Route Purpose
GET /api/v1/admin/identity/users/{user_id}/access Read unified system role plus tenant/project assignments
PUT /api/v1/admin/identity/users/{user_id}/access Replace system role and tenant/project assignments

Guardrails:

  • A platform admin cannot deactivate their own account.
  • The last active platform admin cannot be deactivated or downgraded.
  • The last tenant admin cannot be removed or downgraded without another tenant admin.
  • selected project scope must include at least one project, and all selected projects must belong to the assigned tenant.
  • all project scope cannot include explicit project ids.

Project resolution and tenant-sensitive runtime paths must use ProjectContext and resolved project scope, not user-provided database paths.

Path Validation Middleware

PathValidationMiddleware validates db_path and source_path fields in JSON bodies for POST, PUT, and PATCH requests.

Rules:

  • Empty strings and NUL bytes are rejected.
  • Relative paths are rejected when path_validation_deny_relative is true.
  • .. path components are rejected.
  • Paths are resolved with os.path.realpath().
  • The resolved path must be under an allowed base directory.

Allowed directories are built from:

  • security.path_validation_allowed_base_dirs
  • registered project db_path parent directories
  • registered project source_path directories

Config fields are defined in SecurityConfig:

Field Default
path_validation_enabled true
path_validation_deny_symlinks true
path_validation_deny_relative true
path_validation_allowed_base_dirs ["data/projects/"]

Webhook Authentication

Webhook verification lives in src/api/auth/machine/webhook_auth.py. The main verification function is verify_webhook_signature().

Supported platforms:

Platform Signature/token header Timestamp header
sourcecraft X-SourceCraft-Signature X-SourceCraft-Timestamp
gitverse X-GitVerse-Signature; fallbacks include X-Gitea-Signature, X-Gogs-Signature, X-Hub-Signature-256, X-Hub-Signature X-GitVerse-Timestamp
github X-Hub-Signature-256 X-Hub-Timestamp
gitlab X-Gitlab-Token none

sourcecraft, gitverse, and github use HMAC signature validation. gitlab uses constant-time token comparison. Replay protection uses the configured security.webhook_max_age_seconds default when a platform timestamp header exists.

Audit and SIEM-Relevant Events

Audit actions are defined in src/api/logging/audit_logger.py.

Relevant authorization actions include:

Category Actions
Auth auth.login.success, auth.login.failure, auth.logout, auth.token.refresh, auth.token.revoked
OAuth and LDAP oauth.login.*, ldap.login.*
API keys api_key.created, api_key.revoked, api_key.deleted, api_key.used
Service accounts service_account.created, service_account.rotated, service_account.credential_revoked, service_account.disabled
Machine access machine.access
User and tenant admin user.created, user.updated, user.role.changed, user.tenant.*, user.project.scope.changed
Security security.permission.denied, security.rate_limit.exceeded, security.token.invalid, security.path_violation, security.idor_attempt, security.webhook.replay_rejected, security.mcp.auth_failure

Machine audit records include subject type/id, interface, operation, resource, result, credential id, optional service-account id, optional contract version, project id, and group id.

Configuration Reference

Selected current config fields:

Field Purpose
security.api_key_scope_enforcement Enables machine API-key scope middleware
security.local_dev_unauthenticated_surfaces_enabled Enables guarded loopback local-plugin auth bypass
security.webhook_max_age_seconds Replay window for timestamped webhook signatures
security.auth_rate_limit_* Login/register/refresh/LDAP rate-limit settings
security.service_account_token_ttl_days Default service-account account and credential TTL
security.service_account_default_interfaces Default interfaces for service accounts
security.service_account_max_active_credentials Rotation quota
security.service_account_rotation_overlap_seconds Intended overlap window for rotations
security.service_account_rotation_warning_days Warning window for credential age/expiry
security.service_account_revoke_sla_seconds Revocation SLA reference
security.service_account_mtls_enabled Enables service-account mTLS binding checks
security.service_account_mtls_required_interfaces Interfaces requiring mTLS when enabled
security.service_account_mtls_subject_header Proxy header for certificate subject
security.service_account_mtls_fingerprint_header Proxy header for certificate fingerprint
security.service_account_mtls_bindings Allowed fingerprints by service-account id

Operational Guidance

For administrators:

  1. Use admin only for platform administration.
  2. Prefer tenant GroupRole and selected project access for day-to-day segregation.
  3. Use service accounts for CI, bots, portals, MCP clients, and long-lived machine access.
  4. Use policy templates as a baseline and add only the scopes needed by the caller.
  5. Rotate service-account credentials before revoking old credentials.
  6. Keep JWT blacklist cache loading enabled during application startup.
  7. Keep path validation and API-key scope enforcement enabled in shared environments.

For developers:

  1. Use require_permission() or require_role() for human user route checks.
  2. Use require_scope() only when raw scope strings are the intended contract.
  3. Add new machine routes to API_ROUTE_SCOPE_MAP or MCP_ROUTE_SCOPE_MAP.
  4. Preserve REST/MCP parity by calling shared services rather than duplicating auth logic.
  5. Never authorize tenant-sensitive operations from user-provided db_path.
  6. For local-plugin mutating routes, require named actor and task evidence.
  7. Do not log bearer tokens, API keys, service-account secrets, raw webhook secrets, or raw prompts.

Version: 3.0 | May 2026