githubpolicy

package
v0.12.1 Latest Latest
Warning

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

Go to latest
Published: Jun 24, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package githubpolicy syncs the cloud-hosted GitHub access-control policy down to the managed endpoint and evaluates classified GitHub activity against it locally.

The cloud authors GitHub policy as versioned, epoched rules. The managed endpoint fetches the snapshot, caches it by epoch/hash, and evaluates GitHub CLI / MCP / API activity locally. For ENG-426 the directive is observe-only: the engine records what it *would* allow or deny, but never blocks.

Precedence in the local engine is: general deterministic guardrails > this synced policy > probabilistic signals.

The contract mirrors packages/shared/src/github-policy-snapshot.ts in the kontext cloud repository.

Index

Constants

View Source
const (
	ReasonCodeAllow = "ALLOWED_POLICY_CHECK"
	ReasonCodeDeny  = "DENY_POLICY_CHECK"
)

Reason codes shared with the cloud evaluator.

View Source
const (
	ModeObserve = "observe"
	ModeEnforce = "enforce"
)

Enforcement directive for the local engine.

  • ModeObserve — evaluate every action and record a dry-run decision, but never block. Anything other than an explicit "enforce" is treated as observe.
  • ModeEnforce — block denied actions. Reserved for a later, careful step; not emitted during the observer-mode pilot.
View Source
const (
	LayerOrg   = "org"
	LayerUser  = "user"
	LayerAgent = "agent"
	// LayerEndpoint binds a rule to a managed endpoint's installation
	// ("ins_…") id. Unlike user/agent, this subject is always resolvable on
	// the managed endpoint, so it is how device-scoped policy is enforced
	// locally.
	LayerEndpoint = "endpoint"
)

Policy layers. Each rule binds a subject in exactly one layer.

View Source
const (
	EffectAllow = "allow"
	EffectDeny  = "deny"
)

Rule effects.

View Source
const DefaultRefreshInterval = 60 * time.Second

DefaultRefreshInterval is how often the managed daemon re-fetches the snapshot. Hash-unchanged responses are cheap: they only bump freshness.

View Source
const SchemaVersion = "github-policy-snapshot-v2"

SchemaVersion identifies the snapshot wire format. v2 added the endpoint layer and most-specific-wins evaluation; a snapshot at any other version is rejected (fail closed) rather than misread under the wrong semantics.

View Source
const SnapshotEndpoint = "/api/v1/policy/github/snapshot"

SnapshotEndpoint is the cloud path serving the policy snapshot. Tenancy is resolved from the install token; missing/unknown/revoked tokens get 401.

Variables

This section is empty.

Functions

func DefaultCachePathForDB

func DefaultCachePathForDB(dbPath string) string

DefaultCachePathForDB stores the snapshot next to the guard database, the same convention managedstream uses for its cursor state.

func DefaultIntervalFromEnv

func DefaultIntervalFromEnv() time.Duration

Types

type Cache

type Cache struct {
	// contains filtered or unexported fields
}

Cache holds the per-installation snapshot in memory and mirrors it to disk so the policy survives daemon restarts and cloud outages.

func NewCache

func NewCache(path string) *Cache

func (*Cache) Apply

func (c *Cache) Apply(snapshot Snapshot, fetchedAt time.Time) error

Apply installs a freshly fetched snapshot. When the hash is unchanged the rules are not re-applied or re-persisted; only freshness is updated.

func (*Cache) CurrentSnapshot

func (c *Cache) CurrentSnapshot() (Snapshot, Status, bool)

func (*Cache) LoadPersisted

func (c *Cache) LoadPersisted() error

LoadPersisted primes the cache from disk so evaluation works before the first fetch completes. The persisted snapshot starts out stale until the cloud confirms it.

func (*Cache) MarkFetchFailed

func (c *Cache) MarkFetchFailed(err error)

MarkFetchFailed records that the latest refresh failed; the cached snapshot keeps serving evaluations.

type Evaluation

type Evaluation struct {
	Request      Request
	Result       string // "allow" | "deny"
	ReasonCode   string
	Reason       string
	MatchedRules []MatchedRule
	// DecidingRuleID is the rule that determined the outcome: the first
	// matching deny, or the org-layer allow that anchored an allow. Empty
	// when a layer vetoed by silence.
	DecidingRuleID string
	Mode           string
	Epoch          int
	Hash           string
	// Stale is true when the snapshot used was not confirmed by the cloud on
	// the most recent refresh attempt.
	Stale bool
	// SubjectsResolved is false when no Kontext user/application identity was
	// available, so user/agent-layer rules could not match their subject.
	SubjectsResolved bool
}

Evaluation is the would-be decision for one request under one snapshot.

func Evaluate

func Evaluate(snapshot Snapshot, status Status, request Request) (Evaluation, bool)

Evaluate mirrors the cloud evaluator exactly. It is most-specific-wins:

  1. A rule matches when its layer subject matches and each non-null dimension equals the request value exactly (null = wildcard, no globs).
  2. Among the matching rules the most specific one decides, by this order: (a) more pinned dimensions (action/resource/branch) beats fewer; then (b) a user- or agent-layer rule beats an org-layer rule; then (c) on an exact tie of (a) and (b), deny beats allow. A broad org deny is therefore overridden by a more specific user/agent allow, which is in turn overridden by an even more specific deny.
  3. If no rule matches the request, it is denied (default deny).

The boolean result is false when the snapshot carries no rules at all (no active policy authored yet) — there is nothing to dry-run.

type GitContext

type GitContext struct {
	Branch       string
	RemoteByName map[string]string
}

GitContext carries repository context derived from the session working directory (current branch and configured remotes). It mirrors the toolInput.kontext.git enrichment the cloud classifier consumes.

func GitContextFromCWD

func GitContextFromCWD(cwd string) GitContext

GitContextFromCWD derives the current branch and configured remotes from the session working directory. This stands in for the toolInput.kontext.git enrichment the cloud classifier receives.

type MatchedRule

type MatchedRule struct {
	ID      string `json:"id"`
	Layer   string `json:"layer"`
	Effect  string `json:"effect"`
	Decided bool   `json:"decided,omitempty"`
}

MatchedRule records one rule that matched the request, and whether it was the rule that decided the outcome.

type ProviderAction

type ProviderAction struct {
	// Action is the canonical action name, e.g. "github.pr.write".
	Action string
	// Resource is the repository slug "owner/repo" when derivable.
	Resource string
	// BranchOrRef is the branch or ref when derivable (push refspecs,
	// fetch/pull positionals, current branch fallback).
	BranchOrRef string
}

ProviderAction is one classified GitHub action extracted from a tool call.

func ClassifyProviderActions

func ClassifyProviderActions(toolName string, toolInput map[string]any, gitContext func() GitContext) []ProviderAction

ClassifyProviderActions maps a tool invocation to canonical GitHub actions. gitContext is invoked lazily — only when a git command needs remote or branch resolution — because deriving it shells out to git.

func ClassifyProviderActionsWithCWD

func ClassifyProviderActionsWithCWD(toolName string, toolInput map[string]any, cwd string, gitContext func(string) GitContext) []ProviderAction

ClassifyProviderActionsWithCWD maps a tool invocation to canonical GitHub actions, using cwd as the base for git -C context resolution.

type Request

type Request struct {
	Action        string
	Resource      string
	BranchOrRef   string
	UserID        string
	ApplicationID string
	EndpointID    string
}

Request is one classified GitHub action to evaluate. UserID and ApplicationID are the Kontext user and application subjects; the managed endpoint's trusted identity is the service account + installation, so both are typically empty (unresolved) and user/agent-layer rules then never match their subject. EndpointID is the installation ("ins_…") identity of this managed endpoint — it is always known locally, so endpoint-layer rules are the way to scope policy to a specific device on the managed path.

type Rule

type Rule struct {
	ID    string `json:"id"`
	Layer string `json:"layer"`
	// SubjectID is the org id, kontext user id, application id, or endpoint
	// installation id depending on Layer.
	SubjectID string `json:"subjectId"`
	// ResourceID is a repository slug "owner/repo", or nil for any repository.
	ResourceID *string `json:"resourceId"`
	// ActionName is a canonical action name (e.g. "github.pr.write"), or nil
	// for any action.
	ActionName *string `json:"actionName"`
	// BranchOrRef is a branch or ref constraint, or nil for any branch.
	BranchOrRef *string `json:"branchOrRef"`
	Effect      string  `json:"effect"`
	// Specificity is a diagnostic hint the cloud computes for display/sorting.
	// The evaluator does not read it: it derives precedence from the rule's
	// own dimensions and layer (see Evaluate — most-specific-wins).
	Specificity int `json:"specificity"`
}

Rule is a single matchable rule. nil on ResourceID / ActionName / BranchOrRef means "matches any" for that dimension; non-nil dimensions are exact string matches. For the first GitHub pilot, policy matching uses repo (ResourceID) + branch (BranchOrRef) + action; file path and API endpoint details are audit context only.

type Snapshot

type Snapshot struct {
	SchemaVersion  string `json:"schemaVersion"`
	OrganizationID string `json:"organizationId"`
	// ProviderID is the GitHub provider id, or nil when the org has no GitHub
	// provider configured.
	ProviderID  *string `json:"providerId"`
	ProviderKey string  `json:"providerKey"`
	Mode        string  `json:"mode"`
	// Epoch is the active policy epoch; 0 when the org has no active policy.
	Epoch int `json:"epoch"`
	// Hash is a stable content hash over {epoch, rules}, independent of
	// GeneratedAt, so an unchanged policy keeps an unchanged hash.
	Hash        string `json:"hash"`
	Rules       []Rule `json:"rules"`
	GeneratedAt string `json:"generatedAt"`
}

Snapshot is the response body of GET /api/v1/policy/github/snapshot. The server resolves the organization from the install token; there is no organization parameter.

func FetchSnapshot

func FetchSnapshot(ctx context.Context, client *http.Client, cloudURL, installToken string) (Snapshot, error)

FetchSnapshot retrieves the GitHub policy snapshot from the cloud using the same per-customer install token as the authorization-ledger ingest.

func (Snapshot) Enforce

func (s Snapshot) Enforce() bool

Enforce reports whether the snapshot explicitly directs enforcement. Anything other than the literal "enforce" is observe.

type SnapshotProvider

type SnapshotProvider interface {
	CurrentSnapshot() (Snapshot, Status, bool)
}

SnapshotProvider is what the guard runtime consumes: the current snapshot, its freshness, and whether a snapshot exists at all.

type Status

type Status struct {
	// FetchedAt is when the cached snapshot was last confirmed by the cloud
	// (fetch succeeded, whether or not the hash changed).
	FetchedAt time.Time `json:"fetched_at"`
	// Stale is true when the most recent fetch attempt failed and the cache
	// is serving the last known snapshot.
	Stale bool `json:"stale"`
	// LastError holds the most recent fetch failure, if any.
	LastError string `json:"last_error,omitempty"`
}

Status describes how trustworthy the cached snapshot currently is. A stale snapshot keeps being evaluated — stale-but-deterministic beats unavailable — and the staleness is recorded on every decision.

Jump to

Keyboard shortcuts

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