ghscope

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package ghscope is the GitHub CAPABILITY CATALOGUE, the sibling of internal/adoscope: it names the access a request needs, classifies one request to exactly one name and the repository it targets, and maps those names to GitHub permissions — because a person's own GitHub token carries everything that person can reach in every repository, so the classification and the repository it names are the only things that can hold a run to the work it was launched for.

The package is PURE, standard-library only, for adoscope's reasons (importing internal/types would cycle; internal/api or internal/egress would let a call site's own idea of "read" drift from the classifier's).

FAIL-CLOSED IS THE CONTRACT: Classify errors on a request it can't reason about, and returns a non-grantable capability for a route it does not recognize; a caller refuses both. Permits is the one spelling of "allowed".

Index

Constants

View Source
const (
	// APIHost serves the REST API.
	APIHost = "api.github.com"
	// GitHost serves git over HTTP; nothing else on it is classified.
	GitHost = "github.com"
)

The two hosts this catalogue classifies. GitHub Enterprise Server, the upload, raw-content and package hosts are absent on purpose: a request to any other host is an error.

Variables

This section is empty.

Functions

func GrantableCapabilityList

func GrantableCapabilityList() string

GrantableCapabilityList is GrantableCapabilities as a comma-separated string, the shape an error message wants.

func Label

func Label(c Capability) string

Label is c's plain-language rendering, or "" for a value outside the set — never a guess, so an unrecognized capability can't render as understood.

func PermissionsFor

func PermissionsFor(caps []Capability) (map[string]string, error)

PermissionsFor is the GitHub permission set for caps: each key at the highest level any capability asks, and metadata: read always, which GitHub grants every token.

SECURITY: it never returns an empty or nil map. GitHub reads a token request that names NO permissions as "every permission the installation holds", so an empty result handed to a minter would be the widest token there is, not the narrowest. A capability that is not grantable is an ERROR for the same reason ScopesFor's is in adoscope: an unclassified write must be refused, not minted for. A caller MUST treat a non-nil error as "refuse": the map it returns then is nil, and a nil permission map handed to a minter is the all-permissions request above.

func Permits

func Permits(granted []Capability, repos []string, v Verdict) bool

Permits is THE gate: may a run holding granted, launched for repos, perform v? The only spelling of "allowed" this package offers.

SECURITY: it checks TWO things and a caller needs both. The capability: v's must be grantable and in granted. And the pin: a request that names a repository must name one of repos, and one that names only an organisation or account must name the owner of one of repos — a person's own token reaches every repository they can, so the path is the only place a run is held to its own. repos are "owner/name" as RepoKey reads them; an entry it cannot read matches nothing, and an empty list permits nothing that names a repository or an owner.

METADATA is the one verdict no list names: any run holding at least one grantable capability may make it, inside the same pin.

func RepoKey

func RepoKey(s string) (string, bool)

RepoKey is "owner/name" in the one spelling this package compares repositories in: lowercased, with a trailing ".git" removed. ok=false for anything that is not exactly an owner and a repository name.

Types

type Capability

type Capability string

Capability is the ONE access name a classified request needs. The set is closed: a request is one of these or it is refused.

const (
	// CapCodeRead is a read of a repository's contents, commits, branches,
	// releases and pull requests, and a clone or fetch.
	CapCodeRead Capability = "code_read"
	// CapCodeWrite is a git push. The REST routes that change content or refs
	// are not this capability: they are CapRepoContentWriteREST, refused.
	CapCodeWrite Capability = "code_write"
	// CapWorkflowsWrite is a push that changes a workflow file under
	// .github/workflows. GitHub refuses such a push from a token without its
	// separate workflows permission, so it is its own capability, and it means
	// nothing without CapCodeWrite beside it. The classifier cannot see which
	// files a push touches; GitHub's token check is what tells them apart.
	CapWorkflowsWrite Capability = "workflows_write"
	// CapPR is opening, updating and reviewing a pull request. Merging one,
	// or updating its branch, writes the repository and is
	// CapRepoContentWriteREST.
	CapPR Capability = "pr"
	// CapIssuesRead is a read of issues, their comments, labels and milestones.
	CapIssuesRead Capability = "issues_read"
	// CapIssuesWrite is creating and changing issues, comments, labels and
	// milestones. A pull request's conversation comment is an issue comment
	// on GitHub, so it is this capability, not CapPR.
	CapIssuesWrite Capability = "issues_write"
	// CapActionsRead is a read of workflows, runs, jobs, logs, artifacts and
	// caches.
	CapActionsRead Capability = "actions_read"
	// CapActionsExecute is dispatching, re-running or cancelling a workflow run.
	CapActionsExecute Capability = "actions_execute"
	// CapActionsAdmin is enabling or disabling a workflow, approving a held
	// run, and deleting runs, logs, artifacts and caches. GitHub serves it
	// from the same permission as CapActionsExecute; only the classifier
	// tells them apart.
	CapActionsAdmin Capability = "actions_admin"
	// CapPackagesRead is a read of the packages an organisation or account
	// owns.
	CapPackagesRead Capability = "packages_read"
	// CapPackagesWrite is deleting or restoring a package or one of its versions.
	CapPackagesWrite Capability = "packages_write"
	// CapRepoAdmin is changing or deleting a repository, its collaborators,
	// rulesets, branch protection and Actions settings, and creating one in an
	// organisation.
	CapRepoAdmin Capability = "repo_admin"
	// CapOrgRead is a read of an organisation's profile, members, teams and
	// repository list.
	CapOrgRead Capability = "org_read"
	// CapOrgAdmin is changing an organisation's settings, members, teams and
	// invitations.
	CapOrgAdmin Capability = "org_admin"
	// CapIdentity is reading the signed-in person's own profile (GET /user) —
	// what most tools call first to learn who they are. Not in ProfileDefault.
	CapIdentity Capability = "identity"
)

The GRANTABLE capabilities — the only values a provider row's ceiling or profile, or a policy's github_capabilities, may name, and the only ones PermissionsFor turns into permissions.

const (
	// CapDeniedTokens is every door onto a second credential: app and
	// installation tokens, OAuth authorizations, deploy keys, and the
	// registration tokens of self-hosted runners. A lane that can obtain one
	// is a lane that can leave the lane.
	CapDeniedTokens Capability = "denied_tokens"
	// CapDeniedHooks is repository and organisation webhooks: forwarding
	// events to a caller-chosen address is exfiltration with a webhook's
	// paperwork.
	CapDeniedHooks Capability = "denied_hooks"
	// CapDeniedSecrets is Actions, Dependabot, Codespaces and environment
	// secrets, and private registry credentials. GitHub serves them under
	// permissions of their own (secrets, organization_secrets, …), which
	// PermissionsFor never asks for.
	CapDeniedSecrets Capability = "denied_secrets"
	// CapDeniedAccount is everything that spans the signed-in person's whole
	// reach rather than this run's repositories: everything under /user but
	// the profile itself, the numeric /repositories aliases, notifications
	// and gists.
	CapDeniedAccount Capability = "denied_account"
	// CapDeniedSearch is the search API: a search runs across everything the
	// token can see, and its query, not its path, says where.
	CapDeniedSearch Capability = "denied_search"
	// CapDeniedGraphQL is the GraphQL endpoint — a single door onto every
	// other area, so no capability over it could mean anything.
	CapDeniedGraphQL Capability = "denied_graphql"
	// CapRepoContentWriteREST is a REST write to the repository's git data:
	// creating, moving or deleting a ref, creating a git object, writing or
	// deleting a file, merging (a branch, an upstream, a pull request),
	// updating a pull request's branch, renaming a branch, a release (which
	// creates or moves a tag), a source import, and an autofix commit. Such a
	// request reaches GitHub with no Wardyn check on which ref it moves or
	// what it writes — the run's branch confinement and push content rules
	// apply to git pushes, not to these routes — so it is refused, whatever
	// the run holds. Repository content reaches GitHub through git push.
	CapRepoContentWriteREST Capability = "repo_content_write_rest"
	// CapUnclassifiedWrite is a write this catalogue does not recognize. It is
	// the fail-closed answer, not a capability anyone can hold.
	CapUnclassifiedWrite Capability = "unclassified_write"
	// CapUnclassifiedRead is a read this catalogue does not recognize, refused
	// like its write twin rather than classified as a read GitHub might serve.
	CapUnclassifiedRead Capability = "unclassified_read"
)

The REFUSED capabilities: Capability values, not errors, so a refusal can name the area it refused, but none is grantable, so a caller refuses every one.

const CapMetadata Capability = "metadata"

CapMetadata is a read GitHub serves under the metadata permission every token holds: a repository's own description, languages, topics, tags and rulesets, and the API's root and rate limit. It is NOT grantable — no list may name it — and Permits allows it to any run holding at least one grantable capability, still held to the run's repositories.

func GrantableCapabilities

func GrantableCapabilities() []Capability

GrantableCapabilities is the grantable set in a stable order, for the "want one of: …" half of a rejected write and for a UI's picker.

func ProfileDefault

func ProfileDefault() []Capability

ProfileDefault is the profile a provider row with an empty default_profile gets: read the code. Anything wider is a row's own choice, written out on the row.

func (Capability) Denied

func (c Capability) Denied() bool

Denied reports whether c names an area no capability can be held over.

func (Capability) Grantable

func (c Capability) Grantable() bool

Grantable reports whether c may be written on a provider row or a policy and turned into a permission. Everything else is refused — test THIS, not enumerate refusals.

func (Capability) Valid

func (c Capability) Valid() bool

Valid reports whether c is a value Classify can return at all.

type Request

type Request struct {
	// Method is the HTTP method as it arrived on the request line.
	Method string
	// Host is the request's host WITHOUT a port.
	Host string
	// Path is the request's path, still percent-encoded and WITHOUT the query string.
	Path string
	// RawQuery is the request's query string as it arrived. Only the git
	// handshake reads it: that route names its service there.
	RawQuery string
	// Header may be nil. Only the method-override header is read, and it refuses.
	Header http.Header
}

Request is ONE candidate GitHub request.

type Verdict

type Verdict struct {
	// Capability is the ONE access this request needs.
	Capability Capability
	// Repo is the repository the request targets, as RepoKey spells it
	// ("owner/name"), or "" when its path names none.
	Repo string
	// Owner is the account or organisation the path names — Repo's owner, or
	// the organisation of an organisation route — or "" when it names none.
	Owner string
}

Verdict is one classification: the access the request needs and what it targets. Permits reads all three fields; a caller gating on Capability alone has dropped the repository pin.

func Classify

func Classify(req Request) (Verdict, error)

Classify names the ONE capability req needs and the repository it targets, or refuses req. It errors only where no capability could be honest: a host it does not know, a method it does not classify, a method-override header, a path whose spelling GitHub might route differently from how it reads, an owner or repository segment that is not a name, and a git handshake that does not say which service it starts. Every other request classifies, and one this catalogue does not recognize classifies as CapUnclassifiedRead or CapUnclassifiedWrite, which nobody holds.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL