Documentation
¶
Overview ¶
Package controlzero is the Go SDK for Control Zero AI agent governance.
It provides policies, audit, and observability for tool calls. Works locally with no signup.
Package controlzero exposes API key redaction + leak detection helpers shared by the host-side CLI and the runtime SDK. Mirrors the Python `controlzero.cli._secrets` module so all three SDKs agree on what counts as a leak.
Tier 0a hotfix (#174). See docs/security/key-leak-postmortem.md.
Package controlzero exports the canonical Version of the SDK so runtime callers (audit_remote.go, conformance fixtures, debug bundle) can read it without parsing go.mod or guessing.
#495 / v1 (2026-05-14): introduced alongside the controlzero_sdk_version audit-log column. The wire-format helper builds the normalized "go@<version>" string the backend ingest accepts.
CI drift guard: scripts/ci/check-go-sdk-version-drift.sh asserts Version matches the latest git tag matching `sdks/go/controlzero/v*`. A tag bump without updating this constant fails the check. The reverse (constant ahead of tag) is fine -- the next tag will catch up.
Index ¶
- Constants
- Variables
- func AliasTableJSON() string
- func ApplyTamperBehavior(stateDir string, settings PolicySettings, reason, source string) bool
- func ClearQuarantine(stateDir string)
- func ComputeFingerprint() string
- func DefaultStateDir() string
- func EnterQuarantine(stateDir, reason, source string) error
- func ExpandCandidateActions(actions []string) []string
- func ExtractTextFromArgs(args interface{}) string
- func GenerateKeypair() (ed25519.PrivateKey, string, error)
- func GetAPIURL() string
- func GetFindingsForAudit(matches []DLPMatch) []map[string]interface{}
- func HasBlockingMatch(matches []DLPMatch) bool
- func IsQuarantined(stateDir string) bool
- func LoadDLPRulesFromPolicy(policyData map[string]interface{}) []map[string]interface{}
- func LoadPrivateKey(stateDir string) (ed25519.PrivateKey, error)
- func RedactKey(key string) string
- func RedactText(text string) string
- func SavePrivateKey(priv ed25519.PrivateKey, stateDir string) (string, error)
- func SaveState(state *EnrollmentState, stateDir string) (string, error)
- func SaveTamperState(stateDir string, ts TamperState) error
- func SignRequest(state *EnrollmentState, method, path string, body []byte, stateDir string, ...) (map[string]string, error)
- type AuditEntry
- type AuditIngestResponse
- type BearerAuditOptions
- type BearerAuditSink
- type BundleFormatError
- type BundleSignatureError
- type Client
- type DLPMatch
- type DLPRule
- type DLPScanner
- type EnrollOptions
- type EnrollmentError
- type EnrollmentPolicyRule
- type EnrollmentState
- type EvalContext
- type GuardOptions
- type HeartbeatResponse
- type HostedAuthError
- type HostedBootstrapError
- type KeyMatch
- type LocalAuditLogger
- type LocalAuditOptions
- type NotEnrolledError
- type Option
- func WithAPIKey(key string) Option
- func WithAgentName(name string) Option
- func WithLogFormat(format string) Option
- func WithLogPath(path string) Option
- func WithLogRotation(maxSizeMB, maxBackups, maxAgeDays int, compress bool) Option
- func WithPolicy(policy map[string]any) Option
- func WithPolicyFile(path string) Option
- func WithStrictHosted() Option
- type ParsedPolicy
- type PolicyBundle
- type PolicyDecision
- type PolicyDeniedError
- type PolicyEvaluator
- type PolicyLoadError
- type PolicyRule
- type PolicySettings
- type PolicyValidationError
- type TamperState
- type TokenError
Constants ¶
const ( DefaultPolicyAction = "deny" DefaultPolicyOnMissing = "deny" DefaultPolicyOnTamper = "warn" )
Canonical default values when the bundle or YAML does not carry the knobs. Matches the backend's canonical defaults (see bundle_handler.go: DefaultBundleAction / DefaultBundleOnMissing / DefaultBundleOnTamper) and the hard-coded Python/Node SDK behaviour pre-#228 Phase 2 so upgrading is non-breaking in both directions.
const ( // ReasonCodeRuleMatch is emitted when a user-authored policy rule // matched and its effect (allow/deny/warn) is the decision. ReasonCodeRuleMatch = "RULE_MATCH" // ReasonCodeNoRuleMatch is emitted when the bundle loaded cleanly // but no rule matched the call. The surface then applies // default_action. ReasonCodeNoRuleMatch = "NO_RULE_MATCH" // ReasonCodeNoActivePolicies is emitted when the bundle is // structurally empty (zero attached policies). Synthetic deny // rather than a missing-bundle error so the dashboard can // distinguish "nothing attached" from "bundle broken." ReasonCodeNoActivePolicies = "NO_ACTIVE_POLICIES" // ReasonCodeBundleMissing is emitted when the client is enrolled // but has no cached bundle and cannot pull a fresh one. The // surface then applies default_on_missing. ReasonCodeBundleMissing = "BUNDLE_MISSING" // ReasonCodeBundleTampered is emitted when bundle verification // fails (bad signature, checksum mismatch, unwrap error). ReasonCodeBundleTampered = "BUNDLE_TAMPERED" // ReasonCodeMachineQuarantined is emitted when the machine is // locally quarantined (see tamper.go) and any tool call is denied // until recovery. ReasonCodeMachineQuarantined = "MACHINE_QUARANTINED" // ReasonCodeNetworkError is emitted when the backend is // unreachable (DNS, connect, timeout) and no cached bundle is // available. The surface then applies default_on_missing. ReasonCodeNetworkError = "NETWORK_ERROR" // ReasonCodeDLPBlocked is emitted when a DLP rule with // action="block" matched the tool arguments and overrode a // would-be allow decision. ReasonCodeDLPBlocked = "DLP_BLOCKED" // ReasonCodeHITLSDKTimeout is emitted when the SDK waited the // configured timeout for an approver verdict and gave up. ReasonCodeHITLSDKTimeout = "HITL_SDK_TIMEOUT" // ReasonCodeHITLSLAExpired is emitted when the backend's SLA // timer expired before any approver acted on the request. ReasonCodeHITLSLAExpired = "HITL_SLA_EXPIRED" // ReasonCodeHITLBackendUnreachable is emitted when the SDK could // not reach the backend to enqueue the approval request. ReasonCodeHITLBackendUnreachable = "HITL_BACKEND_UNREACHABLE" // ReasonCodeHITLPolicyVersionConflict is emitted when the policy // bundle changed between request and verdict, invalidating the // pending approval. ReasonCodeHITLPolicyVersionConflict = "HITL_POLICY_VERSION_CONFLICT" // ReasonCodeHITLNoApproverAvailable is emitted when no eligible // approver was online or reachable at request time. ReasonCodeHITLNoApproverAvailable = "HITL_NO_APPROVER_AVAILABLE" // ReasonCodeHITLIdentityNotInOrg is emitted when the approving // identity is not a member of the request's org. ReasonCodeHITLIdentityNotInOrg = "HITL_IDENTITY_NOT_IN_ORG" // ReasonCodeHITLIdentityRequired is emitted when the request // requires an authenticated identity claim and none was provided. ReasonCodeHITLIdentityRequired = "HITL_IDENTITY_REQUIRED" // ReasonCodeHITLIdentityClaimRejected is emitted when the // identity claim attached to the request failed verification. ReasonCodeHITLIdentityClaimRejected = "HITL_IDENTITY_CLAIM_REJECTED" // ReasonCodeHITLArgsHashMismatch is emitted when the args hash // at verdict time differs from the args hash at request time, // indicating the call payload mutated mid-approval. ReasonCodeHITLArgsHashMismatch = "HITL_ARGS_HASH_MISMATCH" )
Canonical reason_code enum values. Stable, machine-readable labels so integrations + dashboards can branch on decision provenance without regex-matching the human-readable `reason` string.
Added in #228 Phase 2 to bring the Go SDK to parity with the Python SDK. The enum is cross-language: the exact same eight strings appear on Python, Node, the compiled policy engine, the Gateway, and the backend audit envelope. New values are additive; downstream consumers MUST tolerate unknown codes.
See docs/behavior-matrix.md section S6 for the canonical list and the canonical mapping from surface event -> reason_code.
const ( SyntheticPolicyIDPrefix = "synthetic:" SyntheticPolicyIDNoRuleMatch = "synthetic:NO_RULE_MATCH" SyntheticPolicyIDNoActive = "synthetic:NO_ACTIVE_POLICIES" SyntheticPolicyIDBundleMiss = "synthetic:BUNDLE_MISSING" SyntheticPolicyIDResGateSkip = "synthetic:RESOURCE_GATE_SKIP" SyntheticPolicyIDQuarantine = "synthetic:QUARANTINE" )
Synthetic policy_id sentinels (T79 / the deny-deny postmortem, 2026-05-11). When a deny is emitted by anything OTHER than a user-authored rule, the SDK stamps the audit row's PolicyID with one of these `synthetic:*` values so the audit dashboard can render a recognizable chip and link it to the right troubleshooting anchor. Without this, four very different bug classes (stale bundle, missing resource gate, vocabulary mismatch, genuine no-match) all looked identical in the Policy column (blank placeholder + Decision=Deny + reason_code=NO_RULE_MATCH).
Keep these in lockstep with the Python (`SYNTHETIC_*`) and Node (`SYNTHETIC_*`) constants; the audit dashboard matches on the exact strings.
const AliasTool = "database"
AliasTool is the only tool covered by this alias table today. Other tools were added post-#350 and always emitted canonical names.
const Version = "v1.7.6"
Version is the package version of the controlzero Go SDK. Bump this in lockstep with the git tag in sdks/go/controlzero. The drift CI guard enforces equality on every PR that touches the package.
Variables ¶
var ErrBundleSignatureInvalid = errors.New("policy bundle signature verification failed")
Sentinel errors.
var ErrHostedModeNotImplemented = errors.New(
"controlzero: hosted mode (dashboard policies + remote audit) is not yet implemented in this package.\n" +
" - For local mode, provide a policy: controlzero.New(controlzero.WithPolicy(...))\n" +
" - For hosted mode today, use the legacy SDK at sdks/go/control-zero\n" +
" - Hosted mode in this package is coming in a future release.",
)
HostedModeNotImplemented is returned when an API key is set but no local policy is provided. Hosted mode is not yet implemented in this slim package; users should provide a local policy or use the legacy SDK.
var ErrHybridMode = errors.New(
"controlzero: explicit local policy overrides the hosted bundle (strict_hosted=true)",
)
ErrHybridMode is returned when an API key and local policy are both provided AND WithStrictHosted was set. T103: message reworded from "manual policy override detected" to "explicit local policy overrides the hosted bundle". Semantics unchanged.
ValidDefaultActions is the canonical enum for settings.default_action and bundle-level default_action. See S1.
var ValidOnMissing = map[string]bool{ "deny": true, "allow": true, }
ValidOnMissing is the canonical enum for settings.default_on_missing and bundle-level default_on_missing. See S2. `last-known-good` is deferred to Phase 3.
var ValidReasonCodes = map[string]bool{ ReasonCodeRuleMatch: true, ReasonCodeNoRuleMatch: true, ReasonCodeNoActivePolicies: true, ReasonCodeBundleMissing: true, ReasonCodeBundleTampered: true, ReasonCodeMachineQuarantined: true, ReasonCodeNetworkError: true, ReasonCodeDLPBlocked: true, ReasonCodeHITLSDKTimeout: true, ReasonCodeHITLSLAExpired: true, ReasonCodeHITLBackendUnreachable: true, ReasonCodeHITLPolicyVersionConflict: true, ReasonCodeHITLNoApproverAvailable: true, ReasonCodeHITLIdentityNotInOrg: true, ReasonCodeHITLIdentityRequired: true, ReasonCodeHITLIdentityClaimRejected: true, ReasonCodeHITLArgsHashMismatch: true, }
ValidReasonCodes is the full enum, exposed for tests + downstream validators. Unknown codes from a newer SDK version are still permitted on the wire; this set is informational, not a gate.
var ValidSyntheticPolicyIDs = map[string]bool{ SyntheticPolicyIDNoRuleMatch: true, SyntheticPolicyIDNoActive: true, SyntheticPolicyIDBundleMiss: true, SyntheticPolicyIDResGateSkip: true, SyntheticPolicyIDQuarantine: true, SyntheticPolicyIDEngineUnavail: true, }
ValidSyntheticPolicyIDs is the full set, exposed for tests and for downstream consumers that want to recognize a synthetic deny without string-prefix matching.
var ValidTamperBehaviors = map[string]bool{ "warn": true, "deny": true, "deny-all": true, "quarantine": true, }
ValidTamperBehaviors mirrors the Python + Node enum so policies authored once validate identically across all SDKs. Source of truth: docs/behavior-matrix.md section S3.
Functions ¶
func AliasTableJSON ¶ added in v1.7.0
func AliasTableJSON() string
AliasTableJSON returns a deterministic JSON dump of the alias table for parity testing against the cross-SDK fixture. Excludes the dev- only "comment" key from the on-disk fixture so each SDK's hash check compares apples to apples.
func ApplyTamperBehavior ¶ added in v1.6.0
func ApplyTamperBehavior(stateDir string, settings PolicySettings, reason, source string) bool
ApplyTamperBehavior applies the configured default_on_tamper mode when tamper is detected. Semantics match the Python + Node SDKs so cross-language behaviour is consistent:
"warn" -> only log (no quarantine, no state change). "deny" -> caller should deny this one call; no quarantine. "deny-all" -> enter quarantine, deny everything until cleared. "quarantine" -> same as deny-all (alias). unknown / "" -> treat as "warn" (safest forward-compat).
Returns true if the machine entered quarantine (so the caller can log / surface the state transition). No-op if stateDir is empty.
func ClearQuarantine ¶
func ClearQuarantine(stateDir string)
ClearQuarantine removes the quarantine file. Best-effort, never errors.
func ComputeFingerprint ¶
func ComputeFingerprint() string
ComputeFingerprint returns a stable 32-char hex hash of host attributes. Re-running enroll on the same machine converges on the same fingerprint, which lets the backend's UNIQUE(org_id, fingerprint_hint) upsert return the existing machine_id rather than minting a new one.
func DefaultStateDir ¶
func DefaultStateDir() string
DefaultStateDir returns ~/.controlzero (or the override env var).
func EnterQuarantine ¶
EnterQuarantine creates a quarantine state file with the given reason and source.
func ExpandCandidateActions ¶ added in v1.7.0
ExpandCandidateActions expands an input slice of candidate actions to include every known alias in both directions (legacy -> canonical AND canonical -> legacy). Originals stay first in stable input order; expansions follow in deterministic order.
func ExtractTextFromArgs ¶
func ExtractTextFromArgs(args interface{}) string
ExtractTextFromArgs flattens nested args into a single scannable string.
func GenerateKeypair ¶
func GenerateKeypair() (ed25519.PrivateKey, string, error)
GenerateKeypair mints a fresh signing keypair and returns the private key bytes plus the PEM-encoded public key suitable for the /api/enroll machine_pubkey field.
func GetAPIURL ¶
func GetAPIURL() string
GetAPIURL resolves the Control Zero API base URL from env var, or falls back to the SaaS default.
func GetFindingsForAudit ¶
GetFindingsForAudit converts matches to audit-safe findings.
For secret-category matches, only count is included (no text). For other categories, the matched text is included.
func HasBlockingMatch ¶
HasBlockingMatch reports whether any match has Action="block".
func IsQuarantined ¶
IsQuarantined checks whether the quarantine state is set without loading the full struct. Returns false if the file does not exist or is malformed.
func LoadDLPRulesFromPolicy ¶
LoadDLPRulesFromPolicy extracts DLP rules from a policy dict's dlp_rules section. Returns nil if no dlp_rules section exists.
func LoadPrivateKey ¶
func LoadPrivateKey(stateDir string) (ed25519.PrivateKey, error)
LoadPrivateKey reads the private key from <stateDir>/machine.key.
func RedactKey ¶ added in v1.7.2
RedactKey converts a full `cz_live_<64hex>` key to `cz_live_***<last5>`. Returns the input verbatim if it does not match the expected shape, so caller-side `fmt.Println(RedactKey(maybe))` is always safe.
func RedactText ¶ added in v1.7.2
RedactText applies RedactKey to every `cz_(live|test)_*` match inside an arbitrary string.
func SavePrivateKey ¶
func SavePrivateKey(priv ed25519.PrivateKey, stateDir string) (string, error)
SavePrivateKey writes the private key to <stateDir>/machine.key with mode 0600 via an atomic tmp + rename.
func SaveState ¶
func SaveState(state *EnrollmentState, stateDir string) (string, error)
SaveState persists the enrollment state to <stateDir>/enrollment.json.
func SaveTamperState ¶
func SaveTamperState(stateDir string, ts TamperState) error
SaveTamperState writes the quarantine state to the given directory. Uses tmp + rename for atomicity. Permissions are set to 0600.
func SignRequest ¶
func SignRequest(state *EnrollmentState, method, path string, body []byte, stateDir string, now int64) (map[string]string, error)
SignRequest builds the X-CZ-* headers for a signed machine request. Canonical signed string format matches the backend exactly:
machine_id + "\n" + ts + "\n" + METHOD + "\n" + path + "\n" + sha256_hex(body)
Types ¶
type AuditEntry ¶
type AuditEntry struct {
ID string `json:"id"`
User string `json:"user,omitempty"`
ToolName string `json:"tool_name"`
Decision string `json:"decision"`
PolicyID string `json:"policy_id,omitempty"`
RuleID string `json:"rule_id,omitempty"`
Reason string `json:"reason,omitempty"`
Hostname string `json:"hostname,omitempty"`
Mode string `json:"mode,omitempty"`
Ts string `json:"ts,omitempty"`
VerbosityLevel string `json:"verbosity_level,omitempty"`
}
AuditEntry is one decision record streamed via /api/audit.
type AuditIngestResponse ¶
AuditIngestResponse is the body of POST /api/audit.
func SendAuditBatch ¶
func SendAuditBatch(ctx context.Context, state *EnrollmentState, stateDir string, entries []AuditEntry) (*AuditIngestResponse, error)
SendAuditBatch streams up to 500 entries to /api/audit. The caller is responsible for batching larger flushes.
type BearerAuditOptions ¶
BearerAuditOptions configures a BearerAuditSink.
type BearerAuditSink ¶
type BearerAuditSink struct {
// contains filtered or unexported fields
}
BearerAuditSink is the hosted-mode audit shipper.
func NewBearerAuditSink ¶
func NewBearerAuditSink(opts BearerAuditOptions) *BearerAuditSink
NewBearerAuditSink constructs a hosted audit sink.
func (*BearerAuditSink) Close ¶
func (s *BearerAuditSink) Close() error
Close flushes pending entries and stops the background goroutine.
func (*BearerAuditSink) IsDisabled ¶
func (s *BearerAuditSink) IsDisabled() bool
IsDisabled reports whether the sink has permanently disabled itself (e.g. due to 401 from the backend).
func (*BearerAuditSink) Log ¶
func (s *BearerAuditSink) Log(entry map[string]any)
Log appends an entry to the buffer. Non-blocking.
type BundleFormatError ¶
type BundleFormatError struct{ Msg string }
BundleFormatError wraps a bundle wire-format problem surfaced to the user. Distinct from bundle signature/crypto failures.
func (*BundleFormatError) Error ¶
func (e *BundleFormatError) Error() string
type BundleSignatureError ¶
type BundleSignatureError struct{ Msg string }
BundleSignatureError wraps a signature/AEAD failure surfaced to the user. Fails closed -- the bundle is untrusted.
func (*BundleSignatureError) Error ¶
func (e *BundleSignatureError) Error() string
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the user-facing entrypoint.
func New ¶
New constructs a Client with the given options. Returns an error rather than panicking on invalid configuration.
This is the synchronous entry point. Hosted mode (WithAPIKey with no local policy) performs a network fetch of the signed policy bundle during construction. Use NewWithContext to pass your own context.
func NewWithContext ¶
NewWithContext is New with a caller-supplied context. The context governs the hosted-mode bootstrap + bundle-pull HTTP calls.
func (*Client) AgentName ¶ added in v1.6.0
AgentName returns the agent identity attached to audit events for this client. Resolution order: WithAgentName option > CZ_AGENT_NAME env var > "default-agent".
func (*Client) Guard ¶
func (c *Client) Guard(tool string, opts GuardOptions) (PolicyDecision, error)
Guard evaluates a tool call against the loaded policy.
Returns a PolicyDecision and an optional error. The error is non-nil only when RaiseOnDeny was true AND the decision is deny: in that case the returned error is *PolicyDeniedError.
func (*Client) PolicySettings ¶ added in v1.6.0
func (c *Client) PolicySettings() PolicySettings
PolicySettings returns the effective settings block (default_action, default_on_missing, default_on_tamper, tamper_behavior) that the client was constructed with. Useful for `controlzero status` style introspection and for tests. Returns the canonical defaults if no local policy was configured.
type DLPMatch ¶
type DLPMatch struct {
RuleID string
RuleName string
Category string
Action string
MatchedText string // plaintext for pii/financial, SHA-256 hash for secret
Offset int
Count int
}
DLPMatch is a single DLP match result.
type DLPRule ¶
type DLPRule struct {
ID string
Name string
Pattern string
Category string // "pii", "financial", "secret", "healthcare"
Action string // "detect", "block", "mask"
Region string
// contains filtered or unexported fields
}
DLPRule is a single DLP scanning rule with a precompiled regex.
type DLPScanner ¶
type DLPScanner struct {
// contains filtered or unexported fields
}
DLPScanner scans text for DLP pattern matches.
Loads built-in patterns on creation. Custom rules can be added via AddRules or passed during construction with NewDLPScannerWithRules.
func NewDLPScanner ¶
func NewDLPScanner() *DLPScanner
NewDLPScanner creates a scanner with all built-in patterns loaded.
func NewDLPScannerNoBuiltins ¶
func NewDLPScannerNoBuiltins() *DLPScanner
NewDLPScannerNoBuiltins creates a scanner with NO built-in patterns. Only custom rules added via AddRules will be active.
func NewDLPScannerWithRules ¶
func NewDLPScannerWithRules(customRules []map[string]interface{}) *DLPScanner
NewDLPScannerWithRules creates a scanner with built-in patterns plus the given custom rules.
func (*DLPScanner) AddRules ¶
func (s *DLPScanner) AddRules(rawRules []map[string]interface{})
AddRules adds custom rules on top of existing ones. Each map must contain at least a "pattern" key. Invalid regex patterns are silently skipped.
func (*DLPScanner) RuleCount ¶
func (s *DLPScanner) RuleCount() int
RuleCount returns the number of loaded rules.
func (*DLPScanner) Scan ¶
func (s *DLPScanner) Scan(text string) []DLPMatch
Scan scans text against all loaded DLP rules.
For secret-category rules, MatchedText is a SHA-256 hash of the actual matched text. For all other categories, MatchedText contains the literal matched string.
type EnrollOptions ¶
type EnrollOptions struct {
APIURL string
Token string
Hostname string // optional override
FingerprintHint string // optional override
PluginVersions map[string]string // optional override
StateDir string // optional override (default: ~/.controlzero)
UserEmail string // optional
Timeout time.Duration // optional, default 30s
}
EnrollOptions is the input to Enroll.
type EnrollmentError ¶
type EnrollmentError struct{ Msg string }
EnrollmentError is the typed error for everything in this file.
func (*EnrollmentError) Error ¶
func (e *EnrollmentError) Error() string
type EnrollmentPolicyRule ¶
type EnrollmentPolicyRule struct {
ID string `json:"id"`
Name string `json:"name"`
Pattern string `json:"pattern"`
Category string `json:"category"`
Action string `json:"action"`
Scopes []string `json:"scopes"`
}
EnrollmentPolicyRule is one entry inside a PolicyBundle. Slim projection of the admin DLPRule shape -- no lifecycle / approver fields.
type EnrollmentState ¶
type EnrollmentState struct {
MachineID string `json:"machine_id"`
OrgID string `json:"org_id"`
APIURL string `json:"api_url"`
Hostname string `json:"hostname"`
FingerprintHint string `json:"fingerprint_hint"`
MachinePubkeyPEM string `json:"machine_pubkey_pem"`
EnrolledAt string `json:"enrolled_at"`
PolicyVersion int `json:"policy_version"`
TamperBehavior string `json:"tamper_behavior"`
OrgSigningPubkeyPEM string `json:"org_signing_pubkey_pem"`
PluginVersions map[string]string `json:"plugin_versions"`
}
EnrollmentState is everything the SDK needs to authenticate signed requests after a successful enroll round-trip.
func Enroll ¶
func Enroll(ctx context.Context, opts EnrollOptions) (*EnrollmentState, error)
Enroll runs the round-trip and persists state + private key. Idempotent on the same machine because the backend dedups by (org_id, fingerprint_hint).
func LoadState ¶
func LoadState(stateDir string) (*EnrollmentState, error)
LoadState reads the persisted enrollment state. Returns (nil, nil) when no state file exists (i.e. machine not yet enrolled).
type EvalContext ¶
EvalContext is optional context for resource-level matching.
type GuardOptions ¶
type GuardOptions struct {
Args map[string]any
Method string
RaiseOnDeny bool
Context *EvalContext
}
GuardOptions configures a single Guard call.
type HeartbeatResponse ¶
type HeartbeatResponse struct {
ServerTime string `json:"server_time"`
PolicyVersion int `json:"policy_version"`
}
HeartbeatResponse mirrors the JSON returned by /api/heartbeat.
func Heartbeat ¶
func Heartbeat(ctx context.Context, state *EnrollmentState, stateDir string, pluginVersions map[string]string) (*HeartbeatResponse, error)
Heartbeat sends a single signed POST /api/heartbeat. The optional pluginVersions update is merged server-side.
type HostedAuthError ¶
type HostedAuthError struct{ Msg string }
HostedAuthError is returned when the project API key is rejected by the backend (401/403). Permanent: retrying with the same key will not help.
func (*HostedAuthError) Error ¶
func (e *HostedAuthError) Error() string
type HostedBootstrapError ¶
type HostedBootstrapError struct{ Msg string }
HostedBootstrapError is returned when hosted mode cannot initialize (backend unreachable, malformed response, etc.) AND no cached bundle is available. The SDK fails closed: without a valid policy, New() refuses to construct.
func (*HostedBootstrapError) Error ¶
func (e *HostedBootstrapError) Error() string
type KeyMatch ¶ added in v1.7.2
KeyMatch describes one occurrence of a `cz_(live|test)_*` substring inside arbitrary text.
func FindKeyLeaks ¶ added in v1.7.2
FindKeyLeaks returns every `cz_(live|test)_*` substring inside `text`. Safe on arbitrary untrusted input (linear regex).
type LocalAuditLogger ¶
type LocalAuditLogger struct {
// contains filtered or unexported fields
}
LocalAuditLogger writes JSON Lines audit entries to a local file with optional size-based rotation via lumberjack.
Why lumberjack?
It's the boring choice for log rotation in Go. Single dependency, mature, zero ceremony, plays well with the standard io.Writer interface.
func NewLocalAuditLogger ¶
func NewLocalAuditLogger(opts LocalAuditOptions) *LocalAuditLogger
NewLocalAuditLogger constructs a sink. If the path is unwritable, all subsequent writes go to stderr instead.
func (*LocalAuditLogger) Close ¶
func (l *LocalAuditLogger) Close() error
Close flushes and closes the underlying file. Safe to call multiple times.
func (*LocalAuditLogger) Log ¶
func (l *LocalAuditLogger) Log(entry map[string]any)
Log writes a single audit entry.
type LocalAuditOptions ¶
type LocalAuditOptions struct {
LogPath string
MaxSizeMB int // size-based rotation; 0 = no rotation
MaxBackups int // how many rotated files to keep
MaxAgeDays int // delete rotated files older than N days
Compress bool // gzip rotated files
Format string // "json" (default) or "pretty"
}
LocalAuditOptions configures the local audit sink.
type NotEnrolledError ¶
type NotEnrolledError struct{ Msg string }
NotEnrolledError fires when a method that needs local state is called before Enroll has run successfully.
func (*NotEnrolledError) Error ¶
func (e *NotEnrolledError) Error() string
type Option ¶
type Option func(*clientConfig)
Option configures a Client at construction time.
func WithAPIKey ¶
func WithAgentName ¶ added in v1.6.0
WithAgentName sets the agent identity attached to audit events. Falls back to CZ_AGENT_NAME env var, then to "default-agent".
func WithLogFormat ¶
func WithLogPath ¶
func WithLogRotation ¶
func WithPolicy ¶
func WithPolicyFile ¶
func WithStrictHosted ¶
func WithStrictHosted() Option
type ParsedPolicy ¶ added in v1.6.0
type ParsedPolicy struct {
Rules []PolicyRule
Settings PolicySettings
}
ParsedPolicy is the full result of loading a policy source. Matches Python's ParsedPolicy and Node's ParsedPolicy one-to-one so fixtures + cross-language parity tests stay consistent.
func LoadPolicyFull ¶ added in v1.6.0
func LoadPolicyFull(source any) (ParsedPolicy, error)
LoadPolicyFull is LoadPolicy but also returns the parsed settings block. Introduced in #228 Phase 2. Prefer this in new code so the evaluator can honour default_action / default_on_missing / default_on_tamper.
type PolicyBundle ¶
type PolicyBundle struct {
PolicyVersion int `json:"policy_version"`
Rules []EnrollmentPolicyRule `json:"rules"`
}
PolicyBundle is the SDK projection returned by GET /api/policy.
func PullPolicy ¶
func PullPolicy(ctx context.Context, state *EnrollmentState, stateDir string) (*PolicyBundle, error)
PullPolicy issues a signed GET /api/policy with If-None-Match using the cached version. Returns nil bundle on 304 (caller keeps the existing local copy). On 200, also persists the new policy_version to the state file.
type PolicyDecision ¶
type PolicyDecision struct {
Effect string // "allow", "deny", "warn", "audit"
PolicyID string
Reason string
ReasonCode string
EvaluatedRules int
}
PolicyDecision is the result of evaluating a tool call against a policy.
ReasonCode is a machine-readable, cross-language enum value drawn from the constants in reason_codes.go (RULE_MATCH, NO_RULE_MATCH, NO_ACTIVE_POLICIES, BUNDLE_MISSING, BUNDLE_TAMPERED, MACHINE_QUARANTINED, NETWORK_ERROR, DLP_BLOCKED). Automation should branch on ReasonCode, not on Reason -- Reason is free text that may be re-worded between releases. Empty string when the decision predates #228 Phase 2 or when a user-authored rule did not declare a reason_code of its own.
func (PolicyDecision) Allowed ¶
func (d PolicyDecision) Allowed() bool
Allowed reports whether the call was allowed.
func (PolicyDecision) Decision ¶
func (d PolicyDecision) Decision() string
Decision returns Effect. Reads more naturally in user code.
func (PolicyDecision) Denied ¶
func (d PolicyDecision) Denied() bool
Denied reports whether the call was denied.
type PolicyDeniedError ¶
type PolicyDeniedError struct {
Decision PolicyDecision
}
PolicyDeniedError is returned by Guard when a deny decision is reached AND the caller passed RaiseOnDeny.
func (*PolicyDeniedError) Error ¶
func (e *PolicyDeniedError) Error() string
type PolicyEvaluator ¶
type PolicyEvaluator struct {
// contains filtered or unexported fields
}
PolicyEvaluator runs in-process. Fail-closed by default: if no rule matches AND no default_action was provided, the call is denied.
Pattern matching uses path.Match, which is Go's stdlib equivalent of fnmatch (case-sensitive shell glob). Friendly schema like "delete_*" is canonicalized to "delete_*:*" in policy_loader.go BEFORE rules reach this evaluator. By the time rules arrive here, every action is in canonical "tool:method" form.
defaultAction controls the no-match path (added in #228 Phase 2):
"" -> fall back to "deny" with ReasonCodeNoRuleMatch (legacy /
fail-closed contract; matches pre-Phase-2 behaviour).
"deny" -> deny + ReasonCodeNoRuleMatch.
"allow" -> allow + ReasonCodeNoRuleMatch.
"warn" -> warn + ReasonCodeNoRuleMatch (effect "warn"; caller
decides whether to fire).
any other value -> treated as empty; fall back to deny. This is
defensive: a corrupt or future-version bundle MUST NOT flip an
org from deny to allow through a typo.
func NewPolicyEvaluator ¶
func NewPolicyEvaluator(rules []PolicyRule) *PolicyEvaluator
NewPolicyEvaluator constructs an evaluator with an optional initial rule set. defaultAction is left empty, which keeps the legacy fail-closed contract (deny on no-match). Use NewPolicyEvaluatorWithSettings to honour a bundle's default_action.
func NewPolicyEvaluatorWithSettings ¶ added in v1.6.0
func NewPolicyEvaluatorWithSettings(rules []PolicyRule, settings PolicySettings) *PolicyEvaluator
NewPolicyEvaluatorWithSettings constructs an evaluator that honours the per-bundle settings block. If settings.DefaultAction is empty or unknown, the evaluator falls back to deny (fail-closed contract).
func (*PolicyEvaluator) Evaluate ¶
func (e *PolicyEvaluator) Evaluate(tool, method string, ctx *EvalContext) PolicyDecision
Evaluate returns a PolicyDecision for the given tool/method. Always returns; never panics.
func (*PolicyEvaluator) Load ¶
func (e *PolicyEvaluator) Load(rules []PolicyRule)
Load replaces the rule set.
func (*PolicyEvaluator) SetDefaultAction ¶ added in v1.6.0
func (e *PolicyEvaluator) SetDefaultAction(action string)
SetDefaultAction sets the no-match default. Accepts "deny" / "allow" / "warn"; any other value (including empty) reverts to the fail-closed contract.
type PolicyLoadError ¶
PolicyLoadError is returned when a policy file cannot be loaded.
func (*PolicyLoadError) Error ¶
func (e *PolicyLoadError) Error() string
func (*PolicyLoadError) Unwrap ¶
func (e *PolicyLoadError) Unwrap() error
type PolicyRule ¶
type PolicyRule struct {
ID string
Name string
Effect string // "allow" or "deny"
Actions []string
Resources []string
Conditions map[string]any
Reason string // Human-readable explanation, surfaced in audit + denies
ReasonCode string // Machine-readable code (see reason_codes.go)
// EscalateOnDeny is the HITL escalation tag (HITL-5c, gh#540):
// when true and the rule's effect is `deny`, future SDK versions
// will mark the resulting PolicyDecision as hitl_eligible=true.
// The actual approval-request flow ships in v1.8.0 (HITL-6a,
// gh#542); v1.7.6 just acknowledges the field so a customer
// pre-tagging rules for HITL does not crash an old client.
EscalateOnDeny bool
}
PolicyRule is the canonical internal representation of a single policy rule. The user-facing schema (see policy_loader.go) is friendlier; this is what the evaluator consumes after translation.
ReasonCode is an optional rule-level override of the emitted reason_code. Today the backend bundle translator stamps it on synthetic empty-bundle denies (NO_ACTIVE_POLICIES); user-authored rules usually leave it empty and the evaluator fills it with RULE_MATCH on match.
func LoadPolicy ¶
func LoadPolicy(source any) ([]PolicyRule, error)
LoadPolicy parses a policy from a map[string]any (in-memory) or a path (string ending in .yaml/.yml/.json). Returns the canonicalized rule list or a *PolicyValidationError / *PolicyLoadError on failure.
Backwards-compatible signature: historic callers treat the first return as a []PolicyRule which is still correct. New callers should prefer LoadPolicyFull for access to PolicySettings.
type PolicySettings ¶ added in v1.6.0
type PolicySettings struct {
// DefaultAction is the decision applied when the evaluator loads
// rules but none match. Values: deny (default), allow, warn.
DefaultAction string
// DefaultOnMissing is the decision applied when the client cannot
// load a bundle at all. Values: deny (default), allow.
DefaultOnMissing string
// DefaultOnTamper is the mode used when tamper is detected. Values:
// warn (default), deny, deny-all, quarantine.
DefaultOnTamper string
// TamperBehavior is the legacy alias for DefaultOnTamper. Kept for
// one release so operators with existing YAML files do not break.
// Populated from settings.tamper_behavior if present.
TamperBehavior string
}
PolicySettings carries the three enforcement-default knobs defined in docs/behavior-matrix.md:
- DefaultAction deny | allow | warn fired when the bundle has rules but nothing matches.
- DefaultOnMissing deny | allow fired when the client is enrolled but no bundle could be loaded.
- DefaultOnTamper warn | deny | deny-all | quarantine mirrors the existing tamper_behavior field. Both names are accepted in YAML; tamper_behavior is the legacy name.
The zero value of this struct is the canonical fallback (deny/deny/ warn). This matches every SDK's hard-coded pre-#228 behaviour, so a caller that never reads settings keeps the old behaviour.
func DefaultPolicySettings ¶ added in v1.6.0
func DefaultPolicySettings() PolicySettings
DefaultPolicySettings returns a settings block pre-populated with the canonical defaults.
func (PolicySettings) EffectiveTamperBehavior ¶ added in v1.6.0
func (s PolicySettings) EffectiveTamperBehavior() string
EffectiveTamperBehavior returns the effective tamper mode, preferring the new DefaultOnTamper field but falling back to the legacy TamperBehavior alias and finally to the canonical default.
type PolicyValidationError ¶
PolicyValidationError is returned when a policy file or map fails schema validation.
func (*PolicyValidationError) Error ¶
func (e *PolicyValidationError) Error() string
type TamperState ¶
type TamperState struct {
Quarantined bool `json:"quarantined"`
Reason string `json:"reason"`
DetectedAt string `json:"detected_at"`
Source string `json:"source"` // "policy_hmac" | "audit_chain" | "bundle_signature"
}
TamperState is the persisted quarantine state for mandatory tamper enforcement. Fields match the Python/Node SDKs exactly.
func LoadTamperState ¶
func LoadTamperState(stateDir string) TamperState
LoadTamperState reads the quarantine state from the given directory. Returns a zero-value TamperState if the file does not exist or is malformed (never returns an error).
type TokenError ¶
type TokenError struct{ Msg string }
TokenError is a more specific subtype for enroll-token rejections.
func (*TokenError) Error ¶
func (e *TokenError) Error() string
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
controlzero
command
Command controlzero is the CLI for the Go SDK.
|
Command controlzero is the CLI for the Go SDK. |
|
parity-parse
command
Parse the parity fixture with the Go SDK bundle parser and print canonical JSON of the translated policy.
|
Parse the parity fixture with the Go SDK bundle parser and print canonical JSON of the translated policy. |
|
Hello World: no API key, no signup.
|
Hello World: no API key, no signup. |
|
integrations
|
|
|
langchaingo
Package langchaingo wraps LangChainGo tools with Control Zero policy enforcement (issue #96).
|
Package langchaingo wraps LangChainGo tools with Control Zero policy enforcement (issue #96). |
|
internal
|
|
|
bundle
Package bundle parses the .czpolicy signed binary bundle format.
|
Package bundle parses the .czpolicy signed binary bundle format. |
|
Package templates exposes the policy templates as embedded files so the `controlzero` binary is self-contained.
|
Package templates exposes the policy templates as embedded files so the `controlzero` binary is self-contained. |