Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

108 linesCodeBlameRaw
1---
2title: Identity and access
3description: How every kind of credential becomes one viewer, what the viewer carries, and how each service decides access with the same compiled-in rules instead of asking identity.
4---
5
6import Aside from '../../../components/Aside.astro';
7import Diagram from '../../../components/Diagram.astro';
8
9`identity` (`services/identity`, Rust, D1 `g1t`) is the only service that
10knows about credentials. It holds accounts, sessions, workspaces and their
11members, teams, repository roles and invitations, access tokens, OAuth,
12device sign-in and two-factor authentication. Every other service learns
13who is asking from the viewer it is handed.
14
15<Diagram name="identity-flow" />
16
17## Credentials
18
19| Credential | Looks like | Used by | Resolved with |
20| --- | --- | --- | --- |
21| Session cookie | `g1t_session`, 64 hex characters, 30 days | The site | `user_for_session` |
22| Access token | `g1t_…`, personal or a workspace's, with scopes | The API, MCP, git, the CLI | `user_for_access_token` |
23| OAuth access token | Issued by `POST /oauth/token`, refreshed with `g1r_…` | MCP clients, the phone app, apps you authorise | `user_for_access_token` |
24| Job token | `G1T_TOKEN` in a workflow job | Actions, for that job alone | `user_for_access_token` |
25| Run credential | Made per sandbox run, up to 6 hours, or per [session](/guides/sessions/) g1t starts on Claude Managed Agents, up to 12 | Sandboxes; a session's agent, for the API, MCP, git and the CLI | `user_for_access_token` |
26| git password | Your account's password | git, only without two-factor authentication | `user_for_git_credentials` |
27
28The CLI signs in with the device flow of RFC 8628 (`/device/code`, then
29`/device/token`) and keeps its token in the system keychain. Session
30cookies and access tokens are stored as their SHA-256, so neither can be
31read back.
32
33## The viewer
34
35`identity` answers with a `User` (`crates/contracts/src/lib.rs`), and the
36edge hands it to every service it calls, as `viewer` in the call's
37arguments. It is `null` for someone signed out.
38
39| Field | Holds |
40| --- | --- |
41| `kind` | `User`, `Workspace`, `Agent` or `System`: what kind of principal this is |
42| `workspaces` | Each membership: the role, the workspace's base permission, the roles that add to a member, such as billing or security manager, and privileges |
43| `grants` | Roles on repositories, directly or through teams |
44| `acting` | For a job or run token: the narrower scope it acts within. A session's run credential resolves to g1t acting on behalf of the person who started it; past the API it is that person with `acting` kept, so their role decides and what it writes is recorded as g1t's for them |
45
46A service never asks `identity` whether someone may do something. Each
47calls `can` in `g1t_contracts::access`, the same rules compiled into every
48Rust service, with `packages/contracts/src/access.ts` as the TypeScript
49mirror, which a test keeps identical. That takes `identity` out of every
50request after the first call, and means access is decided by the service
51that owns the data, next to it.
52
53<Aside title="Why the viewer can be trusted">
54 The viewer is passed as plain JSON, not signed. That is safe because
55 services can be reached only through service bindings: none is on
56 `workers.dev`, and the only routes in the core are `telemetry`'s and
57 `og`'s public endpoints, which take no viewer. A Worker can only be
58 called by the Workers bound to it, and those are the edge Workers and
59 other services, which resolved the viewer themselves.
60</Aside>
61
62## Roles
63
64| Level | Roles |
65| --- | --- |
66| Workspace | Owner and member, plus the roles that add to a member (billing manager, security manager) and member privileges |
67| Repository | Read, Triage, Write, Maintain and Admin, given to a person, a team, or everyone in the workspace as its base permission |
68| Outside collaborators | A repository role for someone who is not a member |
69
70A [work item](/guides/work-items/) belongs to the workspace, so it has no
71repository role of its own. `item_role` gives the viewer the higher of
72their role through workspace membership and their permission on the work
73item's origin repository, if it has one. `can_item` and `check_item` judge
74it with the same table as `can` and `check`. So members see every work
75item, anyone who can read the origin repository sees the ones filed there,
76and a work item with no origin is seen only by members. Managing the
77workspace's labels and milestones takes the role through membership.
78
79The rules live in `crates/contracts/src/access.rs`; the grants live in
80`identity`. [Access and roles](/guides/access-and-roles/) explains each
81role from a person's side.
82
83## Scopes
84
85A token can do no more than its owner, and often less. Scopes are written
86`resource:level`, where `admin` includes `write` and `write` includes
87`read` (`crates/contracts/src/scopes.rs`). A token's reach is every
88workspace its owner belongs to, one of them, or none. The API checks the
89scopes before it calls any service, and the MCP server lists only the
90tools they allow ([How a request is served](/architecture/requests/#scopes)).
91
92## Sign-in hardening
93
94| | |
95| --- | --- |
96| Two-factor | TOTP (RFC 6238), with ten recovery codes stored as SHA-256. A workspace can require it. |
97| Sudo mode | Sensitive changes ask you to prove it is you again (`Identity::proof`) |
98| Lockouts | Wrong passwords are throttled per account and per address |
99| Unconfirmed email | Can sign in, but cannot push |
100| GitHub sign-in | A GitHub App's OAuth web flow with PKCE, keyed on your GitHub account's numeric id |
101| Staff | `sudo.g1t.sh` checks the Cloudflare Access token against a staff list, and is the only caller of `identity`'s admin methods |
102
103## Events
104
105`identity` publishes `user.*`, `workspace.*`, `team.*` and `invite.*`
106events. Lifecycle events, such as a workspace being
107deleted or renamed, go to every subscriber, so each service cleans up or
108renames its own data ([Events](/architecture/events/#subscriber-queues)).