This reference helps administrators and developers check user roles, API permissions and machine-account access. User roles, project scope and machine-account permissions are separate authorization layers. Start with the role permissions, then check the project assignments and machine-account scopes for the request.
Check access
For a user, select a system role from the permission table below, then assign
its group and accessible projects. For automation, create a service account
and assign its scopes, interfaces, and project. A scope is a permission string,
such as query:execute; use its exact value in settings and requests.
Check two requests: an allowed operation and an operation outside the assigned permissions. Compare the user or service account, project, required permission, and result in the audit log. This identifies the access layer to adjust: the role, project assignment, or machine-account scopes.
For corporate sign-in, follow the OAuth/OIDC and LDAP guide. For endpoint URLs and request formats, use the REST API reference.
Runtime Model
CodeGraph uses three related authorization layers:
| Layer | What it controls | Administrator settings |
|---|---|---|
| User role | Operations available to a user | viewer, analyst, reviewer, or admin |
| Machine-account permissions | Operations through API and MCP | Scopes and allowed interfaces for the key or service account |
| Group and project access | Projects available to the account | Group membership and access to all or selected projects |
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
A system role defines the user’s base permissions:
| 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 |
Minimum-role checks use this hierarchy: viewer < analyst < reviewer < admin.
Permission Behavior
Permission currently contains 31 values, including admin:all.
Permission grants follow these rules:
- The
adminrole allows every operation in the catalog. - An explicit scope grants a permission when its string matches the catalog value.
- The
admin:allscope allows every operation in role and machine-scope checks.
Check a role permission
From the CodeGraph source root with its Python dependencies installed, save this example
as check_rbac.py and run python check_rbac.py:
from src.api.auth.permissions import Permission, Role, has_permission
assert has_permission(Role.VIEWER, Permission.SCENARIOS_READ)
assert not has_permission(Role.VIEWER, Permission.SCENARIOS_EXECUTE)
assert has_permission(Role.ANALYST, Permission.SCENARIOS_EXECUTE)
assert not has_permission(Role.ANALYST, Permission.REVIEW_EXECUTE)
assert has_permission(Role.REVIEWER, Permission.REVIEW_EXECUTE)
print("Role permission checks passed")
Expect Role permission checks passed and exit status 0. An AssertionError identifies
a mismatch with the expected grant. This example checks the role function only; it does
not exercise tokens, HTTP requests or project restrictions.
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()returnsscenarios:read,scenarios:execute,query:execute,sessions:read,sessions:write,history:read.validate_scopes()filters unknown strings against thePermissionenum.has_permission()considers role grants first and explicit scope strings second.
FastAPI Auth Context
AuthContext is defined in src/contracts/auth_context.py and exported through src.api.auth.middleware. It combines the access subject (AuthSubject) and permission scope (AuthScope).
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:
- Reuse
request.state.machine_principalifScopeEnforcementMiddlewarehas already validated an API key. - Check
X-YC-IAM-Tokenif IAM is enabled. - Check JWT bearer token and reject blacklisted JWT ids.
- Check
X-API-Keyas a machine credential. - 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 /token |
Primary username/password token endpoint |
POST /refresh |
Rotate refresh token and issue new tokens |
POST /oauth/token |
RFC 6749-compatible password and refresh-token grants |
An administrator creates a local account through POST /api/v1/admin/identity/users and assigns its role; the default is analyst. The response discloses the password once. Local passwords are hashed with bcrypt. To log out, call DELETE /api/v1/me/logout with the current bearer token; the route adds that token to the blacklist.
JWT Bearer Tokens
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 |
iss |
Optional token issuer |
aud |
Optional audience: a string or list of strings |
resource |
Optional RFC 8707 resource indicator |
client_id |
Optional public OAuth client identity |
project_id |
Optional consent-bound project |
Defaults:
- Access token TTL: the JWT configuration setting
access_token_expire_minuteswhenexpires_deltais omitted; the fallback is 30 minutes. - 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 thetoken_blacklisttable.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()usessecrets.compare_digest().ApiKey.group_idoptionally limits a key to one tenant group.- User API keys authenticate as
MachinePrincipal(subject_type="user", auth_method="api_key"). - User API keys allow
apiandmcpinterfaces by default. ApiKeyInfois the secret-free response model.ApiKeyWithSecretextendsApiKeyInfoand 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
scopesandallowed_interfaces. - Optional
group_idorproject_idrestriction. - 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, currently2026-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
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
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 |
LDAPAuthenticator.map_groups_to_role() reads LDAPConfig.role_mapping and normalizes group DN/CN aliases. If several roles match, it selects the first in the order admin, reviewer, analyst, viewer. With no match it returns analyst. This fallback grants more permissions than viewer; verify group mappings before enabling LDAP.
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.ANALYSTscopes=["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:
- Validate the key as a user API key or service-account credential.
- Save
MachinePrincipalonrequest.state.machine_principal. - Resolve requested project scope from
X-Project-Id. - Validate optional machine contract header.
- Validate optional mTLS binding for service accounts.
- Run
authorize_machine_request(). - Emit a machine audit event.
- Return
403 insufficient_scopeon denial.
API route scope map excerpt:
| Prefix | Required scope |
|---|---|
/review |
review:execute |
/metrics |
metrics:read |
/security |
scenarios:execute |
/code |
query:execute |
/import |
scenarios:execute |
/documentation |
scenarios:execute |
/edit |
scenarios:execute |
/optimize |
scenarios:execute |
/compliance |
scenarios:execute |
/dashboard |
stats:read |
/context |
query:execute |
/admin/runtime/acp |
scenarios:execute |
/workflows |
scenarios:execute |
/webhooks |
No separate scope requirement |
/health |
No separate scope requirement |
/auth |
No separate scope requirement |
/admin/workforce |
admin:all |
/groups |
admin:all |
/projects |
admin:all |
/digital-employees |
digital_employees:read |
/employee-lanes |
digital_employees:read |
/employee-tasks |
digital_employees:read |
/demo |
No separate scope requirement |
/ws |
No separate scope requirement |
Each route applies its authentication, webhook-signature and handler-dependency checks.
authorize_machine_request() checks allowed interfaces and service-account project
or group restrictions for every path. The scope map adds the required permission
for the prefixes listed above.
MCP route scope map:
| Prefix | Required scope |
|---|---|
/mcp |
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:
In your FastAPI application, import Depends from fastapi and create an
APIRouter named router. Add the handler below and mount the router in the
application. With authentication configured, send requests from accounts with
and without the required scope to check both access outcomes.
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 |
Assign a role and projects in the interface
You need administrator access, an existing user, and a group containing projects. The interface calls a group a tenant.
- Open Users and select the user’s card.
- Fill in Audit tenant and Change reason to record the context of the administrative operation.
- In System role, select a role from the permission table above, save it, and confirm the change.
- On the Access tab, add a tenant and select its role: Viewer, Editor, or Admin.
- Under Project scope, choose Selected projects and select at least one project belonging to that tenant. Choose All projects to grant access to all its projects.
- Review the selected assignments, select Save access, and confirm the save. Reopen the card and compare the stored tenant, role, and projects with your selections.
- Sign in again as that user to obtain a token with the current role.
Open an allowed project and perform an operation
permitted by the system role. To check the project restriction, use a test
user with a single
selectedassignment: the selected project should open, and another project should return an access denial.
This screen edits direct user assignments. Check access inherited through
tenant groups in Tenants. For automation, supply tenant_id, change_reason,
system_role, and the assignments list in the PUT request. Each assignment
contains group_id, role, project_scope_mode, and allowed_project_ids.
The request replaces the assignment list: include every assignment you want
to retain, then read the result through GET.
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.
selectedproject scope must include at least one project, and all selected projects must belong to the assigned tenant.allproject 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_relativeis true. - Path components are normalized before checking the file location.
- 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_pathparent directories - registered project
source_pathdirectories
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. Timestamp age validation uses security.webhook_max_age_seconds only when the request supplies a nonempty supported timestamp header. This check does not reject a missing header or track previously accepted events, so it does not itself prevent repeats within the allowed window. GitLab has no timestamp check.
Audit and SIEM-Relevant Events
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 |
Maximum allowed age of a supplied webhook timestamp |
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:
- Use
adminonly for platform administration. - Prefer tenant
GroupRoleand selected project access for day-to-day segregation. - Use service accounts for CI, bots, portals, MCP clients, and long-lived machine access.
- Use policy templates as a baseline and add only the scopes needed by the caller.
- Rotate service-account credentials before revoking old credentials.
- Keep JWT blacklist cache loading enabled during application startup.
- Keep path validation and API-key scope enforcement enabled in shared environments.
For developers:
- Use
require_permission()orrequire_role()for human user route checks. - Use
require_scope()only when raw scope strings are the intended contract. - Add new machine routes to
API_ROUTE_SCOPE_MAPorMCP_ROUTE_SCOPE_MAP. - Preserve REST/MCP parity by calling shared services rather than duplicating auth logic.
- Never authorize tenant-sensitive operations from user-provided
db_path. - For local-plugin mutating routes, require named actor and task evidence.
- Do not log bearer tokens, API keys, service-account secrets, raw webhook secrets, or raw prompts.
Related Documents
- Enterprise Security Brief
- OAuth2/OIDC and LDAP/AD Integration
- SIEM Integration
- DLP Security
- REST API Reference
Version: 3.0 | May 2026