poutine

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Apr 17, 2026 License: AGPL-3.0 Imports: 18 Imported by: 0

Documentation

Overview

Package poutine provides shared poutine CI/CD security analysis functionality. This package is used by both Kitchen (remote analysis) and Brisket (local analysis).

Index

Constants

View Source
const (
	CloudProviderAWS   = "aws"
	CloudProviderGCP   = "gcp"
	CloudProviderAzure = "azure"
)

CloudProvider constants

Variables

View Source
var ExtendedRules = []string{

	"debug_enabled",

	"excessive_permissions",
}

ExtendedRules adds post-exploitation relevant rules for comprehensive analysis. These are useful for understanding the full attack surface but are not strictly "initial access" vectors.

View Source
var OffensiveRules = []string{

	"injection",

	"pr_runs_on_self_hosted",

	"untrusted_checkout_exec",
}

OffensiveRules are poutine rules focused on initial access attack vectors. These rules identify vulnerabilities that can be exploited to gain code execution in CI/CD pipelines from an unauthenticated or low-privileged position.

This is the single source of truth for offensive rules across the codebase.

Functions

This section is empty.

Types

type AnalysisObserver

type AnalysisObserver interface {
	OnAnalysisStarted(description string)
	OnDiscoveryCompleted(org string, totalCount int)
	OnRepoStarted(repo string)
	OnRepoCompleted(repo string)
	OnRepoError(repo string, err error)
	OnRepoSkipped(repo string, reason string)
	OnStepCompleted(description string)
	OnFinalizeStarted(totalPackages int)
	OnFinalizeCompleted()
}

type AnalysisResult

type AnalysisResult struct {
	Success  bool          `json:"success"`
	Duration time.Duration `json:"duration"`

	// Target metadata
	Target     string `json:"target"`      // org or org/repo (remote) or path (local)
	TargetType string `json:"target_type"` // "org", "repo", or "local"
	Repository string `json:"repository,omitempty"`

	// Findings summary
	ReposAnalyzed    int `json:"repos_analyzed"`
	TotalFindings    int `json:"total_findings"`
	CriticalFindings int `json:"critical_findings"`
	HighFindings     int `json:"high_findings"`
	MediumFindings   int `json:"medium_findings"`
	LowFindings      int `json:"low_findings"`

	// Detailed findings
	Findings []Finding `json:"findings"`

	// Workflow metadata (for loot scoring)
	Workflows []WorkflowMeta `json:"workflows,omitempty"`

	// All repos that were analyzed (for tracking private repos with no vulns)
	AnalyzedRepos []string `json:"analyzed_repos,omitempty"`

	// Secret findings from gitleaks scanning
	SecretFindings []SecretFinding `json:"secret_findings,omitempty"`

	// Errors (non-fatal issues during analysis)
	Errors []string `json:"errors,omitempty"`
}

AnalysisResult represents the result of a poutine CI/CD security analysis. This is the unified result type used by both remote (Kitchen) and local (Brisket) analysis.

func AnalyzeLocal

func AnalyzeLocal(ctx context.Context, path string) (*AnalysisResult, error)

AnalyzeLocal performs analysis on a local filesystem path. This is used by Brisket for on-target scanning.

func AnalyzeRemote

func AnalyzeRemote(ctx context.Context, token, target, targetType string) (*AnalysisResult, error)

AnalyzeRemote performs analysis on a remote GitHub target using the GitHub API. This is used by Kitchen for pre-agent reconnaissance. The token is used ephemerally and NOT persisted.

func AnalyzeRemoteWithObserver

func AnalyzeRemoteWithObserver(ctx context.Context, token, target, targetType string, observer AnalysisObserver) (*AnalysisResult, error)

type AppAction

type AppAction struct {
	Action         string `json:"action"`
	Version        string `json:"version"`
	PrivateKey     string `json:"private_key"`
	AppID          string `json:"app_id"`
	HardcodedAppID string `json:"hardcoded_app_id,omitempty"`
}

AppAction represents a detected GitHub App token action in a workflow.

type CloudAction

type CloudAction struct {
	Provider string            `json:"provider"` // aws, gcp, azure
	Action   string            `json:"action"`   // Full action name (owner/repo)
	Version  string            `json:"version"`  // Action version (@v1, @main, etc.)
	Inputs   map[string]string `json:"inputs"`   // Relevant inputs for pivot
}

CloudAction represents a detected cloud provider action in a workflow.

type Finding

type Finding struct {
	ID          string `json:"id"`                    // Generated ID (V001, V002, etc.)
	Repository  string `json:"repository"`            // org/repo
	Workflow    string `json:"workflow"`              // .github/workflows/foo.yml
	Line        int    `json:"line,omitempty"`        // Line number in workflow
	Job         string `json:"job,omitempty"`         // Job name
	Step        string `json:"step,omitempty"`        // Step name
	RuleID      string `json:"rule_id"`               // poutine rule ID
	Title       string `json:"title"`                 // Finding title
	Description string `json:"description,omitempty"` // Rule description
	Severity    string `json:"severity"`              // "critical", "high", "medium", "low"
	Details     string `json:"details,omitempty"`     // Additional details

	// Injection-specific metadata (for initial access exploitation)
	Context     string `json:"context,omitempty"`      // Injection context: "bash_run", "github_script", etc.
	BashContext string `json:"bash_context,omitempty"` // Bash wrapper context when applicable
	Trigger     string `json:"trigger,omitempty"`      // Workflow trigger: "pull_request", "push", etc.
	Expression  string `json:"expression,omitempty"`   // The vulnerable expression (${{ ... }})

	InjectionSources   []string                      `json:"injection_sources,omitempty"`
	ReferencedSecrets  []string                      `json:"referenced_secrets,omitempty"`
	LOTPTool           string                        `json:"lotp_tool,omitempty"`
	LOTPAction         string                        `json:"lotp_action,omitempty"`
	LOTPTargets        []string                      `json:"lotp_targets,omitempty"`
	CachePoisonWriter  bool                          `json:"cache_poison_writer,omitempty"`
	CachePoisonReason  string                        `json:"cache_poison_reason,omitempty"`
	CachePoisonVictims []cachepoison.VictimCandidate `json:"cache_poison_victims,omitempty"`

	// Gate constraints (if: conditions on enclosing job/step)
	GateTriggers   []string `json:"gate_triggers,omitempty"`
	GateRaw        string   `json:"gate_raw,omitempty"`
	GateUnsolvable string   `json:"gate_unsolvable,omitempty"`

	// Fingerprint for deduplication
	Fingerprint string `json:"fingerprint,omitempty"`
}

Finding represents a single vulnerability found during analysis.

type GateConstraint

type GateConstraint struct {
	Expression string
	Solvable   bool
	Triggers   []string
	Unsolvable string
}

func ParseGateExpression

func ParseGateExpression(ifExpr, injectionSource string) GateConstraint

type JobMeta

type JobMeta struct {
	ID            string            `json:"id"`                     // Mandatory job key from YAML
	DisplayName   string            `json:"display_name,omitempty"` // Optional name attribute
	Secrets       []string          `json:"secrets,omitempty"`
	CloudActions  []CloudAction     `json:"cloud_actions,omitempty"`
	AppActions    []AppAction       `json:"app_actions,omitempty"`
	SecretTypes   map[string]string `json:"secret_types,omitempty"`
	HasOIDC       bool              `json:"has_oidc,omitempty"`
	HasWrite      bool              `json:"has_write,omitempty"`
	SelfHosted    bool              `json:"self_hosted,omitempty"`
	GitHubTokenRW bool              `json:"github_token_rw,omitempty"`
}

JobMeta contains metadata about a specific job.

type NoopFormatter

type NoopFormatter struct{}

NoopFormatter is a no-op formatter that satisfies the analyze.Formatter interface. We process results directly rather than using poutine's built-in formatters.

func (*NoopFormatter) Format

Format does nothing - we handle results ourselves.

func (*NoopFormatter) FormatWithPath

func (f *NoopFormatter) FormatWithPath(_ context.Context, _ []*models.PackageInsights, _ map[string][]*models.RepoInfo) error

FormatWithPath does nothing - we handle results ourselves.

type SecretFinding

type SecretFinding struct {
	RuleID      string  `json:"rule_id"`
	Description string  `json:"description"`
	Repository  string  `json:"repository"`
	File        string  `json:"file"`
	StartLine   int     `json:"start_line"`
	Secret      string  `json:"secret"`
	Fingerprint string  `json:"fingerprint"`
	Entropy     float32 `json:"entropy"`
}

SecretFinding represents a secret found by gitleaks during analysis.

type WorkflowMeta

type WorkflowMeta struct {
	Repository         string                        `json:"repository"`
	Path               string                        `json:"path"`
	Secrets            []string                      `json:"secrets,omitempty"`
	JobSecrets         map[string][]string           `json:"job_secrets,omitempty"`
	Jobs               []JobMeta                     `json:"jobs,omitempty"`
	SecretTypes        map[string]string             `json:"secret_types,omitempty"`
	HardcodedAppIDs    []string                      `json:"hardcoded_app_ids,omitempty"`
	HasOIDC            bool                          `json:"has_oidc,omitempty"`
	HasWrite           bool                          `json:"has_write,omitempty"`
	SelfHosted         bool                          `json:"self_hosted,omitempty"`
	CachePoisonVictims []cachepoison.VictimCandidate `json:"cache_poison_victims,omitempty"`
}

WorkflowMeta contains metadata about a workflow for loot potential scoring.

Jump to

Keyboard shortcuts

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