Documentation
¶
Overview ¶
Package githubscope limits what a run's github grant can change on GitHub to the repositories that belong to the project being worked on.
It has three parts:
- Discovery finds the GitHub repositories checked out in a workspace by reading git config files directly (it never runs git).
- The approval Store records, outside the workspace, which repositories the user has approved for each project. A repository that appears after the first run is not trusted until the user approves it, so an agent cannot widen its own access by cloning a repository or rewriting a remote and waiting for the next run.
- Policy renders a Keep rule file for the proxy's "http" scope that denies writes to any other repository.
Index ¶
- Constants
- func Approve(store *Store, project string, cands []Candidate, now time.Time) error
- func CommitRun(store *Store, plan *Plan, meta map[string]RepoMeta, now time.Time) error
- func DefaultStorePath(moatHome string) string
- func FindProject(dir string, store *Store) (string, error)
- func Forget(store *Store, project string, repos []string) (notFound []string, err error)
- func LookupFailureReason(err error) string
- func LookupRepos(ctx context.Context, client *http.Client, apiURL, token string, repos []string) (map[string]RepoMeta, map[string]error)
- func MergeHTTPPolicy(github, user []byte) ([]byte, error)
- func NormalizeRepo(s string) (string, error)
- func ParseRemoteURL(raw string) (string, bool)
- func PolicyTemplate() []byte
- func ProjectKey(workspace string) (string, error)
- func RecordFirstRun(store *Store, plan *Plan, now time.Time) error
- func RenderPolicy(p PolicyParams) ([]byte, error)
- func Reset(store *Store, project string) (existed bool, err error)
- func SetRepoMeta(store *Store, project string, meta map[string]RepoMeta) error
- func ValidatePlan(store *Store, plan *Plan, meta map[string]RepoMeta) error
- type Approval
- type Candidate
- type DiscoverResult
- type Found
- type LookupHTTPError
- type Plan
- type PolicyParams
- type Project
- type RepoMeta
- type Settings
- type Store
Constants ¶
const ( ScopeAny = "any" ScopeWorkspace = "workspace" )
Scope values for github.scope in moat.yaml.
const ( OtherReposRead = "read" OtherReposNone = "none" )
OtherRepos values for github.other_repos in moat.yaml.
const DefaultAPIURL = "https://api.github.com"
DefaultAPIURL is GitHub's REST API base URL.
const PolicyScope = "http"
PolicyScope is the Keep scope the proxy evaluates HTTP requests in.
const SourceAllowCommand = "moat github allow"
SourceAllowCommand marks repositories approved with `moat github allow`.
const SourceExtraRepos = "moat.yaml extra_repos"
SourceExtraRepos marks repositories listed in moat.yaml's extra_repos.
const StoreFileName = "github-repos.json"
StoreFileName is the approval store's file name under the moat home directory. It lives outside every workspace so an agent cannot edit it.
Variables ¶
This section is empty.
Functions ¶
func Approve ¶
Approve adds repositories to the project's approvals. It does not lock the project: a project that has never run keeps its first-run behavior, so approving one extra repository up front does not turn the workspace's own repositories into pending ones.
func CommitRun ¶
CommitRun records what a scoped run established, once the run's policy has been built: on a first run it locks the project and approves the granted repositories; when moat.yaml asked for the scope it remembers other_repos for later sticky runs; and it caches the repositories' GitHub identity. Call it only after everything that can reject the run's configuration has passed, so a run that never starts does not lock the project.
func DefaultStorePath ¶
DefaultStorePath returns the store location under moatHome (see config.GlobalConfigDir).
func FindProject ¶
FindProject returns the project `moat github` commands act on, walking up from dir: the nearest directory with a stored record (so a command run from a subdirectory — even one with its own moat.yaml — reaches the project its runs use), else the nearest directory containing moat.yaml, else dir.
func Forget ¶
Forget removes repositories from the project's approvals. It returns the names that were not approved. The project stays locked.
func LookupFailureReason ¶
LookupFailureReason gives actionable, credential-free guidance for lookup failures.
func LookupRepos ¶
func LookupRepos(ctx context.Context, client *http.Client, apiURL, token string, repos []string) (map[string]RepoMeta, map[string]error)
LookupRepos fetches RepoMeta for each repository with token, in parallel. Failures are returned per repository; callers fall back to cached metadata.
func MergeHTTPPolicy ¶
MergeHTTPPolicy appends the rules and defs of a user's http-scope Keep policy (network.keep_policy) to a rendered github policy. Keep evaluates one rule file per scope, so the two must agree on the scope-wide settings the github rules depend on: enforce mode, closed error handling, and case-insensitive matching.
func NormalizeRepo ¶
NormalizeRepo validates an "owner/repo" name and returns it lowercased. GitHub treats owner and repository names case-insensitively, so all comparisons in this package use the lowercased form.
func ParseRemoteURL ¶
ParseRemoteURL returns the lowercased "owner/repo" a git remote URL points at, or ok=false when the URL is not a github.com repository.
Accepted forms:
https://github.com/owner/repo(.git) (also http://, git://, with user@ or :port) ssh://git@github.com(:22)/owner/repo(.git) (also git+ssh://, ssh+git://) git@github.com:owner/repo(.git) (scp-like; any user)
insteadOf rewrites are not applied: the proxy enforces the policy on the request GitHub actually receives, so a rewrite can only change which repositories discovery suggests, never what a push can reach.
func PolicyTemplate ¶
func PolicyTemplate() []byte
PolicyTemplate returns the unrendered Keep rule file. Its defs hold example values, so it loads and can be tested with `keep test` as-is.
func ProjectKey ¶
ProjectKey returns the store key for a workspace path: absolute with symlinks resolved, so the same project reached by different paths shares one record.
func RecordFirstRun ¶
RecordFirstRun locks the project and approves every granted repository when the plan is a first run. It is a no-op otherwise. If another process locked the project in the meantime, that record wins.
func RenderPolicy ¶
func RenderPolicy(p PolicyParams) ([]byte, error)
RenderPolicy fills the template's defs and messages for one run.
func Reset ¶
Reset deletes the project's record: its approvals and its lock. The next run with github.scope: workspace starts over as a first run.
func SetRepoMeta ¶
SetRepoMeta caches GitHub's identity for approved repositories.
Types ¶
type Approval ¶
type Approval struct {
ApprovedAt time.Time `json:"approved_at"`
// Source is where the repository was found when it was approved, e.g.
// "./keep (origin)", "moat.yaml extra_repos", or "moat github allow".
Source string `json:"source,omitempty"`
// FullName, ID, and NodeIDs cache GitHub's identity for the repository so
// the policy can recognize it by numeric ID (/repositories/{id}) and by
// GraphQL node ID (repositoryId). They are refreshed on each run when
// GitHub is reachable.
FullName string `json:"full_name,omitempty"`
ID int64 `json:"id,omitempty"`
NodeIDs []string `json:"node_ids,omitempty"`
}
Approval records that the user trusted a repository for a project.
type DiscoverResult ¶
type DiscoverResult struct {
Repos []Found
// Truncated reports that the scan stopped at maxEntries.
Truncated bool
}
DiscoverResult is the outcome of scanning a workspace.
func Discover ¶
func Discover(workspace string) (DiscoverResult, error)
Discover finds the github.com repositories referenced by the remotes of git checkouts in workspace: the workspace itself, nested clones, linked worktrees, and checked-out submodules, up to maxDepth levels deep.
It reads git config files directly and never executes git: the workspace is writable by the agent, and running git there could execute hooks or fsmonitor commands on the host.
type Found ¶
type Found struct {
// Repo is the lowercased "owner/repo".
Repo string
// Sources says where it was found, e.g. "./keep (origin)".
Sources []string
}
Found is a GitHub repository discovered in a workspace.
type LookupHTTPError ¶
LookupHTTPError retains the status without exposing response bodies or credentials.
func (*LookupHTTPError) Error ¶
func (e *LookupHTTPError) Error() string
type Plan ¶
type Plan struct {
// ApprovalSnapshot is the store state used to build this plan and its policy.
ApprovalSnapshot *Project
// Project is the store key: the workspace's absolute, symlink-resolved path.
Project string
// Enabled is true when the run is scoped to its workspace repositories.
Enabled bool
// Sticky is true when the scope is enforced only because the project was
// locked by an earlier run; moat.yaml no longer asks for it.
Sticky bool
// FirstRun is true when the project has no approvals yet. Every repository
// found is approved (trust on first use) and the project is locked.
FirstRun bool
// LockedAt is when the project was locked (zero on the first run).
LockedAt time.Time
// Granted are the repositories this run may write to.
Granted []Candidate
// Pending were found in the workspace but are not approved. They get the
// same access as any other repository until the user approves them.
Pending []Candidate
// Missing are approved repositories that were not found this run. They
// are not granted; they stay approved for when they come back.
Missing []string
// OtherRepos is the access level for every repository not in Granted.
OtherRepos string
// Truncated reports that discovery stopped early on a very large workspace.
Truncated bool
}
Plan is the GitHub scope decision for one run. It is computed by BuildPlan, the single source of truth for both the CLI pre-flight (which prints it and asks about Pending repositories) and run creation (which enforces it), so the two cannot disagree about what a run may change.
func BuildPlan ¶
BuildPlan decides what a run in workspace may change on GitHub. existing is the project's stored record, or nil when it has none.
func (*Plan) GrantedRepos ¶
GrantedRepos returns the names in Granted.
type PolicyParams ¶
type PolicyParams struct {
// Repos are the lowercased owner/repo names the run may write to,
// including canonical names of renamed repositories.
Repos []string
// RepoIDs are GitHub's numeric repository IDs for Repos.
RepoIDs []int64
// NodeIDs are GraphQL node IDs for Repos (both formats).
NodeIDs []string
// OtherRepos is OtherReposRead or OtherReposNone.
OtherRepos string
}
PolicyParams are the per-run values rendered into the policy.
func PlanPolicyParams ¶
func PlanPolicyParams(plan *Plan, project *Project, fresh map[string]RepoMeta) PolicyParams
PlanPolicyParams builds the policy parameters for a plan. GitHub identity comes from fresh (this run's lookups) when present, else from the identity cached on the project's approvals.
type Project ¶
type Project struct {
// LockedAt is when the project was first run with github.scope:
// workspace. Once set, later runs keep the scope even if moat.yaml stops
// asking for it; `moat github forget --all` clears it.
LockedAt time.Time `json:"locked_at"`
// OtherRepos is the github.other_repos setting of the latest run whose
// moat.yaml asked for the scope. A run that is scoped only because the
// project is locked uses it, so deleting the github: block cannot loosen
// other_repos: none.
OtherRepos string `json:"other_repos,omitempty"`
Repos map[string]Approval `json:"repos"`
}
Project is the stored state for one project, keyed by its absolute path.
type RepoMeta ¶
type RepoMeta struct {
// FullName is the canonical owner/repo, lowercased. It differs from the
// name in the remote URL when the repository was renamed or transferred.
FullName string
ID int64
// NodeIDs holds the repository's GraphQL node IDs in both the legacy
// format (MDEw...) and the next-generation format (R_...). GitHub accepts
// either as input, and clients send whichever form they last received.
NodeIDs []string
}
RepoMeta is GitHub's identity for a repository.
type Settings ¶
type Settings struct {
Scope string // ScopeAny (or "") or ScopeWorkspace
OtherRepos string // OtherReposRead (or "") or OtherReposNone
ExtraRepos []string // normalized owner/repo names
}
Settings is the github: block of moat.yaml, already validated.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store persists per-project approvals as JSON. It is safe for concurrent use within a process, and across processes via an advisory lock on a sidecar lock file.
func OpenStore ¶
OpenStore opens the store at path. The file is created on first write.
A malformed file is an error rather than an empty store: silently discarding approvals would also discard the project's lock.