secretscan

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Oct 20, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package secretscan finds credentials committed to a build context or stored as plain environment values, and applies a per-app deploy policy. A finding never carries the matched value, only a short fingerprint.

Index

Constants

View Source
const (
	RuleURLCredentials = "url-credentials"
	RuleHighEntropy    = "high-entropy-secret"
)

RuleURLCredentials and RuleHighEntropy are rules with extra handling.

View Source
const (
	SourceFile = "file"
	SourceEnv  = "env"
)

Sources a finding came from.

View Source
const (
	ActionSkipped = "skipped"
	ActionAllow   = "allow"
	ActionWarn    = "warn"
	ActionBlock   = "block"
)

Gate actions stored on a run.

View Source
const (
	StatusComplete = "complete"
	StatusPartial  = "partial"
	StatusError    = "error"
)

Run statuses.

Variables

View Source
var ErrInvalid = errors.New("secretscan: invalid request")

ErrInvalid marks a rejected setting or request.

View Source
var ErrRunNotFound = errors.New("secretscan: no scan recorded for this deploy")

ErrRunNotFound means no scan was recorded for the deploy attempt.

View Source
var Rules = []Rule{
	{ID: "aws-access-key-id", Desc: "AWS access key id", Re: regexp.MustCompile(`\b(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16}\b`)},
	{ID: "aws-secret-access-key", Desc: "AWS secret access key", Re: regexp.MustCompile(`(?i)aws[\w .-]{0,24}secret[\w .-]{0,24}['"=:\s]{1,4}([A-Za-z0-9/+=]{40})`), Group: 1},
	{ID: "gcp-api-key", Desc: "Google API key", Re: regexp.MustCompile(`\bAIza[0-9A-Za-z_-]{35}\b`)},
	{ID: "azure-storage-key", Desc: "Azure storage account key", Re: regexp.MustCompile(`AccountKey=([A-Za-z0-9+/]{60,}={0,2})`), Group: 1},
	{ID: "private-key", Desc: "private key block", Re: regexp.MustCompile(`-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----`)},
	{ID: "github-token", Desc: "GitHub token", Re: regexp.MustCompile(`\bgh[pousr]_[A-Za-z0-9]{36,255}\b`)},
	{ID: "github-fine-grained-token", Desc: "GitHub fine-grained token", Re: regexp.MustCompile(`\bgithub_pat_[A-Za-z0-9_]{22,255}\b`)},
	{ID: "gitlab-token", Desc: "GitLab personal access token", Re: regexp.MustCompile(`\bglpat-[A-Za-z0-9_-]{20,}\b`)},
	{ID: "slack-token", Desc: "Slack token", Re: regexp.MustCompile(`\bxox[abprs]-[A-Za-z0-9-]{10,}\b`)},
	{ID: "slack-webhook", Desc: "Slack webhook URL", Re: regexp.MustCompile(`https://hooks\.slack\.com/services/T[A-Z0-9]+/B[A-Z0-9]+/[A-Za-z0-9]+`)},
	{ID: "stripe-secret-key", Desc: "Stripe live secret key", Re: regexp.MustCompile(`\b[sr]k_live_[0-9A-Za-z]{16,}\b`)},
	{ID: "sendgrid-key", Desc: "SendGrid API key", Re: regexp.MustCompile(`\bSG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}\b`)},
	{ID: "npm-token", Desc: "npm access token", Re: regexp.MustCompile(`\bnpm_[A-Za-z0-9]{36}\b`)},
	{ID: "jwt", Desc: "JSON web token", Re: regexp.MustCompile(`\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b`)},
	{ID: RuleURLCredentials, Desc: "password embedded in a URL", Re: regexp.MustCompile(`\b[a-z][a-z0-9+.-]{1,20}://[^\s:/@'"]{1,64}:([^\s:/@'"]{8,128})@[^\s/'"]+`), Group: 1},
}

Rules is the built-in ruleset.

Functions

func Describe

func Describe(reason string, f []Finding) string

Describe adds the top findings' rule ids and locations (never values) to a reason.

func Fingerprint

func Fingerprint(ruleID, value string) string

Fingerprint is a stable short hash of the rule and the matched value, so an allow-list entry survives a file move without exposing the value.

func RuleDescription

func RuleDescription(id string) string

RuleDescription is the human label for a rule id, or the id itself when unknown.

Types

type AllowEntry

type AllowEntry struct {
	App         string    `json:"app,omitempty"`
	Fingerprint string    `json:"fingerprint"`
	Reason      string    `json:"reason"`
	Actor       string    `json:"actor"`
	CreatedAt   time.Time `json:"created_at"`
}

AllowEntry suppresses one fingerprint for an app, with who and why.

type BlockedError

type BlockedError struct{ Reason string }

BlockedError is returned to the deploy pipeline when the policy blocks a release.

func (*BlockedError) Error

func (e *BlockedError) Error() string

type Finding

type Finding struct {
	RuleID      string `json:"rule_id"`
	Source      string `json:"source"`
	Path        string `json:"path,omitempty"`
	Line        int    `json:"line,omitempty"`
	Var         string `json:"var,omitempty"`
	Fingerprint string `json:"fingerprint"`
	Allowed     bool   `json:"allowed,omitempty"`
}

Finding is one suspected credential. Path is relative to the build context; Var is set for env findings.

type Input

type Input struct {
	App       string
	AttemptID string
	Root      string
	Env       map[string]string
}

Input is what one deploy hands the scanner. Root is the build context (empty for image deploys) and Env only the plain, non-secret values.

type Limits

type Limits struct {
	MaxFiles      int
	MaxFileBytes  int64
	MaxTotalBytes int64
	MaxFindings   int
	Timeout       time.Duration
}

Limits bound a scan so a huge repo cannot stall a deploy.

func DefaultLimits

func DefaultLimits() Limits

DefaultLimits are the built-in caps, overridable through the environment.

func LimitsFromEnv

func LimitsFromEnv(lookup func(string) (string, bool)) Limits

LimitsFromEnv reads the caps, keeping the default for a missing or invalid value.

type Option

type Option func(*Service)

Option customizes a Service.

func WithClock

func WithClock(now func() time.Time) Option

WithClock overrides the clock, for tests.

func WithDirScanner

func WithDirScanner(f func(ctx context.Context, root string, lim Limits) (Result, error)) Option

WithDirScanner swaps the directory scanner, for tests.

type Policy

type Policy string

Policy is what a scan may do to a release.

const (
	PolicyOff   Policy = "off"
	PolicyWarn  Policy = "warn"
	PolicyBlock Policy = "block"
)

Policies. Warn is the default.

func ParsePolicy

func ParsePolicy(s string) (Policy, error)

ParsePolicy validates a policy name.

type Result

type Result struct {
	Findings     []Finding `json:"findings"`
	FilesScanned int       `json:"files_scanned"`
	FilesSkipped int       `json:"files_skipped"`
	Truncated    bool      `json:"truncated"`
}

Result is a scan's outcome. Truncated means a cap stopped the walk early.

func ScanDir

func ScanDir(ctx context.Context, root string, lim Limits) (Result, error)

ScanDir walks root (never following symlinks, so it cannot leave root) and reports suspected secrets. A cap stops the walk and sets Truncated. An error means the scan could not run to a trustworthy end.

func ScanEnv

func ScanEnv(env map[string]string, lim Limits) Result

ScanEnv checks plain (non-secret) environment values for credentials. Secret-backed and referenced values are the caller's to leave out.

type Rule

type Rule struct {
	ID    string
	Desc  string
	Re    *regexp.Regexp
	Group int
}

Rule is one token format. Group is the capture group holding the secret (0 is the whole match).

type Run

type Run struct {
	AttemptID    string    `json:"deployment_id"`
	App          string    `json:"app"`
	Policy       Policy    `json:"policy"`
	Action       string    `json:"action"`
	Reason       string    `json:"reason,omitempty"`
	Status       string    `json:"status"`
	Error        string    `json:"error,omitempty"`
	FilesScanned int       `json:"files_scanned"`
	Truncated    bool      `json:"truncated"`
	Findings     []Finding `json:"findings"`
	ScannedAt    time.Time `json:"scanned_at"`
}

Run is the persisted outcome of one deploy's scan.

type SQLStore

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

SQLStore implements Store over the control plane database.

func NewSQLStore

func NewSQLStore(db *store.DB) *SQLStore

NewSQLStore wraps db.

func (*SQLStore) AddAllow

func (s *SQLStore) AddAllow(ctx context.Context, e AllowEntry) error

AddAllow inserts an entry, keeping the first one's actor and reason on a repeat.

func (*SQLStore) DeleteApp

func (s *SQLStore) DeleteApp(ctx context.Context, app string) error

DeleteApp removes everything stored for app.

func (*SQLStore) GetPolicy

func (s *SQLStore) GetPolicy(ctx context.Context, app string) (Policy, error)

GetPolicy returns the stored policy, or warn when none is set.

func (*SQLStore) GetRun

func (s *SQLStore) GetRun(ctx context.Context, attemptID string) (Run, error)

GetRun returns the run of a deploy attempt.

func (*SQLStore) ListAllow

func (s *SQLStore) ListAllow(ctx context.Context, app string) ([]AllowEntry, error)

ListAllow returns the app's allow-list, oldest first.

func (*SQLStore) RemoveAllow

func (s *SQLStore) RemoveAllow(ctx context.Context, app, fingerprint string) (bool, error)

RemoveAllow deletes an entry and reports whether it existed.

func (*SQLStore) SaveRun

func (s *SQLStore) SaveRun(ctx context.Context, r Run) error

SaveRun upserts a run record.

func (*SQLStore) SetPolicy

func (s *SQLStore) SetPolicy(ctx context.Context, app string, p Policy) error

SetPolicy upserts the policy.

type Service

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

Service scans deploy inputs and applies each app's policy.

func New

func New(st Store, lim Limits, log *slog.Logger, opts ...Option) *Service

New builds a Service.

func (*Service) Allow

func (s *Service) Allow(ctx context.Context, app, fingerprint, reason, actor string) (AllowEntry, error)

Allow adds a fingerprint to the allow-list; who and why are required.

func (*Service) AllowList

func (s *Service) AllowList(ctx context.Context, app string) ([]AllowEntry, error)

AllowList returns the app's allow-list.

func (*Service) BeforeDeploy

func (s *Service) BeforeDeploy(ctx context.Context, in Input) error

BeforeDeploy scans in and applies the app's policy. Only a block returns an error. It runs on the deploy job goroutine, bounded by the scan timeout.

func (*Service) DeleteApp

func (s *Service) DeleteApp(ctx context.Context, app string) error

DeleteApp removes an app's policy, allow-list and runs.

func (*Service) Limits

func (s *Service) Limits() Limits

Limits returns the effective caps.

func (*Service) Policy

func (s *Service) Policy(ctx context.Context, app string) (Policy, error)

Policy returns the app's policy, defaulting to warn.

func (*Service) Run

func (s *Service) Run(ctx context.Context, attemptID string) (Run, error)

Run returns the stored scan of a deploy attempt.

func (*Service) ScanEnvNow

func (s *Service) ScanEnvNow(ctx context.Context, app string, env map[string]string) (Result, error)

ScanEnvNow scans an env set without persisting, with the allow-list applied.

func (*Service) SetPolicy

func (s *Service) SetPolicy(ctx context.Context, app, policy string) (Policy, error)

SetPolicy stores the app's policy.

func (*Service) Unallow

func (s *Service) Unallow(ctx context.Context, app, fingerprint string) (bool, error)

Unallow removes an allow-list entry and reports whether it existed.

type Store

type Store interface {
	GetPolicy(ctx context.Context, app string) (Policy, error)
	SetPolicy(ctx context.Context, app string, p Policy) error
	ListAllow(ctx context.Context, app string) ([]AllowEntry, error)
	AddAllow(ctx context.Context, e AllowEntry) error
	RemoveAllow(ctx context.Context, app, fingerprint string) (bool, error)
	SaveRun(ctx context.Context, r Run) error
	GetRun(ctx context.Context, attemptID string) (Run, error)
	DeleteApp(ctx context.Context, app string) error
}

Store persists policy, allow-list entries and run records; *SQLStore satisfies it.

Jump to

Keyboard shortcuts

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