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 ¶
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.
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 ¶
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.