Skip to content

Repository files navigation

relayauth

Scoped token issuance and permission enforcement for relayfile. Control exactly what each agent can read, write, and access.

Why

Agents need access to files. They shouldn't have access to all files. Relayauth issues scoped JWT tokens where the VFS paths are the permission boundaries:

# Code review agent: read PRs, write only reviews
relayfile:fs:read:/github/repos/acme/api/pulls/*
relayfile:fs:write:/github/repos/acme/api/pulls/*/reviews/*# Support agent: Slack support channel only
relayfile:fs:read:/slack/channels/support/*
relayfile:fs:write:/slack/channels/support/messages/*# Notion reader: specific pages, read-only
relayfile:fs:read:/notion/pages/product-roadmap/*
relayfile:fs:read:/notion/pages/eng-specs/*

No separate ACL config. The filesystem paths are the permissions.

Quick Start

npm install @relayauth/sdk

Proactive Runtime Token Contract

M1 for the proactive runtime uses a three-step token flow:

typeWorkspaceTokenIssueRequest={workspaceId: string;name?: string;scopes?: string[];};typeAgentTokenIssueRequest={agentId: string;scopes?: string[];audience?: string[];expiresIn?: number;// capped to 3600s server-side};typePathTokenIssueRequest={agentId: string;workspaceId?: string;paths: string[];scopes?: string[];audience?: string[];expiresIn?: number;ttlSeconds?: number;// capped to 3600s server-side};typeWorkspacePathTokenIssueRequest={workspaceId: string;agentId?: string;agentName?: string;paths: string[];scopes?: string[];audience?: string[];ttlSeconds?: number;// capped to 3600s server-side};
  • POST /v1/tokens/workspace returns a long-lived relay_ws_* workspace token.
  • POST /v1/tokens/agent accepts that workspace token via x-api-key and returns a short-lived relay_ag_* token pair for one agentId.
  • POST /v1/tokens/path accepts that same workspace token via x-api-key or Authorization: Bearer relay_ws_* and returns a short-lived relay_pa_* token pair whose relayfile:fs:* scopes are intersected with the requested paths.
  • POST /v1/tokens/workspace-path accepts an org API key plus an explicit workspaceId and returns a short-lived relay_pa_* token pair directly, without creating or returning a relay_ws_* workspace token.
  • POST /v1/tokens/refresh rotates the current pair and preserves the agent-token lineage. Revoking the parent workspace token invalidates all derived agent tokens.

paths uses the same filesystem constraint model as relayfile:fs:* scopes: exact paths or trailing-prefix globs such as /linear/issues/*. For compatibility, /linear/issues/** is normalized to /linear/issues/* during issuance.

The TypeScript SDK includes an AgentTokenSession helper for transparent agent-token rotation:

import{AgentTokenSession,RelayAuthClient}from"@relayauth/sdk";constclient=newRelayAuthClient({baseUrl: "https://relayauth.example.com",apiKey: process.env.RELAY_API_KEY,});constsession=newAgentTokenSession({
client,agentId: "agent_support_runtime",scopes: ["relayauth:role:read:*"],});constaccessToken=awaitsession.getAccessToken();

Verify a token

import{TokenVerifier}from"@relayauth/sdk";constverifier=newTokenVerifier({jwksUrl: "https://relayauth.example.com/.well-known/jwks.json",issuer: "https://relayauth.dev",});constclaims=awaitverifier.verify(token);// claims.scopes → ["relayfile:fs:read:/github/*", "relayfile:fs:write:/github/*/reviews/*"]

Check if a request is allowed

import{ScopeChecker}from"@relayauth/core";constchecker=newScopeChecker(claims.scopes);checker.check("relayfile:fs:read:/github/repos/acme/api/pulls/42/metadata.json");// ✅ allowedchecker.check("relayfile:fs:write:/slack/channels/general/messages/reply.json");// ❌ denied — agent only has GitHub access

Generate a dev token

RELAYAUTH_SIGNING_KEY_PEM="$(cat private.pem)" \
RELAYAUTH_SUB=review-agent \
RELAYAUTH_SCOPES_JSON='["relayfile:fs:read:/github/*", "relayfile:fs:write:/github/*/reviews/*"]' \
./scripts/generate-dev-token.sh

Run the server

npm install
RELAYAUTH_SIGNING_KEY_PEM="$(cat private.pem)" \
RELAYAUTH_SIGNING_KEY_PEM_PUBLIC="$(cat public.pem)" \
npm run start

OIDC sponsor binding

Organizations can require identity sponsors to be established by an OIDC provider. Organizations omitted from RELAYAUTH_SPONSOR_FEDERATIONS remain in the backward-compatible legacy mode.

RELAYAUTH_SPONSOR_FEDERATIONS='{ "org_example": { "sponsorBinding": "oidc", "issuer": "https://id.example.com", "clientId": "relayauth-registration", "allowedAudiences": ["relayauth-registration"], "sponsorIdClaim": "sub" }}'

An authenticated caller exchanges a fresh IdP token at POST /v1/sponsors/proof with { "idToken": "...", "intent": "identity.create" }. The returned short-lived, intent-bound sponsorProof names the verified human principal. A proof whose intent is identity.create must accompany sponsorId on POST /v1/identities; proofs issued for other purposes, such as approval, cannot be substituted. RelayAuth checks the issuer, audience, lifetime, RS256 signature, intent, and matching sponsor before creating the identity. The identity response records whether its sponsor was established in legacy or oidc mode.

Scope Format

Scopes follow plane:resource:action:path:

SegmentValuesExample
planerelayfile, relaycast, cloud, relayauth, *relayfile
resourcefs, ops, admin, *fs
actionread, write, create, delete, manage, *read
pathVFS path with wildcard support/github/repos/acme/*

manage implies read + write + create + delete.

Path matching: /github/repos/acme/* matches any file under that prefix.

Packages

PackagenpmDescription
@relayauth/corenpmScope parsing, matching, token verification
@relayauth/sdknpmTypeScript SDK client
@relayauth/serverExpress-compatible auth server
@relayauth/typesnpmShared TypeScript types
@relayauth/aiAI-aware permission helpers
relayauth (CLI)Token generation and management
Python SDKpackages/sdk/python/
Go middlewarepackages/go-middleware/

How It Fits with Relayfile

┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Agent │────▶│ relayfile │────▶│ relayauth │
│ │ │ (VFS) │ │ (verify + │
│ read/write │ │ │ │ enforce) │
│ files │ │ checks token│ │ │
└──────────────┘ │ on every │ │ scopes map │
│ request │ │ to VFS paths│
└──────────────┘ └──────────────┘
  1. Agent sends request with JWT token to relayfile
  2. Relayfile passes the token to relayauth for verification
  3. Relayauth checks the token's scopes against the requested path
  4. If allowed, relayfile serves the file. If not, 403.

ACL Files

Place .relayfile.acl files in any directory to set permissions that inherit downward:

{
"semantics": {
"permissions": [
"scope:relayfile:fs:read:*",
"deny:agent:untrusted-bot"
]
}
}

Rules:

  • scope:<scope> — allow if token has matching scope
  • agent:<name> — allow if JWT agent_name matches
  • deny:scope:<scope> / deny:agent:<name> — explicit deny (overrides allow)
  • Child rules append to parent rules (inheritance)

Configuration

Relayauth supports config files for managing agents, roles, and ACLs declaratively:

# relay.config.yamlagents:
review-bot:
roles: [reviewer]support-bot:
roles: [support]roles:
reviewer:
scopes:
- relayfile:fs:read:/github/*
- relayfile:fs:write:/github/*/reviews/*support:
scopes:
- relayfile:fs:read:/slack/channels/support/*
- relayfile:fs:write:/slack/channels/support/messages/*acl:
- path: /githubrules:
- scope:relayfile:fs:read:*
- path: /slack/channels/supportrules:
- role:support

Cloud

Relayfile Cloud manages token issuance, agent permissions, and scope enforcement. No self-hosting required.

Development

npm install
npx turbo build
npx turbo test

License

MIT

About

Your agent can do exactly what it should — and nothing else

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages