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, ...)returnsTruefor every requested permission.- Explicit scopes also grant permissions when the scope string matches the required
Permission.value. - The
admin:allscope grants all permission checks inhas_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()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/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:
- 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 /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_deltais 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 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¶
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.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 |
|---|---|
/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.
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 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_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. 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:
- 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