Documentation
¶
Overview ¶
Package gate is the pre-execution and pre-edit policy engine (RFC-001 §5.5): commands are parsed with a real shell parser (never regex), classified against the effect catalogue, and evaluated against CEL rules over the declared environment. Every decision is appended to an audit log.
Index ¶
Constants ¶
const ( VerdictAllow = "allow" VerdictApproval = "require_approval" VerdictDeny = "deny" )
Decision severities, weakest to strongest.
Variables ¶
var ErrBlocked = errors.New("blocked by policy")
ErrBlocked marks decisions that must stop the caller (enforce mode); the CLI maps it to a distinct exit code for hooks and CI.
Functions ¶
func Audit ¶
Audit appends the decision to <root>/.seamark/audit.jsonl (0600, rotated by size and age). The default entry stores the normalized command names, a SHA-256 of the input, the verdict and the policy hash — never the raw input, which frequently carries tokens, passwords and connection strings. Opting in via policy.yaml (`audit:` → `raw: true`) persists the input line with best-effort secret redaction.
func ChangedPaths ¶ added in v0.2.0
ChangedPaths lists the repo-relative files a unified diff touches, in first-appearance order — the same header parsing EvalDiff trusts, so an advisory surface (lessons on `check`) can never disagree with the verdict about which files changed.
func FailClosed ¶ added in v0.7.0
FailClosed wraps err in Blocked when enforced. Under enforcement the gate's own failures must block: a security hook that fails open on a malformed payload or a broken policy is itself a bypass. Without enforcement the error is returned as it is, and the caller reports it while the command proceeds.
Types ¶
type Blocked ¶ added in v0.7.0
type Blocked struct {
// Cause is the reason: the matched rule messages, or the failure.
Cause error
}
Blocked is the error of a blocking outcome: a blocking verdict, or a gate failure under enforcement. It wraps ErrBlocked, so errors.Is(err, ErrBlocked) stays the one test for "exit 2", and it keeps the cause apart from the prefix, so a hook reply can carry the cause in the client's own form.
type CommandRequest ¶ added in v0.7.0
type CommandRequest struct {
// Root is the workspace root. The policy, the effect catalogue, and
// the audit log live under it.
Root string
// Command is the shell text to evaluate. An empty command is an
// error: a gate that shrugs at nothing to evaluate is a bypass.
Command string
// Enforce overrides the policy mode with enforce, as --enforce does.
Enforce bool
}
CommandRequest is one shell command to gate in a workspace.
type Decision ¶
type Decision struct {
Verdict string `json:"verdict"` // strongest of the matches
Mode string `json:"mode"` // warn | enforce
Effects []string `json:"effects"`
Matches []Match `json:"matches,omitempty"`
// Commands are the normalized command names found in the input
// (wrappers unwrapped, interpreter payloads included) — what the
// audit log stores instead of the raw command line.
Commands []string `json:"commands,omitempty"`
// PolicySHA identifies the exact policy text that produced this
// decision.
PolicySHA string `json:"policy_sha256,omitempty"`
// Notes carry uncertainty the verdict alone would hide — e.g.
// changed files the index has no symbols for. A note never changes
// the verdict; it changes how much the verdict may be trusted.
Notes []string `json:"notes,omitempty"`
}
Decision is the gate's answer for one input.
func Decide ¶ added in v0.7.0
func Decide(req CommandRequest, warn func(error)) (*Decision, error)
Decide gates one command end to end: it loads the policy and the effect catalogue of the workspace, evaluates the command, and appends the decision to the audit log. The plain command and every client hook share this order, so a policy or catalogue failure has one meaning everywhere. The decision says whether it blocks; the caller renders it in its own form.
A failure returns a Blocked error when enforcement is active at that point. Explicit enforcement is active from the start. The policy's own mode is active once the policy loads: a warn policy lets a later failure through, and an enforce policy blocks it. Under warn the same failure is a plain error.
The audit is best effort. A failed append reaches warn, when given, and never changes the decision.
func EvalCommand ¶
EvalCommand parses a shell command line, classifies every command in it (pipelines, &&-chains, substitutions, and interpreter payloads like `bash -c "…"` included) against the catalogue, and evaluates the policy. Parse failures fail closed with an error — a gate that shrugs at unparseable input is a bypass.
type Match ¶
type Match struct {
RuleID string `json:"rule"`
Verdict string `json:"verdict"`
Message string `json:"message"`
}
Match is one rule that fired.
type Policy ¶
type Policy struct {
// Mode "warn" reports verdicts without blocking; "enforce" makes deny
// and require_approval binding. Warn is the default (§10: a policy
// layer that cries wolf gets disabled).
Mode string `yaml:"mode"`
Environment struct {
Detect []string `yaml:"detect"`
ProdMarkers []string `yaml:"prod_markers"`
} `yaml:"environment"`
// Audit configures decision logging. Raw opts into persisting the
// input line (secret patterns redacted best-effort); the default
// entry carries only a SHA-256 of the input, because command lines
// frequently embed tokens, passwords and connection strings.
Audit struct {
Raw bool `yaml:"raw"`
} `yaml:"audit"`
Deny []Rule `yaml:"deny"`
RequireApproval []Rule `yaml:"require_approval"`
// Hash is the SHA-256 of the policy source that produced this rule
// set: audit entries carry it so a decision can be correlated with
// the exact policy text that made it.
Hash string `yaml:"-"`
}
Policy is the loaded rule set.
func LoadPolicy ¶
LoadPolicy returns the workspace policy at .seamark/policy.yaml, or the embedded warn-mode default when absent. The workspace file REPLACES the default rule set (policy is a whole, not a patch — partial merges would make the effective rules unreviewable). All CEL expressions compile at load time so a broken rule fails loudly, not at decision time.