Access model
How Fred decides who may do what: Keycloak identity, relationship-based authorization (ReBAC / OpenFGA) as the single enforcement mechanism, and a display-only role catalog for the UI.
Authorization in one line
Authentication is a Keycloak JWT (OIDC), validated on every request. Authorization is ReBAC (OpenFGA), and only ReBAC — evaluated at every endpoint against the caller's relationships to the object (their team, the resource owner, the tag hierarchy).
Source: fred-core security/authorization.py · security/rebac/schema.fga
Identity & claims
A validated token is decoded into a KeycloakUser:
| Token claim | KeycloakUser field | Use |
|---|---|---|
| sub | uid | Stable user identity key |
| preferred_username | username | Display and tracing |
| User profile | ||
| resource_access[client].roles | roles | Identity marker only — used to recognize service_agent (the evaluation worker); org/team roles are never derived from this claim |
There is no groups claim on KeycloakUser. Team membership is not
derived from the JWT at all — platform_admin/platform_observer and
team_admin/team_editor/team_analyst/team_member
are stored OpenFGA relations, granted through the bootstrap endpoint or an explicit admin
action (see ReBAC below).
Source: fred-core security/oidc.py (decode_jwt) · security/structure.py (KeycloakUser)
ReBAC — the enforcement model
Fred's OpenFGA schema defines object types, the relations users/teams can hold on them, and the permissions those relations grant. Every protected endpoint resolves to one of these checks.
Organization
Relations: platform_admin, platform_observer (= [user] or platform_admin).
Both are stored tuples only — granted through the one-time root bootstrap (POST
/bootstrap/platform-admin) or an explicit admin action — never derived from a
Keycloak role or group. Global capabilities are organization permissions:
| Permission | Granted to |
|---|---|
| can_create_team · can_list_all_teams · can_delete_team · can_rescue_team_admin | platform_admin |
| can_edit_agent_class_path | platform_admin |
| can_manage_platform · can_administer_users · can_run_benchmark | platform_admin |
| can_observe_platform | platform_observer |
admin/editor/viewer bridge
and the "any connected user" organization tier (can_create_agent,
can_read_kpi, can_read_logs, can_read_metrics,
can_read_opensearch, can_read_content, can_process_content)
outright — those call sites now rely on authentication alone, not an organization-level ReBAC
check. A separate can_read_kpi_global was also retired as a duplicate of
can_observe_platform.
Team
team_admin and team_editor are orthogonal, not hierarchical:
team_admin has full governance authority (membership, roles) and no agent/prompt
authority by default; team_editor is the reverse. team_analyst is a separate,
narrower role for evaluation work. A public marker grants read-only team-info
visibility to anyone, never data/agent/conversation access.
team_admin,team_editor,team_analyst=[user];team_member=[user] or team_admin or team_editor or team_analyst;public=[user:*]. None of these is ever derived from a platform role — a team's firstteam_adminis granted only by the platform-admin-gatedPOST /teamsbootstrap, never a standing relation.can_read=team_member or public;can_update_info,can_administer_members/editors/analysts/admins=team_admin;can_read_members,can_read_conversations,can_use_team_agents=team_member.- Team-scoped evaluation permissions:
can_run_evaluations,can_manage_evaluation_corpus=team_analyst or team_admin;can_read_conversations_for_evaluation=team_analystonly. - Helper permissions
can_update_resourcesandcan_update_agents=team_editor— these flow into the object types below.
Agent, tag, document, resource
- agent —
owner: [user, team];read= owner or team member;update/delete= owner or the team'scan_update_agents. - tag —
owner/editor/viewer(each[user, team]) and aparenttag; grantsread,update,delete,share, with inheritance from the parent tag and the owning team'scan_update_resources. - document — has a
parenttag;readfollows the tag's read, andupdate/delete/processfollow the tag's update. - resource — like document:
read/update/delete/sharederived from its parent tag.
Source: fred-core security/rebac/schema.fga · security/rebac/rebac_engine.py
Frontend capability flags
permission_catalog.py
and its static ROLE_CAPABILITIES role→resource matrix were removed under AUTHZ-05
— they mirrored Keycloak role names (admin/editor/viewer)
that no longer exist. The frontend now derives capability booleans directly, per request,
from the same OpenFGA relations described above.
Two hooks, no matrix
- Org-level —
useUserCapabilities()readscanAdmin/canObservePlatformoffFrontendBootstrap.permissions(is_platform_admin/is_platform_observer, both OpenFGA-derived). - Team-level —
useTeamCapabilities(team)readscanRead,canUpdateInfo,canUpdateResources,canUpdateAgents,canReadMembers,canAdministerMembers/Editors/Analysts/Admins,canReadConversations,canUseTeamAgents,canRunEvaluations,canManageEvaluationCorpus,canReadConversationsForEvaluationoffTeamWithPermissions.permissions— computed per team byteams/service.py::_get_team_permissions_for_user, not a static table.
The only role-string check left anywhere in this path is service_agent — an
identity marker on the JWT (KeycloakUser.roles), not a ReBAC relation, that lets
the evaluation worker act within a scoped team_id.
Source: fred-core security/structure.py (SERVICE_AGENT_ROLE) ·
control-plane teams/service.py, product/schemas.py · frontend
rework/core/hooks/useUserCapabilities.ts, useTeamCapabilities.ts
Endpoint enforcement
Authentication is a Keycloak JWT everywhere. Authorization is a ReBAC check keyed to the surface:
| Surface | Authorization (ReBAC) |
|---|---|
| control-plane · teams | Team permissions — can_read, can_update_info, can_read_members, can_administer_* |
| control-plane · platform, import/export, agent class path | Organization can_manage_platform |
| control-plane · global KPIs | Organization can_observe_platform |
| control-plane · sessions & history | Team + owner scoping — session.user_id == uid and matching team_id |
| control-plane · evaluations | Team can_update_agents / can_read |
| fred-agents · execute / stream | Pod-side team can_read on a mandatory team_id, per request |
| knowledge-flow · tags, documents, resources | Object-level tag / document / resource permissions |
| knowledge-flow · KPIs, opensearch statistics | Organization can_observe_platform |
| knowledge-flow · logs, task status | Authentication only — no ReBAC check (AUTHZ-05 removed the "any connected user" organization tier) |
| health / readiness | Public — no auth |
Source: control-plane teams/service.py, product/service.py, evaluations/api.py · knowledge-flow features/* · fred-runtime app/agent_app.py
Execution authorization
Agent execution is team-scoped and authorized at the pod. There is no
signed grant or capability token: the control plane issues none. Identity is the
Keycloak JWT, and the agent pod runs an OpenFGA check on the caller's team_id for
every request before executing. The ExecutionGrantAction values (execute,
resume) are action labels, not tokens.
Source: fred-sdk contracts/execution.py · fred-runtime app/agent_app.py
ReBAC-disabled mode
When OIDC is off or ReBAC is disabled, Fred loads a no-op engine: has_permission()
returns True, and lookups return a "disabled" marker so services skip object-level
filtering. The deployment is effectively open — intended for local development, never production.
Source: fred-core security/rebac/noop_engine.py · security/rebac/rebac_factory.py