Skip to content
680 linesCodeBlameRaw
1/**
2 * Scopes: what an access token may do on its owner's behalf. Mirrors
3 * `crates/contracts/src/scopes.rs`, which is the source of truth; a Rust
4 * test keeps the tables here the same.
5 *
6 * A token's permissions are its scopes read per resource: each resource
7 * at none or one level, such as issues: write. What a request may do is
8 * the intersection of the owner's role, the token's reach (the workspace
9 * it is made for, and its repositories) and its permissions.
10 */
11
12export type ScopeResource =
13 | "repo"
14 | "code"
15 | "security"
16 | "packages"
17 | "issues"
18 | "pull_requests"
19 | "agents"
20 | "workflows"
21 | "workflow_files"
22 | "checks"
23 | "deployments"
24 | "memory"
25 | "account"
26 | "notifications"
27 | "chat"
28 | "workspace"
29 | "billing"
30 | "access"
31 | "webhooks"
32 | "secrets"
33 | "runners"
34 | "models"
35 | "artifacts";
36
37export type ScopeLevel = "read" | "write" | "run" | "delete" | "admin";
38
39/** Every scope, grouped by resource, least first. */
40export const SCOPES = [
41 { scope: "repo:read", description: "See repositories, their settings, labels, timelines, releases, languages, contributors and security alerts, and search" },
42 { scope: "repo:write", description: "Create repositories, rename branches, change how pull requests merge and publish releases" },
43 { scope: "repo:admin", description: "Rename, archive, transfer, delete or change who can see a repository, change its rulesets, and dismiss security alerts" },
44 { scope: "code:read", description: "Clone and fetch private repositories with git" },
45 { scope: "code:write", description: "Push commits with git" },
46 { scope: "security:read", description: "See secret scanning, code scanning and vulnerability alerts, custom patterns, the dependency graph and SBOM, and security settings" },
47 { scope: "security:write", description: "Dismiss and reopen alerts, bypass push protection, review bypass requests, manage custom patterns, upload SARIF and change security settings" },
48 { scope: "packages:read", description: "Pull container images and install private packages" },
49 { scope: "packages:write", description: "Push container images and publish packages" },
50 { scope: "packages:delete", description: "Delete and restore packages and their versions" },
51 { scope: "issues:read", description: "Read issues, comments and plans" },
52 { scope: "issues:write", description: "Open, edit, close and comment on issues" },
53 { scope: "pull_requests:read", description: "Read pull requests, their changes, sessions and merge queues" },
54 { scope: "pull_requests:write", description: "Open, review, close and merge pull requests" },
55 { scope: "agents:run", description: "Put g1t agents to work and message them, which uses the workspace's money" },
56 { scope: "workflows:read", description: "Read workflows, runs and logs" },
57 { scope: "workflows:write", description: "Run, cancel, rerun and turn workflows on or off" },
58 { scope: "workflow_files:write", description: "Add, change and delete workflow files under .g1t/workflows and .github/workflows, with git or the API" },
59 { scope: "checks:read", description: "Read commits' statuses, check runs, check suites and annotations" },
60 { scope: "checks:write", description: "Report statuses and check runs on commits, and ask for checks to run again" },
61 { scope: "deployments:read", description: "See deployments, their statuses and environments" },
62 { scope: "deployments:write", description: "Report deployments and their statuses, from any CI" },
63 { scope: "memory:read", description: "Recall memory and search the workspace's context" },
64 { scope: "memory:write", description: "Save memory for the next agent" },
65 { scope: "account:read", description: "Read your email addresses, invites, invitations, pinned projects and stars" },
66 { scope: "account:write", description: "Change your email addresses, make invites, answer invitations, pin projects and star repositories" },
67 { scope: "notifications:read", description: "See your inbox, its threads, and what you subscribe to and watch" },
68 { scope: "notifications:write", description: "Mark notifications read, done, saved or snoozed, subscribe to threads and watch repositories" },
69 { scope: "chat:read", description: "Read the conversations you are in: channels, direct messages, threads and reactions, live" },
70 { scope: "chat:write", description: "Send, edit and delete your messages, react, mark conversations read, and answer cards in chat" },
71 { scope: "workspace:read", description: "Read workspace settings, invites, integrations, model routes, teams and rulesets" },
72 { scope: "workspace:admin", description: "Create and delete workspaces, invite members, connect integrations, create, change and delete teams, and change the workspace's rulesets" },
73 { scope: "billing:read", description: "See a workspace's usage, budget, AI credit and invoices" },
74 { scope: "billing:write", description: "Change a workspace's budget and buy AI credit" },
75 { scope: "access:read", description: "See who has access to repositories" },
76 { scope: "access:admin", description: "Give and take away access to repositories, a team's included" },
77 { scope: "webhooks:read", description: "See webhooks and their deliveries" },
78 { scope: "webhooks:admin", description: "Create, change and delete webhooks" },
79 { scope: "secrets:read", description: "List secrets (never their values) and read variables" },
80 { scope: "secrets:admin", description: "Set and delete secrets and variables" },
81 { scope: "runners:read", description: "See self-hosted runners, their groups and where agents run" },
82 { scope: "runners:admin", description: "Register and remove self-hosted runners, change their groups and settings" },
83 { scope: "models:read", description: "See the workspace's AI Gateway requests: their models, tokens, cost and status" },
84 { scope: "models:write", description: "Send model requests through the AI Gateway, which uses the workspace's AI credit" },
85 { scope: "artifacts:read", description: "List, read and search artifacts you can see, their versions, and the numbers their dashboards show" },
86 { scope: "artifacts:write", description: "Create, rename, move, edit, trash and restore artifacts, and propose changes to them" },
87 { scope: "artifacts:admin", description: "Share artifacts, change who can open them, and delete them for good" },
88] as const;
89
90/**
91 * Scopes of resources being built that tokens are not offered yet (Rust:
92 * `Resource::offered`). They parse in Rust and are typed here, but no
93 * preset, full access, OAuth request or token form hands them out, and no
94 * operation needs them. When one ships, its rows move to the end of
95 * `SCOPES` and `SCOPE_RESOURCES`. Empty now: Artifacts shipped last.
96 */
97export const UPCOMING_SCOPES: readonly { scope: Scope; description: string }[] = [
98] as const;
99
100export type Scope = (typeof SCOPES)[number]["scope"];
101
102/** Where a resource sits on the token form; only a person's token may hold account ones. */
103export type ResourceGroup = "repository" | "workspace" | "account";
104
105/** Resources in the order settings show them, with their names for people and their group. */
106export const SCOPE_RESOURCES: { resource: ScopeResource; label: string; group: ResourceGroup }[] = [
107 { resource: "repo", label: "Repositories", group: "repository" },
108 { resource: "code", label: "Code", group: "repository" },
109 { resource: "security", label: "Security", group: "repository" },
110 { resource: "packages", label: "Packages", group: "repository" },
111 { resource: "issues", label: "Issues", group: "repository" },
112 { resource: "pull_requests", label: "Pull requests", group: "repository" },
113 { resource: "agents", label: "g1t agents", group: "repository" },
114 { resource: "workflows", label: "Workflows", group: "repository" },
115 { resource: "workflow_files", label: "Workflow files", group: "repository" },
116 { resource: "checks", label: "Checks and statuses", group: "repository" },
117 { resource: "deployments", label: "Deployments", group: "repository" },
118 { resource: "memory", label: "Memory and context", group: "repository" },
119 { resource: "account", label: "Your account", group: "account" },
120 { resource: "notifications", label: "Notifications", group: "account" },
121 { resource: "chat", label: "Chat", group: "account" },
122 { resource: "workspace", label: "Workspaces", group: "workspace" },
123 { resource: "billing", label: "Billing", group: "workspace" },
124 { resource: "access", label: "Who has access", group: "repository" },
125 { resource: "webhooks", label: "Webhooks", group: "repository" },
126 { resource: "secrets", label: "Secrets and variables", group: "repository" },
127 { resource: "runners", label: "Self-hosted runners", group: "workspace" },
128 { resource: "models", label: "AI Gateway", group: "workspace" },
129 { resource: "artifacts", label: "Artifacts", group: "workspace" },
130];
131
132/** Resources not offered yet, as `UPCOMING_SCOPES`: settings never show them. */
133export const UPCOMING_RESOURCES: { resource: ScopeResource; label: string; group: ResourceGroup }[] = [
134];
135
136const LEVEL_ORDER: Record<ScopeLevel, number> = { read: 0, write: 1, run: 2, delete: 3, admin: 4 };
137
138export function scopeResource(scope: Scope): ScopeResource {
139 return scope.split(":")[0] as ScopeResource;
140}
141
142export function scopeLevel(scope: Scope): ScopeLevel {
143 return scope.split(":")[1] as ScopeLevel;
144}
145
146export function isScope(text: string): text is Scope {
147 return SCOPES.some((row) => row.scope === text);
148}
149
150/** Changes that are hard to undo, or decide who can reach what. */
151export function isDangerous(scope: Scope): boolean {
152 const level = scopeLevel(scope);
153 return level === "admin" || level === "delete";
154}
155
156export function describeScope(scope: Scope): string {
157 return [...SCOPES, ...UPCOMING_SCOPES].find((row) => row.scope === scope)?.description ?? scope;
158}
159
160/** Whether holding `held` gives `needed`: the same resource, at its level or lower. */
161export function scopeIncludes(held: Scope, needed: Scope): boolean {
162 return (
163 scopeResource(held) === scopeResource(needed) &&
164 LEVEL_ORDER[scopeLevel(held)] >= LEVEL_ORDER[scopeLevel(needed)]
165 );
166}
167
168/**
169 * Whether a token holding `scopes` may do what `needed` gates (Rust:
170 * `TokenAccess::allows`). Null or absent scopes are full access, which
171 * holds every scope; unknown names give nothing.
172 */
173export function tokenAllows(scopes: readonly string[] | null | undefined, needed: Scope): boolean {
174 if (scopes === null || scopes === undefined) return true;
175 return scopes.some((held) => isScope(held) && scopeIncludes(held, needed));
176}
177
178/** The levels a resource has, least first. */
179export function levelsOf(resource: ScopeResource): ScopeLevel[] {
180 return SCOPES.filter((row) => scopeResource(row.scope) === resource).map((row) => scopeLevel(row.scope));
181}
182
183/** The token form's groups, in order. */
184export const RESOURCE_GROUPS: { group: ResourceGroup; label: string; about: string }[] = [
185 { group: "repository", label: "Repository permissions", about: "What it may do in the repositories it reaches." },
186 { group: "workspace", label: "Workspace permissions", about: "What it may do with the workspaces it reaches themselves." },
187 { group: "account", label: "Account permissions", about: "What it may do with your own account. Personal tokens only." },
188];
189
190/** A token's permissions: each resource held, at its highest level; left out is none. */
191export type Permissions = Partial<Record<ScopeResource, ScopeLevel>>;
192
193/** Scopes as permissions. Null scopes (full access) are every resource at its highest. */
194export function permissionsOf(scopes: readonly string[] | null): Permissions {
195 const permissions: Permissions = {};
196 const held = scopes === null ? SCOPES.map((row) => row.scope) : parseScopes(scopes.join(" "));
197 for (const scope of held) {
198 const resource = scopeResource(scope);
199 const now = permissions[resource];
200 if (!now || LEVEL_ORDER[scopeLevel(scope)] > LEVEL_ORDER[now]) permissions[resource] = scopeLevel(scope);
201 }
202 return permissions;
203}
204
205/** Permissions as the scopes a token stores: the highest of each resource, in table order. */
206export function scopesOfPermissions(permissions: Permissions): Scope[] {
207 const wanted = new Set(
208 Object.entries(permissions)
209 .filter(([, level]) => level)
210 .map(([resource, level]) => `${resource}:${level}`),
211 );
212 return SCOPES.map((row) => row.scope).filter((scope) => wanted.has(scope));
213}
214
215/** The longest a token with an expiry may last, in days. */
216export const MAX_TOKEN_LIFETIME_DAYS = 366;
217
218/** The most repositories a token may select. */
219export const MAX_SELECTED_REPOSITORIES = 50;
220
221/** Scopes from text separated by spaces or commas, in table order; unknown ones are left out. */
222export function parseScopes(text: string): Scope[] {
223 const given = new Set(text.split(/[\s,]+/).map((part) => part.trim().toLowerCase()));
224 return SCOPES.map((row) => row.scope).filter((scope) => given.has(scope));
225}
226
227/** What a token stores for full access. */
228export const FULL_ACCESS = "*";
229
230export type PresetId = "read_only" | "agent" | "ci" | "full";
231
232/** Starting points for choosing scopes. `*` is full access. No preset gives chat: an application gets it only by asking for it. */
233export const PRESET_SCOPES = {
234 read_only: [
235 "repo:read", "code:read", "security:read", "packages:read", "issues:read", "pull_requests:read", "workflows:read", "checks:read", "deployments:read", "memory:read", "account:read", "notifications:read", "workspace:read", "billing:read", "access:read", "webhooks:read", "secrets:read", "runners:read", "models:read", "artifacts:read",
236 ] as const,
237 agent: [
238 "repo:read", "code:read", "code:write", "security:read", "packages:read", "issues:read", "issues:write", "pull_requests:read", "pull_requests:write", "agents:run", "workflows:read", "checks:read", "deployments:read", "memory:read", "memory:write", "account:read", "notifications:read", "notifications:write", "workspace:read", "billing:read", "access:read", "webhooks:read", "secrets:read", "models:read", "artifacts:read",
239 ] as const,
240 ci: [
241 "repo:read", "code:read", "code:write", "packages:read", "packages:write", "workflows:read", "workflows:write", "checks:read", "checks:write", "deployments:read", "deployments:write",
242 ] as const,
243 full: [
244 "*",
245 ] as const,
246};
247
248export const PRESETS: { id: PresetId; label: string; description: string }[] = [
249 { id: "read_only", label: "Read only", description: "Read everything you can read; change nothing." },
250 { id: "agent", label: "Agent", description: "Read everything, work on issues and pull requests, push code and run g1t agents." },
251 { id: "ci", label: "CI", description: "Clone and push code, push and pull packages, run workflows, and report checks and deployments." },
252 { id: "full", label: "Full access", description: "Everything you can do, including deleting repositories and changing who has access." },
253];
254
255/** The scopes of a preset, or null for full access. */
256export function presetScopes(id: PresetId): Scope[] | null {
257 if (id === "full") return null;
258 return [...PRESET_SCOPES[id]] as Scope[];
259}
260
261/** What an OAuth client gets when it asks for nothing in particular. */
262export const OAUTH_DEFAULT_SCOPES: Scope[] = [...PRESET_SCOPES.agent];
263
264/** The operation each scope gates, by the API's operation names. */
265export const OPERATION_SCOPES = [
266 ["list_emails", "account:read"],
267 ["add_email", "account:write"],
268 ["confirm_email", "account:write"],
269 ["remove_email", "account:write"],
270 ["update_email_settings", "account:write"],
271 ["list_invites", "account:read"],
272 ["create_invite", "account:write"],
273 ["revoke_invite", "account:write"],
274 ["list_invitations", "account:read"],
275 ["accept_invitation", "account:write"],
276 ["decline_invitation", "account:write"],
277 ["list_my_repo_invitations", "account:read"],
278 ["accept_repo_invitation", "account:write"],
279 ["decline_repo_invitation", "account:write"],
280 // Your pinned projects: a preference of your account.
281 ["list_pinned_projects", "account:read"],
282 ["pin_project", "account:write"],
283 // Your stars: a preference of your account.
284 ["list_starred", "account:read"],
285 ["check_starred", "account:read"],
286 ["star_repo", "account:write"],
287 ["unstar_repo", "account:write"],
288 ["unpin_project", "account:write"],
289 ["reorder_pinned_projects", "account:write"],
290 // Your inbox: notifications, subscriptions and watching.
291 ["list_notifications", "notifications:read"],
292 ["get_notification_thread", "notifications:read"],
293 ["get_thread_subscription", "notifications:read"],
294 ["get_repo_subscription", "notifications:read"],
295 ["list_watched_repos", "notifications:read"],
296 ["mark_notifications_read", "notifications:write"],
297 ["mark_thread_read", "notifications:write"],
298 ["mark_thread_done", "notifications:write"],
299 ["save_thread", "notifications:write"],
300 ["snooze_thread", "notifications:write"],
301 ["set_thread_subscription", "notifications:write"],
302 ["delete_thread_subscription", "notifications:write"],
303 ["set_repo_subscription", "notifications:write"],
304 ["delete_repo_subscription", "notifications:write"],
305 ["create_workspace", "workspace:admin"],
306 ["delete_workspace", "workspace:admin"],
307 ["get_workspace", "workspace:read"],
308 ["update_workspace", "workspace:admin"],
309 ["list_members", "workspace:read"],
310 ["update_member", "workspace:admin"],
311 ["remove_member", "workspace:admin"],
312 ["transfer_ownership", "workspace:admin"],
313 ["leave_workspace", "account:write"],
314 ["list_workspace_invites", "workspace:read"],
315 ["invite_member", "workspace:admin"],
316 ["revoke_workspace_invite", "workspace:admin"],
317 ["list_integrations", "workspace:read"],
318 ["connect_integration", "workspace:admin"],
319 ["update_integration", "workspace:admin"],
320 ["disconnect_integration", "workspace:admin"],
321 ["test_integration", "workspace:admin"],
322 ["get_model_routes", "workspace:read"],
323 ["set_model_routes", "workspace:admin"],
324 ["list_teams", "workspace:read"],
325 ["get_team", "workspace:read"],
326 ["list_team_members", "workspace:read"],
327 ["list_child_teams", "workspace:read"],
328 ["list_team_repos", "workspace:read"],
329 ["list_user_teams", "workspace:read"],
330 ["create_team", "workspace:admin"],
331 ["list_workspace_rulesets", "workspace:read"],
332 ["get_workspace_ruleset", "workspace:read"],
333 ["list_workspace_rule_evaluations", "workspace:read"],
334 ["create_workspace_ruleset", "workspace:admin"],
335 ["update_workspace_ruleset", "workspace:admin"],
336 ["delete_workspace_ruleset", "workspace:admin"],
337 ["update_team", "workspace:admin"],
338 ["delete_team", "workspace:admin"],
339 ["set_team_member", "workspace:admin"],
340 ["remove_team_member", "workspace:admin"],
341 ["set_team_review_assignment", "workspace:admin"],
342 // A workspace's billing: usage, budget, AI credit and invoices.
343 ["get_usage", "billing:read"],
344 ["get_budget", "billing:read"],
345 ["get_ai_credit", "billing:read"],
346 ["list_invoices", "billing:read"],
347 ["get_billing_details", "billing:read"],
348 ["set_budget", "billing:write"],
349 ["buy_ai_credit", "billing:write"],
350 ["list_repos", "repo:read"],
351 ["get_repo", "repo:read"],
352 // Projects follow their repositories.
353 ["list_projects", "repo:read"],
354 ["get_project", "repo:read"],
355 ["search", "repo:read"],
356 ["list_events", "repo:read"],
357 // What the default branch says about a repository, who starred it, and
358 // its releases.
359 ["get_languages", "repo:read"],
360 ["list_contributors", "repo:read"],
361 ["get_license", "repo:read"],
362 ["list_stargazers", "repo:read"],
363 ["list_releases", "repo:read"],
364 ["get_latest_release", "repo:read"],
365 ["get_release_by_tag", "repo:read"],
366 ["get_release", "repo:read"],
367 ["create_release", "repo:write"],
368 ["update_release", "repo:write"],
369 ["delete_release", "repo:write"],
370 ["list_labels", "repo:read"],
371 ["list_milestones", "repo:read"],
372 ["get_milestone", "repo:read"],
373 ["create_label", "issues:write"],
374 ["update_label", "issues:write"],
375 ["delete_label", "issues:write"],
376 ["add_default_labels", "issues:write"],
377 ["create_milestone", "issues:write"],
378 ["update_milestone", "issues:write"],
379 ["delete_milestone", "issues:write"],
380 ["get_repo_settings", "repo:read"],
381 ["list_check_names", "repo:read"],
382 ["list_deleted_repos", "repo:read"],
383 ["list_security_alerts", "repo:read"],
384 ["get_codeowners_errors", "repo:read"],
385 ["create_repo", "repo:write"],
386 ["update_repo", "repo:write"],
387 ["update_project", "repo:write"],
388 ["update_repo_settings", "repo:write"],
389 ["list_repo_rulesets", "repo:read"],
390 ["get_repo_ruleset", "repo:read"],
391 ["get_branch_rules", "repo:read"],
392 ["list_rule_evaluations", "repo:read"],
393 ["create_repo_ruleset", "repo:admin"],
394 ["update_repo_ruleset", "repo:admin"],
395 ["delete_repo_ruleset", "repo:admin"],
396 ["rename_branch", "repo:write"],
397 ["rename_repo", "repo:admin"],
398 ["transfer_repo", "repo:admin"],
399 ["archive_repo", "repo:admin"],
400 ["unarchive_repo", "repo:admin"],
401 ["set_repo_visibility", "repo:admin"],
402 ["delete_repo", "repo:admin"],
403 ["restore_repo", "repo:admin"],
404 ["purge_repo", "repo:admin"],
405 ["dismiss_security_alert", "repo:admin"],
406 ["reopen_security_alert", "repo:admin"],
407 // The security suite.
408 ["list_secret_scanning_alerts", "security:read"],
409 ["get_secret_scanning_alert", "security:read"],
410 ["list_secret_scanning_locations", "security:read"],
411 ["list_bypass_requests", "security:read"],
412 ["list_custom_patterns", "security:read"],
413 ["list_code_scanning_alerts", "security:read"],
414 ["get_code_scanning_alert", "security:read"],
415 ["list_code_scanning_analyses", "security:read"],
416 ["get_sarif_upload", "security:read"],
417 ["list_vulnerability_alerts", "security:read"],
418 ["get_vulnerability_alert", "security:read"],
419 ["get_dependency_graph", "security:read"],
420 ["get_sbom", "security:read"],
421 ["compare_dependencies", "security:read"],
422 ["get_security_settings", "security:read"],
423 ["get_workspace_security_settings", "security:read"],
424 ["get_security_overview", "security:read"],
425 ["update_secret_scanning_alert", "security:write"],
426 ["bypass_push_protection", "security:write"],
427 ["check_secret_validity", "security:write"],
428 ["review_bypass_request", "security:write"],
429 ["create_custom_pattern", "security:write"],
430 ["update_custom_pattern", "security:write"],
431 ["delete_custom_pattern", "security:write"],
432 ["dry_run_custom_pattern", "security:write"],
433 ["update_code_scanning_alert", "security:write"],
434 ["upload_sarif", "security:write"],
435 ["update_vulnerability_alert", "security:write"],
436 ["fix_security_alert", "security:write"],
437 ["update_security_settings", "security:write"],
438 ["update_workspace_security_settings", "security:write"],
439 ["list_issues", "issues:read"],
440 ["get_issue", "issues:read"],
441 ["get_plan", "issues:read"],
442 ["create_issue", "issues:write"],
443 ["update_issue", "issues:write"],
444 ["list_issue_labels", "issues:read"],
445 ["add_issue_labels", "issues:write"],
446 ["set_issue_labels", "issues:write"],
447 ["remove_issue_labels", "issues:write"],
448 ["close_issue", "issues:write"],
449 ["reopen_issue", "issues:write"],
450 ["add_comment", "issues:write"],
451 ["edit_comment", "issues:write"],
452 ["delete_comment", "issues:write"],
453 ["import_issue", "issues:write"],
454 ["apply_plan", "issues:write"],
455 ["list_pull_requests", "pull_requests:read"],
456 ["get_pull_request", "pull_requests:read"],
457 ["get_pull_request_changes", "pull_requests:read"],
458 ["read_session", "pull_requests:read"],
459 ["get_merge_queue", "pull_requests:read"],
460 ["create_pull_request", "pull_requests:write"],
461 ["update_pull_request", "pull_requests:write"],
462 ["record_session", "pull_requests:write"],
463 ["mark_pull_request_ready", "pull_requests:write"],
464 ["close_pull_request", "pull_requests:write"],
465 ["reopen_pull_request", "pull_requests:write"],
466 ["convert_pull_request_to_draft", "pull_requests:write"],
467 ["review_pull_request", "pull_requests:write"],
468 ["merge_pull_request", "pull_requests:write"],
469 ["request_reviewers", "pull_requests:write"],
470 ["remove_requested_reviewers", "pull_requests:write"],
471 ["assign_issue", "agents:run"],
472 ["delegate", "agents:run"],
473 ["plan_work", "agents:run"],
474 ["message_agent", "agents:run"],
475 ["answer_message", "agents:run"],
476 // A workspace agent's own computer: waking it spends the workspace's
477 // money, so the same scope covers reading and managing it.
478 ["get_agent_computer", "agents:run"],
479 ["wake_agent_computer", "agents:run"],
480 ["sleep_agent_computer", "agents:run"],
481 ["reset_agent_computer", "agents:run"],
482 ["list_agent_computer_commands", "agents:run"],
483 ["take_messages", "agents:run"],
484 ["list_workflows", "workflows:read"],
485 ["list_workflow_runs", "workflows:read"],
486 ["get_workflow_run", "workflows:read"],
487 ["get_job_logs", "workflows:read"],
488 ["dispatch_workflow", "workflows:write"],
489 ["cancel_workflow_run", "workflows:write"],
490 ["rerun_workflow_run", "workflows:write"],
491 ["update_workflow", "workflows:write"],
492 ["list_artifacts", "workflows:read"],
493 ["list_workflow_run_artifacts", "workflows:read"],
494 ["get_artifact", "workflows:read"],
495 ["download_artifact", "workflows:read"],
496 ["get_artifact_retention", "workflows:read"],
497 ["delete_artifact", "workflows:write"],
498 ["set_artifact_retention", "workflows:write"],
499 ["list_commit_statuses", "checks:read"],
500 ["get_combined_status", "checks:read"],
501 ["list_check_runs_for_ref", "checks:read"],
502 ["get_check_run", "checks:read"],
503 ["list_check_run_annotations", "checks:read"],
504 ["list_check_suites_for_ref", "checks:read"],
505 ["get_check_suite", "checks:read"],
506 ["create_commit_status", "checks:write"],
507 ["create_check_run", "checks:write"],
508 ["update_check_run", "checks:write"],
509 ["rerequest_check_run", "checks:write"],
510 ["rerequest_check_suite", "checks:write"],
511 // Deployments, wherever they run: reading them, and reporting them.
512 ["list_deployments", "deployments:read"],
513 ["get_deployment", "deployments:read"],
514 ["list_deployment_statuses", "deployments:read"],
515 ["list_environments", "deployments:read"],
516 ["get_environment", "deployments:read"],
517 ["create_deployment", "deployments:write"],
518 ["create_deployment_status", "deployments:write"],
519 // What keeps runs safe: the runs environments hold and reviewing them,
520 // approving a pull request's run, and a repository's own rules for its
521 // environments and tokens, which are an admin's.
522 ["get_pending_deployments", "workflows:read"],
523 ["review_pending_deployments", "workflows:write"],
524 ["approve_workflow_run", "workflows:write"],
525 ["get_workflow_permissions", "repo:read"],
526 ["get_fork_pr_approval", "repo:read"],
527 ["get_actions_access", "repo:read"],
528 ["update_environment", "repo:admin"],
529 ["delete_environment", "repo:admin"],
530 ["set_workflow_permissions", "repo:admin"],
531 ["set_fork_pr_approval", "repo:admin"],
532 ["set_actions_access", "repo:admin"],
533 // Starting workflows from outside, as a push would.
534 ["create_repository_dispatch", "code:write"],
535 // A workspace's policy for its repositories' tokens.
536 ["get_workspace_workflow_permissions", "workspace:read"],
537 ["set_workspace_workflow_permissions", "workspace:admin"],
538 // A workspace's rules for personal access tokens, and its members' tokens.
539 ["get_token_policy", "workspace:read"],
540 ["set_token_policy", "workspace:admin"],
541 ["list_member_tokens", "access:read"],
542 ["list_token_requests", "access:read"],
543 ["review_token_request", "access:admin"],
544 ["revoke_member_token", "access:admin"],
545 ["recall", "memory:read"],
546 ["search_context", "memory:read"],
547 ["get_entity", "memory:read"],
548 ["get_context", "memory:read"],
549 ["remember", "memory:write"],
550 ["list_collaborators", "access:read"],
551 ["get_collaborator_permission", "access:read"],
552 ["list_repo_invitations", "access:read"],
553 ["list_outside_collaborators", "access:read"],
554 ["add_collaborator", "access:admin"],
555 ["update_collaborator", "access:admin"],
556 ["remove_collaborator", "access:admin"],
557 ["revoke_repo_invitation", "access:admin"],
558 ["set_base_permission", "access:admin"],
559 ["set_team_repo", "access:admin"],
560 ["remove_team_repo", "access:admin"],
561 ["list_deploy_keys", "access:read"],
562 ["get_deploy_key", "access:read"],
563 ["create_deploy_key", "access:admin"],
564 ["delete_deploy_key", "access:admin"],
565 ["get_mirror", "repo:read"],
566 ["sync_mirror", "code:write"],
567 ["get_hand_back_plan", "repo:admin"],
568 ["take_over_mirror", "repo:admin"],
569 ["set_ci_failover", "repo:admin"],
570 ["hand_back_mirror", "repo:admin"],
571 ["move_mirror_to_g1t", "repo:admin"],
572 ["add_mirror_remote", "repo:admin"],
573 ["update_mirror_remote", "repo:admin"],
574 ["remove_mirror_remote", "repo:admin"],
575 ["list_webhooks", "webhooks:read"],
576 ["list_webhook_deliveries", "webhooks:read"],
577 ["create_webhook", "webhooks:admin"],
578 ["update_webhook", "webhooks:admin"],
579 ["delete_webhook", "webhooks:admin"],
580 ["ping_webhook", "webhooks:admin"],
581 ["redeliver_webhook", "webhooks:admin"],
582 ["list_actions_secrets", "secrets:read"],
583 ["list_actions_variables", "secrets:read"],
584 ["set_actions_secret", "secrets:admin"],
585 ["delete_actions_secret", "secrets:admin"],
586 ["set_actions_variable", "secrets:admin"],
587 ["delete_actions_variable", "secrets:admin"],
588 // Self-hosted runners.
589 ["list_runners", "runners:read"],
590 ["list_runner_groups", "runners:read"],
591 ["get_runner_settings", "runners:read"],
592 ["create_runner_registration_token", "runners:admin"],
593 ["remove_runner", "runners:admin"],
594 ["create_runner_group", "runners:admin"],
595 ["update_runner_group", "runners:admin"],
596 ["delete_runner_group", "runners:admin"],
597 ["update_runner_settings", "runners:admin"],
598 // Packages: reading them, their versions and who may use them needs
599 // `packages:read`; changing their settings, access and Manage Actions
600 // access `packages:write` (and the Admin role on the package, which the
601 // packages service checks); deleting and restoring packages and
602 // versions `packages:delete`, as the registries' own deletes do.
603 ["list_packages", "packages:read"],
604 ["get_package", "packages:read"],
605 ["list_package_versions", "packages:read"],
606 ["get_package_version", "packages:read"],
607 ["list_package_access", "packages:read"],
608 ["list_package_actions_access", "packages:read"],
609 ["update_package", "packages:write"],
610 ["link_package", "packages:write"],
611 ["unlink_package", "packages:write"],
612 ["set_package_access", "packages:write"],
613 ["remove_package_access", "packages:write"],
614 ["set_package_actions_access", "packages:write"],
615 ["remove_package_actions_access", "packages:write"],
616 ["delete_package", "packages:delete"],
617 ["restore_package", "packages:delete"],
618 ["delete_package_version", "packages:delete"],
619 ["restore_package_version", "packages:delete"],
620 // The AI Gateway. Sending a request to a model needs `models:write`,
621 // checked by the model proxy at models.g1t.sh.
622 ["list_gateway_requests", "models:read"],
623 // Artifacts mode's docs, slides, designs and dashboards (the `artifact`
624 // MCP tool). The artifacts service then checks the person's role on each.
625 ["list_workspace_artifacts", "artifacts:read"],
626 ["search_workspace_artifacts", "artifacts:read"],
627 ["get_workspace_artifact", "artifacts:read"],
628 ["get_workspace_artifact_content", "artifacts:read"],
629 ["list_workspace_artifact_versions", "artifacts:read"],
630 ["get_workspace_artifact_access", "artifacts:read"],
631 ["list_workspace_artifact_templates", "artifacts:read"],
632 ["list_workspace_artifact_spaces", "artifacts:read"],
633 ["query_workspace_dataset", "artifacts:read"],
634 ["create_workspace_artifact", "artifacts:write"],
635 ["update_workspace_artifact", "artifacts:write"],
636 ["edit_workspace_artifact", "artifacts:write"],
637 ["trash_workspace_artifact", "artifacts:write"],
638 ["restore_workspace_artifact", "artifacts:write"],
639 ["restore_workspace_artifact_version", "artifacts:write"],
640 ["set_workspace_artifact_access", "artifacts:admin"],
641 ["purge_workspace_artifact", "artifacts:admin"],
642] as const;
643
644/**
645 * The scopes a token or grant holds, as stored: null for full access, or
646 * the list. `legacy` marks a token made before scopes, which has full
647 * access until someone narrows it.
648 */
649export type TokenScopes = {
650 scopes: Scope[] | null;
651 legacy: boolean;
652};
653
654/**
655 * How settings group scopes into a checklist: each group's scopes, least
656 * first. Admin scopes are not here; they are under "Dangerous" on their
657 * own (see `DANGEROUS_SCOPES`). Every other scope is in exactly one group.
658 */
659export const SCOPE_GROUPS: { id: string; label: string; scopes: Scope[] }[] = [
660 { id: "code", label: "Repositories & code", scopes: ["repo:read", "repo:write", "code:read", "code:write"] },
661 { id: "security", label: "Security", scopes: ["security:read", "security:write"] },
662 { id: "packages", label: "Packages", scopes: ["packages:read", "packages:write"] },
663 { id: "work", label: "Issues & pull requests", scopes: ["issues:read", "issues:write", "pull_requests:read", "pull_requests:write"] },
664 { id: "agents", label: "Agents", scopes: ["agents:run"] },
665 { id: "workflows", label: "Workflows", scopes: ["workflows:read", "workflows:write", "workflow_files:write"] },
666 { id: "checks", label: "Checks", scopes: ["checks:read", "checks:write"] },
667 { id: "deployments", label: "Deployments", scopes: ["deployments:read", "deployments:write"] },
668 { id: "memory", label: "Memory & search", scopes: ["memory:read", "memory:write"] },
669 { id: "account", label: "Account", scopes: ["account:read", "account:write"] },
670 { id: "notifications", label: "Notifications", scopes: ["notifications:read", "notifications:write"] },
671 { id: "chat", label: "Chat", scopes: ["chat:read", "chat:write"] },
672 { id: "workspace", label: "Workspace", scopes: ["workspace:read", "access:read", "webhooks:read", "secrets:read"] },
673 { id: "billing", label: "Billing", scopes: ["billing:read", "billing:write"] },
674 { id: "runners", label: "Runners", scopes: ["runners:read"] },
675 { id: "models", label: "AI Gateway", scopes: ["models:read", "models:write"] },
676 { id: "artifacts", label: "Artifacts", scopes: ["artifacts:read", "artifacts:write"] },
677];
678
679/** The admin and delete scopes, shown under "Dangerous" behind a warning. */
680export const DANGEROUS_SCOPES: Scope[] = SCOPES.map((row) => row.scope).filter(isDangerous);