Security

Tenancy and isolation

The organization is the isolation boundary. Every organization-owned record (projects, tickets, runs, knowledge, credentials, organization-scoped skills) carries its organization ID, and every query is filtered by the organization scope derived from the caller’s identity — never from request parameters alone.

Knowledge isolation is enforced in the Knowledge service itself, so a bug in Core or the UI cannot expose another organization’s knowledge. Its isolation test suite (knowledge/internal/isolation) runs in CI and covers REST, search and MCP with two organizations holding near-identical content: foreign tokens on foreign spaces (403), foreign entry IDs inside the caller’s own space (404, no content returned), cross-organization search results (none), and forged tokens — wrong audience, untrusted signing key, expired, missing organization or capability. Any new Knowledge endpoint or tool must be added to it.

Human identity: OIDC

Ballet does not manage passwords. Humans log in through an external OIDC identity provider (ADR-0006: OIDC Claims-Based RBAC for Humans, Scoped Run Tokens for Agents).

Authorization is a set of role bindings: a claim match, a role, and a scope.

claim groups contains "acme-devs"  →  engineer        @ organization:acme
claim groups contains "ballet-ops" →  platform-admin       @ platform
claim email = "pm@acme.example"    →  approver        @ project:ACME

A binding matches when the token claim equals the value, or — for list claims such as groups — contains it.

Roles and the actions they grant (more actions are added as features arrive):

Role

Grants

Bindable at

platform-admin

Every action, including creating organizations

platform only

organization-admin

Read and update the organization; create, read, update its projects; manage and read role bindings within the organization; read and write its tracker and knowledge; manage its LLM credentials; edit and publish its skills; queue and cancel runs by hand (run.manage)

organization

engineer

Read organization and projects; read and write the tracker (milestones, epics, tickets)

organization, project

approver

Read organization, projects and tracker (approvals as they are added)

organization, project

viewer

Read organization, projects and tracker

organization, project

Scope semantics:

  • A platform binding applies everywhere.

  • An organization binding applies to the organization and all its projects.

  • A project binding applies to that project, and lets the holder see (read) the project’s organization — nothing else of it.

  • Platform-level actions (creating organizations, platform-scope bindings) require a platform binding, so an organization admin cannot escalate.

Evaluation is deny-by-default in Core’s application layer; list endpoints return only what the caller may read. Workload identities (run tokens) are never authorized through role bindings.

Bootstrap. [rbac] bootstrap_platform_admins grants platform-admin to claim matchers from configuration (e.g. groups:ballet-admins), so a fresh installation has an administrator. Bootstrap bindings are listed but cannot be deleted through the API.

Agent identity: scoped run tokens

Agents are not OIDC users. When a run (or planner session) starts, Core mints a short-lived token bound to:

  • organization, project and ticket (or planner session);

  • the capabilities of that run: read its ticket and plan context, report progress, propose work, read/write knowledge in its organization space, call the LLM gateway.

Tokens expire with the run. Planner tokens additionally record the human on whose behalf the planner acts; audit entries show both.

Secrets

  • LLM provider credentials are stored in Core per organization (default) and optionally per project (override), encrypted with AES-256-GCM using the key in [secrets] key_file; each ciphertext is bound to its scope. The REST API accepts keys but only ever returns a fingerprint; events record the fingerprint only. The decrypted key is returned solely by /internal/v1/credentials/resolve to services holding credentials.read (the gateway) — agent sessions never receive provider keys.

  • Git platform credentials are stored per project. Core uses an API token for the forge adapter (open, review, merge pull requests); a session receives the git token with its run’s start only, in its environment, to clone and push the run’s branch.

Session isolation

Ballet creates no containers and needs no container socket (ADR-0025: Agent Fleet Runs Sessions as Processes). The container or VM an agent runs in is the isolation boundary:

  • sessions run as an unprivileged session user, so they cannot read the agent’s token or signal the agent; each gets a fresh workspace and HOME, removed afterwards, and a minimal environment;

  • later sessions run where earlier ones ran: run agents of different organizations in different pools;

  • coding agents skip their permission prompts (IS_SANDBOX=1, "permission": "allow") because the container is the boundary;

  • restrict the containers’ network egress to the git remote, the LLM gateway, Ballet’s MCP endpoints and package registries, and give them resource limits (Kubernetes, compose or the VM’s firewall).