Authentication & RBAC reference¶
Meridian supports three authentication models. They can coexist: the server tries each in order and uses the first that succeeds.
Auth model summary¶
| Model | Header | When to use |
|---|---|---|
| API token | Authorization: Bearer <token> |
CI/CD, scripts, MCP server, pre-commit hooks |
| OIDC JWT | Authorization: Bearer <jwt> |
Human users, SSO, identity provider integration |
| Unauthenticated | — | MERIDIAN_AUTH_ENABLED=false (dev only) |
When MERIDIAN_AUTH_ENABLED=false (the default), all endpoints are open. Never run in this mode in production.
API token auth¶
The simplest model. One or more opaque tokens are accepted from any caller that presents them.
Configuration¶
# Single token (simplest)
CRA_API_TOKEN=<random-string-32-chars-min>
# Multiple tokens (rotate without downtime)
MERIDIAN_API_TOKENS=token-a,token-b,token-c
# Separate high-privilege override token
CRA_OVERRIDE_TOKEN=<separate-random-string>
# Enable auth enforcement
MERIDIAN_AUTH_ENABLED=true
CRA_API_TOKEN and MERIDIAN_API_TOKENS are equivalent — any token in either list is accepted.
CRA_OVERRIDE_TOKEN is required for override and approve endpoints and is checked separately (it is NOT accepted as a general API token).
Request header¶
Alternative (legacy support):
Generating tokens¶
OIDC / JWT auth¶
For identity provider integration (Keycloak, Auth0, Azure AD, etc.).
Configuration¶
MERIDIAN_OIDC_ISSUER=https://keycloak.example.com/realms/my-realm
MERIDIAN_OIDC_AUDIENCE=meridian # must match the `aud` claim in the JWT
JWT claim extraction¶
Meridian reads the following claims from the decoded JWT:
| JWT claim | Meridian field | Notes |
|---|---|---|
sub |
user ID | |
email |
user email | |
name |
display name | |
meridian_roles |
RBAC roles | Custom claim, array of role strings |
realm_access.roles |
RBAC roles | Keycloak-style nested claim |
meridian_tenant |
tenant ID | For multi-tenant setups |
If no role claim is present, the caller gets the viewer role.
RBAC roles¶
Six roles form a strict hierarchy — each role includes all permissions of roles below it.
| Role | Level | Permissions |
|---|---|---|
viewer |
1 | Read any RFC, finding, rule, or stat |
submitter |
2 | + Submit diffs (POST /api/cra/analyze), create findings |
reviewer |
3 | + Comment on findings, run LLM review |
cab-member |
4 | + Approve or reject RFCs |
change-manager |
5 | + Update configuration, manage rules |
admin |
6 | Full access (all endpoints) |
Route-level enforcement (examples)¶
| Endpoint | Minimum role |
|---|---|
GET /api/cra/health |
None (unauthenticated) |
GET /api/cra/rfc/* |
viewer |
POST /api/cra/analyze |
submitter |
POST /api/cra/rfc/*/override |
cab-member (or override token) |
PUT /api/cra/rules |
change-manager |
POST /api/cra/rfc/*/delete |
admin |
Override token¶
Override actions (approving, overriding, bulk-overriding a BLOCKED RFC) require the CRA_OVERRIDE_TOKEN in addition to the standard API token.
This separation means: - CI pipelines can analyze and read without override capability. - Override capability is held only by callers that explicitly need it.
The MCP server's cra.override tool loads the override token from a separate Keychain entry (cra-mcp-override) so it does not need to be stored together with the read/write token.
# Present override token for approve/override endpoints
curl -X POST .../api/cra/approve/RFC-9F2C... \
-H "Authorization: Bearer $CRA_OVERRIDE_TOKEN" \
-d '{"reason":"FP — parameterized query confirmed"}'
Endpoint auth matrix¶
| Endpoint | API token | Override token | Session (browser) | Unauthenticated |
|---|---|---|---|---|
GET /api/cra/health |
— | — | — | ✓ |
POST /api/cra/analyze |
✓ | — | ✓ | dev only |
GET /api/cra/rfc/* |
✓ | — | ✓ | dev only |
POST /api/cra/rfc/*/override |
— | ✓ | ✓ (admin) | — |
POST /api/cra/approve/* |
— | ✓ | ✓ (admin) | — |
POST /api/cra/bulk-override |
— | ✓ | ✓ (admin) | — |
GET /api/cra/rfcs |
✓ | — | ✓ | dev only |
GET /api/cra/prod-check/* |
✓ | — | ✓ | dev only |
POST /api/cra/webhook |
HMAC | — | — | — |
POST /api/cra/pending-tasks |
✓ (token only) | — | — | — |
POST /api/cra/task-status |
✓ (token only) | — | — | — |
POST /api/cra/usage/log |
✓ (token only) | — | — | — |
PUT /api/cra/rules |
— | — | ✓ (admin) | — |
"Token only" means a session cookie is NOT accepted — only a machine bearer token.
Webhook authentication¶
The POST /api/cra/webhook endpoint does not use bearer token auth. Instead it validates an HMAC signature against the raw request body.
GitHub / Forgejo¶
The signature is in the X-Hub-Signature-256 header:
Configure the webhook secret in your VCS and set it as CRA_WEBHOOK_SECRET on the Meridian instance:
GitLab¶
The secret is in X-Gitlab-Token:
Set MERIDIAN_GITLAB_SECRET on the Meridian instance.
Security recommendations¶
- Always enable auth in production.
MERIDIAN_AUTH_ENABLED=falseis for local development only. - Rotate tokens without downtime using
MERIDIAN_API_TOKENS=old-token,new-token, then remove the old one after all callers have updated. - Separate the override token. Do not share
CRA_OVERRIDE_TOKENwith CI pipelines — only give it to callers (the MCP server, dashboard) that genuinely need override capability. - Store tokens in a secrets manager, not in plaintext files or environment variable exports in shell profiles. For the MCP server on macOS, use the Keychain.
- Use HTTPS. Tokens travel in HTTP headers; without TLS they are trivially captured.
Next: API endpoints · MCP tools