secretscan

package
v0.109.0 Latest Latest
Warning

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

Go to latest
Published: Sep 6, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package secretscan is WB's deterministic secret-shape gate for agent-authored continuation text (wb session move's handover and wb session park's continuation). It exists because that text is written by an AI, persisted, handed to another agent, and eventually pushed -- so anything in it is effectively published. Guidance alone is not trusted here because its failure is silent: a leaked key in a continuation reads exactly like a good continuation.

WB does not maintain its own secret-shape corpus. It parses gitleaks' maintained TOML rule format (https://github.com/gitleaks/gitleaks) -- vendored at gitleaks/gitleaks.toml, see gitleaks/PROVENANCE.md for the exact pinned tag and how to refresh it without a WB release -- and applies its own fail-closed/warn-only policy on top. WB owns the integration and the blocking decision, never the regex corpus.

Index

Constants

View Source
const ExtraRulesEnvVar = "WB_SECRETSCAN_RULES"

ExtraRulesEnvVar names an additional gitleaks-schema rules file to load on top of the embedded baseline. Set it to point at a refreshed config/gitleaks.toml (see gitleaks/PROVENANCE.md) to pick up new upstream patterns without a WB release, or at a small file of internal-only token shapes.

Variables

This section is empty.

Functions

func EmbeddedRuleset

func EmbeddedRuleset() []byte

EmbeddedRuleset returns the byte-for-byte vendored gitleaks ruleset WB ships inside its own binary, so the gate always works even with no network access and no external config file. See gitleaks/PROVENANCE.md for exact provenance.

func Fingerprint

func Fingerprint(secret []byte) string

Fingerprint identifies a matched secret without ever reproducing any of its characters: a scanner that prints the secret it found has moved the leak, not stopped it. It is a truncated SHA-256 digest of the exact matched bytes plus their length, e.g. "sha256:4f9c2a1b len=41" -- stable across runs (so the same finding produces the same fingerprint, letting --override-secret name it exactly) and useless for reconstructing the input.

Deliberately not "first 4 chars + length": for several of the named patterns this gate blocks on, the first characters ARE the identifying public prefix (e.g. "AKIA", "ghp_"), but for others they overlap the secret material itself, and a reviewer of refusal output should never have to reason about which case applies. A hash carries zero raw characters in every case.

func FormatRefusal

func FormatRefusal(blocking []Finding) error

FormatRefusal renders a fail-closed refusal for one or more blocking findings. It never contains the matched value -- only rule IDs, locations, and Fingerprint's redacted digest -- and always states how to proceed, because an agent that cannot tell why it was blocked will work around the check instead of fixing the input.

func UserRulesPath

func UserRulesPath(userConfigDir string) (path string, found bool)

UserRulesPath returns the default user-level extra rules path: <config dir>/wb/secretscan/rules.toml. It never errors when the config directory cannot be determined -- it just reports found=false, since this path is optional.

Types

type Finding

type Finding struct {
	RuleID      string
	Description string
	Severity    Severity
	Segment     string
	Line        int
	Column      int
	Fingerprint string
	Source      string
}

Finding is one rule match. It never carries the matched bytes: Fingerprint is the only trace of what was found, see Fingerprint.

func (Finding) Key

func (f Finding) Key() string

Key identifies this exact occurrence for --override-secret matching: "<rule-id>:<fingerprint-digest>". Line/column are deliberately excluded so an override survives immaterial reflow of surrounding text, but a different secret (different bytes, different fingerprint) always needs its own explicit override.

func (Finding) String

func (f Finding) String() string

String renders one refusal line: which rule, where, and the redacted fingerprint -- never the matched value.

type LoadOptions

type LoadOptions struct {
	// EnvExtraRulesPath overrides ExtraRulesEnvVar's value, for tests.
	// Empty means "read the real environment variable".
	EnvExtraRulesPath *string
	// UserConfigDir overrides the user-level config directory (normally
	// os.UserConfigDir), for tests.
	UserConfigDir string
}

LoadOptions controls where LoadDefault looks for the operator-extensible rules file, beyond the WB binary's embedded baseline.

type Overrides

type Overrides map[string]bool

Overrides is a set of explicitly acknowledged findings, keyed by Finding.Key ("<rule-id>:<fingerprint-digest>"). It downgrades one exact occurrence from block to warn -- never a rule ID alone, and never a blanket "skip scanning" switch. A different secret produces a different fingerprint and therefore needs its own explicit override, so this can never be reused as a standing bypass.

Overrides must be constructed from caller-supplied --override-secret values (see ParseOverrides), which in turn can only be produced by first running into the refusal and reading its printed fingerprint. That sequencing -- fail, read, decide, re-run with the exact key -- is the point: an override is explicit and effortful, never the path of least resistance.

func ParseOverrides

func ParseOverrides(raw []string) (Overrides, error)

ParseOverrides parses repeatable "<rule-id>:<fingerprint-digest>" CLI values, e.g. "aws-access-token:sha256:4f9c2a1b", as produced verbatim by Finding.Key. Every value must be well-formed; a malformed override is a hard error rather than a silently ignored no-op, because a bypass an agent believes is armed but is not would defeat its own purpose.

func (Overrides) Covers

func (o Overrides) Covers(finding Finding) bool

Covers reports whether finding was explicitly acknowledged.

type Result

type Result struct {
	Findings []Finding
}

Result is every finding from one Scan call.

func (Result) Blocking

func (r Result) Blocking(overrides Overrides) []Finding

Blocking returns every SeverityBlock finding not covered by overrides.

func (Result) Warnings

func (r Result) Warnings(overrides Overrides) []Finding

Warnings returns every SeverityWarn finding, plus every SeverityBlock finding that was overridden (so the override is still surfaced, not silently dropped).

type Rule

type Rule struct {
	ID          string
	Description string
	Regex       *regexp.Regexp
	// Keywords are a cheap case-insensitive substring pre-filter: if none of
	// them appear in the scanned content, the (potentially expensive) regex
	// is skipped. An empty Keywords list means always evaluate the regex.
	Keywords []string
	Severity Severity
	// Source names where this rule came from, for refusal messages and
	// audit trails: "gitleaks-embedded", or the loaded config file path.
	Source string
}

Rule is one compiled secret-shape detector.

func LoadRulesFile

func LoadRulesFile(path string) ([]Rule, []string, error)

LoadRulesFile parses one operator-supplied extra rules file. It uses the same [[rules]] TOML schema gitleaks itself defines, plus one WB-only optional field per rule, `severity = "warn"` (see classifyExtraRule).

type Scanner

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

Scanner evaluates Rules against text. The zero value is not usable; build one with Load or LoadDefault.

func LoadDefault

func LoadDefault(options LoadOptions) (*Scanner, []string, error)

LoadDefault builds the Scanner WB uses in production: the embedded gitleaks-derived baseline, plus whichever extra rules file is configured (env var first, then the user-level default path), if it exists. A missing extra rules file is not an error -- it is the common case.

func NewScanner

func NewScanner(rules []Rule) *Scanner

NewScanner builds a Scanner from already-loaded rules, for tests and for callers assembling a custom rule set directly.

func (*Scanner) Rules

func (s *Scanner) Rules() []Rule

Rules returns every loaded rule, for diagnostics (e.g. `wb` printing what a scan ran with). Callers must not mutate the result.

func (*Scanner) Scan

func (s *Scanner) Scan(segments ...Segment) Result

Scan evaluates every rule against every segment and returns every match. It never returns an error: an unusable rule was already dropped at load time (see parseTOMLRuleset), so scanning itself cannot fail.

type Segment

type Segment struct {
	Name    string
	Content []byte
}

Segment is one named, independently line-numbered slice of the text under scan, e.g. a park continuation's distinct fields (--summary, --validation, --remaining, the body file) so a refusal can say exactly which one matched.

type Severity

type Severity string

Severity is WB's own policy classification for a rule, not anything gitleaks declares. See policy.go for how it is assigned.

const (
	// SeverityBlock rules refuse the operation outright: fail closed. Every
	// rule loaded from the vendored/embedded ruleset is Block unless it is
	// named in heuristicRuleIDs.
	SeverityBlock Severity = "block"
	// SeverityWarn rules are reported but never refuse. Reserved for
	// entropy-driven heuristics that lack a fixed, brand-specific shape and
	// are known to false-positive on hashes, UUIDs, and base64 blobs.
	SeverityWarn Severity = "warn"
)

Jump to

Keyboard shortcuts

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