workflow

package
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Jun 5, 2025 License: AGPL-3.0 Imports: 14 Imported by: 0

Documentation

Overview

Package workflow provides tools for analyzing and securing GitHub Actions workflows.

The package handles: - Parsing workflow YAML files while preserving line numbers and structure - Detecting unpinned action references (tags/branches instead of SHAs) - Resolving action versions to pinned SHA hashes - Modifying workflow files with pinned SHAs while preserving formatting

Key Components:

  • Processor: Scans workflows to find unpinned actions
  • Fixer: Modifies workflows to replace versions with SHAs
  • Parser: Reads and interprets workflow YAML structure
  • WorkflowFile: Structured representation of workflow contents

Usage Example:

processor := workflow.NewProcessor()
parser := workflow.NewParser()
results, _ := processor.Process(".github/workflows", parser)

client := actionlookup.NewClient()
fixer := workflow.NewFixer(client)
_ = fixer.ApplyFixes(context.Background(), results)

The package uses functional options for configuration and supports custom: - Output writers (for results/status messages) - HTTP clients (via actionlookup) - Parsing strategies

Dependencies:

  • gopkg.in/yaml.v3 for YAML parsing with line number tracking
  • actionlookup package for SHA resolution

Index

Constants

This section is empty.

Variables

View Source
var ErrWorkflowDirNotFound = NewProcessingError(ErrorTypeWorkflowDirNotFound, "workflow directory not found", nil)

ErrWorkflowDirNotFound is a sentinel error for when the workflow directory is not found

Functions

func IsNetworkError

func IsNetworkError(err error) bool

IsNetworkError returns true if the error is related to network operations

func IsPinnedWithSHA

func IsPinnedWithSHA(action string) bool

IsPinnedWithSHA checks if a GitHub Action reference is properly pinned with a full 40-character SHA hash. Returns true if the action reference matches the format 'owner/repo@sha123...' with a valid SHA, false for any other format including tags, branches, or partial SHAs.

func IsRateLimitError

func IsRateLimitError(err error) bool

IsRateLimitError returns true if the error is related to API rate limiting

func IsRetryable

func IsRetryable(err error) bool

IsRetryable returns true if the error is potentially recoverable with a retry

func IsTestEnvironment

func IsTestEnvironment() bool

IsTestEnvironment returns true if the code is running in a test environment

func RetryOperation

func RetryOperation(operation func() error, config RetryConfig) error

RetryOperation retries the given operation according to the retry configuration

func RetryOperationWithResult

func RetryOperationWithResult[T any](operation func() (T, error), config RetryConfig) (T, error)

RetryOperationWithResult retries the given operation that returns a result and error

Types

type Action

type Action struct {
	Ref         string
	SHA         string
	Type        string
	Content     string
	Owner       string
	Repo        string
	Tag         string
	IsRemote    bool
	IsComposite bool
}

Action represents a GitHub Action with its metadata and content.

type ActionLookup

type ActionLookup interface {
	// Lookup resolves an action reference (owner/repo) and version to a full SHA hash.
	// version can be a tag, branch, or partial SHA. Returns the full 40-character SHA
	// or an error if resolution fails.
	Lookup(ctx context.Context, action, version string) (string, error)
}

ActionLookup defines the interface for resolving GitHub Actions versions to SHAs. Implementations should handle caching, API communication, and version validation.

type ActionLookupClientInterface

type ActionLookupClientInterface interface {
	Lookup(ctx context.Context, actionPath, ref string) (string, error)
	GetFileContent(ctx context.Context, owner, repo, path, ref string) (string, error)
}

ActionLookupClientInterface defines the interface for action lookup clients

type Cache

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

Cache is a simple in-memory cache for workflow files and action references.

func NewCache

func NewCache(ttl time.Duration) *Cache

NewCache creates a new cache with the specified TTL (time to live).

func (*Cache) Cleanup

func (c *Cache) Cleanup()

Cleanup removes expired entries from the cache.

func (*Cache) Clear

func (c *Cache) Clear()

Clear empties the cache.

func (*Cache) Close

func (c *Cache) Close() error

Close implements the cache.Cache interface.

func (*Cache) GetActionResults

func (c *Cache) GetActionResults(actionRef string) ([]types.DeepResult, bool)

GetActionResults retrieves action results from the cache. Returns the results and a boolean indicating if they were found.

func (*Cache) GetWorkflow

func (c *Cache) GetWorkflow(path string) (*types.WorkflowFile, bool)

GetWorkflow retrieves a workflow file from the cache. Returns the workflow file and a boolean indicating if it was found.

func (*Cache) SetActionResults

func (c *Cache) SetActionResults(actionRef string, results []types.DeepResult)

SetActionResults stores action results in the cache.

func (*Cache) SetWorkflow

func (c *Cache) SetWorkflow(path string, workflow *types.WorkflowFile)

SetWorkflow stores a workflow file in the cache.

func (*Cache) Size

func (c *Cache) Size() int

Size returns the number of entries in the cache.

type CompositeAction

type CompositeAction struct {
	Name        string `yaml:"name"`
	Description string `yaml:"description"`
	Runs        Runs   `yaml:"runs"`
}

CompositeAction represents a GitHub Action with composite runs. Contains the action name, description, and steps to execute.

type CompositeActionParser

type CompositeActionParser struct {
}

CompositeActionParser handles parsing of GitHub Actions composite action YAML files. Implements WorkflowParserInterface to allow use with the existing processor.

func NewCompositeActionParser

func NewCompositeActionParser() *CompositeActionParser

NewCompositeActionParser creates a new CompositeActionParser instance.

func (*CompositeActionParser) Parse

func (p *CompositeActionParser) Parse(content []byte) (*WorkflowFile, error)

Parse reads and parses a GitHub Actions composite action file from disk. content: Byte array containing the action file content (.yml or .yaml) Returns: - *WorkflowFile: Structured representation of the action as a workflow - error: Parsing errors, file IO errors, invalid YAML structure, or non-composite action

type DeepProcessor

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

DeepProcessor extends the base Processor with recursive scanning capabilities.

func NewDeepProcessor

func NewDeepProcessor(opts ...DeepProcessorOption) *DeepProcessor

NewDeepProcessor creates a new DeepProcessor instance with optional configuration.

func (*DeepProcessor) AddError

func (p *DeepProcessor) AddError(err error)

AddError adds an error to the collection

func (*DeepProcessor) ClearErrors

func (p *DeepProcessor) ClearErrors()

ClearErrors clears all collected errors

func (*DeepProcessor) GetAction

func (p *DeepProcessor) GetAction(ctx context.Context, actionRef string) (Action, error)

GetAction fetches a GitHub Action's content and metadata.

func (*DeepProcessor) GetErrors

func (p *DeepProcessor) GetErrors() []error

GetErrors returns all errors collected during processing

func (*DeepProcessor) HasErrors

func (p *DeepProcessor) HasErrors() bool

HasErrors returns true if any errors were collected during processing

func (*DeepProcessor) Process

func (p *DeepProcessor) Process(path string, parser WorkflowParserInterface) ([]DeepResult, error)

Process performs deep scanning of workflow files to find unpinned actions. baseDir: Path to either a directory containing .github/workflows or a specific workflow file parser: Implementation of WorkflowParserInterface to parse YAML files Returns a slice of DeepResult findings and any error encountered during processing

type DeepProcessorOption

type DeepProcessorOption func(*DeepProcessor)

DeepProcessorOption defines a functional option type for configuring a DeepProcessor.

func WithActionLookupClient

func WithActionLookupClient(client ActionLookupClientInterface) DeepProcessorOption

WithActionLookupClient sets a custom action lookup client for the deep processor. This is primarily used for testing.

func WithBaseDir

func WithBaseDir(baseDir string) DeepProcessorOption

WithBaseDir sets the base directory for the DeepProcessor.

func WithCache

func WithCache(cache cache.Cache) DeepProcessorOption

WithCache configures the cache to use for the DeepProcessor.

func WithCompositeParser

func WithCompositeParser(parser WorkflowParserInterface) DeepProcessorOption

WithCompositeParser configures the parser to use for composite actions.

func WithDeepProcessorWriter

func WithDeepProcessorWriter(w io.Writer) DeepProcessorOption

WithDeepProcessorWriter configures the DeepProcessor to write output to the specified Writer.

func WithDeepScan

func WithDeepScan(deepScan bool) DeepProcessorOption

WithDeepScan configures whether to perform deep scanning of remote actions.

func WithGitHubToken

func WithGitHubToken(token string) DeepProcessorOption

WithGitHubToken configures the GitHub token to use for remote action processing.

func WithIgnoreErrors

func WithIgnoreErrors(ignoreErrors bool) DeepProcessorOption

WithIgnoreErrors configures whether to ignore errors during processing.

func WithMaxDepth

func WithMaxDepth(depth int) DeepProcessorOption

WithMaxDepth configures the maximum recursion depth for dependency scanning.

func WithRemoteWorkflowFetcher

func WithRemoteWorkflowFetcher(fetcher RemoteWorkflowFetcherInterface) DeepProcessorOption

WithRemoteWorkflowFetcher configures the remote workflow fetcher to use for the DeepProcessor.

func WithRetryConfig

func WithRetryConfig(config RetryConfig) DeepProcessorOption

WithRetryConfig configures the retry settings for the DeepProcessor.

func WithReusableWorkflowParser

func WithReusableWorkflowParser(parser WorkflowParserInterface) DeepProcessorOption

WithReusableWorkflowParser configures the parser to use for reusable workflow files.

func WithVerbose

func WithVerbose(verbose bool) DeepProcessorOption

WithVerbose configures whether to show verbose output.

func WithWorkflowParser

func WithWorkflowParser(parser WorkflowParserInterface) DeepProcessorOption

WithWorkflowParser configures the parser to use for workflow files.

type DeepResult

type DeepResult struct {
	FilePath       string   // Path to the file containing the action reference
	LineNumber     int      // Line number where the action reference appears
	Action         string   // Action name (e.g., "actions/checkout")
	JobName        string   // Name of the job containing the action
	StepName       string   // Name of the step using the action
	ActionRef      string   // Full action reference (e.g., "actions/checkout@v2")
	SourceType     string   // Type of source (workflow, composite action, reusable workflow)
	DependencyPath []string // Chain of dependencies leading to this action
	Fixable        bool     // Whether this action can be fixed automatically
}

DeepResult extends Result with additional information about the dependency chain.

type ErrorType

type ErrorType string

ErrorType represents the category of error that occurred

const (
	// ErrorTypeIO represents errors related to file I/O operations
	ErrorTypeIO ErrorType = "IO"

	// ErrorTypeParser represents errors related to parsing workflow files
	ErrorTypeParser ErrorType = "Parser"

	// ErrorTypeNetwork represents errors related to network operations
	ErrorTypeNetwork ErrorType = "Network"

	// ErrorTypeRateLimit represents errors related to API rate limiting
	ErrorTypeRateLimit ErrorType = "RateLimit"

	// ErrorTypeValidation represents errors related to validation
	ErrorTypeValidation ErrorType = "Validation"

	// ErrorTypeInternal represents internal errors
	ErrorTypeInternal ErrorType = "Internal"

	// ErrorTypeLookup represents errors related to action lookups
	ErrorTypeLookup ErrorType = "Lookup"

	// ErrorTypeActionFetch represents errors related to fetching action content
	ErrorTypeActionFetch ErrorType = "ActionFetch"

	// ErrorTypeWorkflowDirNotFound represents errors when workflow directory is not found
	ErrorTypeWorkflowDirNotFound ErrorType = "WorkflowDirNotFound"
)

type Fixer

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

Fixer handles secure pinning of GitHub Actions in workflow files by replacing version tags with full SHA hashes. Uses an ActionLookup client to resolve action versions to their corresponding Git SHAs.

func NewFixer

func NewFixer(client ActionLookup, opts ...FixerOption) *Fixer

NewFixer creates a new Fixer instance with required dependencies and options. client: ActionLookup implementation for SHA resolution opts: Optional configuration settings for output and behavior

func (*Fixer) ApplyFixes

func (f *Fixer) ApplyFixes(ctx context.Context, results []Result) error

ApplyFixes processes analysis results to modify workflow files, replacing action versions with pinned SHAs. Handles multiple fixes per file and preserves formatting. ctx: Context for cancellation and timeouts results: List of unpinned action findings from processor Returns error if any file operation or SHA lookup fails

For each result: 1. Locates the target line in the workflow file 2. Preserves original indentation and formatting 3. Replaces version with SHA while adding original version as a comment 4. Writes modified content back to disk

type FixerOption

type FixerOption func(*Fixer)

FixerOption defines a functional option type for configuring a Fixer. Used to provide optional dependencies and settings to the Fixer during creation.

func WithFixerWriter

func WithFixerWriter(w io.Writer) FixerOption

WithFixerWriter configures an output destination for fixer status messages. Defaults to os.Stdout if not specified. Use this option to redirect output to other writers like buffers or files.

type Job

type Job struct {
	Name           string `yaml:"name"`
	Steps          []Step `yaml:"steps"`
	Uses           string `yaml:"uses"` // For reusable workflows
	UsesLineNumber int    `yaml:"-"`    // Line number for reusable workflow reference
}

Job represents a GitHub Actions job within a workflow. Contains the job name and sequence of steps to execute.

type ParserOption

type ParserOption func(*WorkflowParser)

ParserOption defines a functional option type for configuring a WorkflowParser. Used to provide optional parsing behaviors or settings during parser creation.

type ProcessingError

type ProcessingError struct {
	// Type categorizes the error
	Type ErrorType

	// Message is a human-readable description of the error
	Message string

	// FilePath is the path to the file being processed when the error occurred
	FilePath string

	// ActionRef is the action reference being processed when the error occurred
	ActionRef string

	// DependencyPath is the chain of dependencies leading to the error
	DependencyPath []string

	// Depth is the recursion depth at which the error occurred
	Depth int

	// Retryable indicates whether the error is potentially recoverable with a retry
	Retryable bool

	// Wrapped is the underlying error
	Wrapped error
}

ProcessingError represents an error that occurred during workflow processing with additional context about where and why the error occurred.

func NewProcessingError

func NewProcessingError(errType ErrorType, message string, wrapped error) *ProcessingError

NewProcessingError creates a new ProcessingError with the given type and message

func (*ProcessingError) Error

func (e *ProcessingError) Error() string

Error implements the error interface

func (*ProcessingError) Unwrap

func (e *ProcessingError) Unwrap() error

Unwrap returns the wrapped error

func (*ProcessingError) WithActionRef

func (e *ProcessingError) WithActionRef(actionRef string) *ProcessingError

WithActionRef adds action reference context to the error

func (*ProcessingError) WithDependencyPath

func (e *ProcessingError) WithDependencyPath(dependencyPath []string) *ProcessingError

WithDependencyPath adds dependency path context to the error

func (*ProcessingError) WithDepth

func (e *ProcessingError) WithDepth(depth int) *ProcessingError

WithDepth adds recursion depth context to the error

func (*ProcessingError) WithFilePath

func (e *ProcessingError) WithFilePath(filePath string) *ProcessingError

WithFilePath adds file path context to the error

func (*ProcessingError) WithParentAction

func (e *ProcessingError) WithParentAction(parentAction string) *ProcessingError

WithParentAction adds parent action reference context to the error

func (*ProcessingError) WithRetryable

func (e *ProcessingError) WithRetryable(retryable bool) *ProcessingError

WithRetryable marks the error as retryable or not

type Processor

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

Processor handles the analysis and processing of GitHub Actions workflow files. Configurable through functional options to control output destination and behavior.

func NewProcessor

func NewProcessor(opts ...ProcessorOption) *Processor

NewProcessor creates a new Processor instance with optional configuration. Accepts ProcessorOption arguments to customize output destination and other settings.

func (*Processor) Process

func (p *Processor) Process(baseDir string, parser WorkflowParserInterface) ([]Result, error)

Process analyzes workflow files to find unpinned actions, either in a directory or single file. baseDir: Path to either a directory containing .github/workflows or a specific workflow file parser: Implementation of WorkflowParserInterface to parse YAML files Returns a slice of Result findings and any error encountered during processing

type ProcessorOption

type ProcessorOption func(*Processor)

ProcessorOption defines a functional option type for configuring a Processor.

func WithProcessorWriter

func WithProcessorWriter(w io.Writer) ProcessorOption

WithProcessorWriter configures the Processor to write output to the specified Writer. This option allows redirecting output from the default os.Stdout.

type RemoteWorkflowFetcher

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

RemoteWorkflowFetcher fetches remote workflow files.

func NewRemoteWorkflowFetcher

func NewRemoteWorkflowFetcher(client *actionlookup.Client, outWriter io.Writer) *RemoteWorkflowFetcher

NewRemoteWorkflowFetcher creates a new RemoteWorkflowFetcher.

func (*RemoteWorkflowFetcher) Cleanup

func (f *RemoteWorkflowFetcher) Cleanup(ctx context.Context) error

Cleanup removes the temporary directory.

func (*RemoteWorkflowFetcher) FetchWorkflow

func (f *RemoteWorkflowFetcher) FetchWorkflow(ctx context.Context, workflowRef string) (string, error)

FetchWorkflow fetches a remote workflow file and returns the path to the local copy.

func (*RemoteWorkflowFetcher) WithBaseURL

func (f *RemoteWorkflowFetcher) WithBaseURL(baseURL string) *RemoteWorkflowFetcher

WithBaseURL sets the base URL for the GitHub API.

func (*RemoteWorkflowFetcher) WithCacheDir

func (f *RemoteWorkflowFetcher) WithCacheDir(cacheDir string) *RemoteWorkflowFetcher

WithCacheDir sets the cache directory for downloaded workflows.

func (*RemoteWorkflowFetcher) WithCacheTTL

WithCacheTTL sets the cache TTL for downloaded workflows.

type RemoteWorkflowFetcherInterface

type RemoteWorkflowFetcherInterface interface {
	FetchWorkflow(ctx context.Context, workflowRef string) (string, error)
}

RemoteWorkflowFetcherInterface defines the interface for remote workflow fetchers

type RemoteWorkflowOption

type RemoteWorkflowOption func(*RemoteWorkflowFetcher)

RemoteWorkflowOption defines a functional option type for configuring a RemoteWorkflowFetcher.

type Result

type Result struct {
	FilePath   string
	JobName    string
	LineNumber int
	Action     string
	StepName   string // Name of the step using the action
}

Result represents a finding of an unpinned GitHub Action in a workflow file. Contains file path, job name, line number, and action reference details.

type RetryConfig

type RetryConfig struct {
	// MaxRetries is the maximum number of retry attempts
	MaxRetries int

	// InitialDelay is the initial delay between retries
	InitialDelay time.Duration

	// MaxDelay is the maximum delay between retries
	MaxDelay time.Duration

	// BackoffFactor is the multiplier for the delay after each retry
	BackoffFactor float64

	// RetryableCheck is a function that determines if an error is retryable
	RetryableCheck func(error) bool

	// OutWriter is where retry status messages are written
	OutWriter io.Writer
}

RetryConfig holds configuration for retry operations

func DefaultRetryConfig

func DefaultRetryConfig() RetryConfig

DefaultRetryConfig returns a default retry configuration

func TestRetryConfig

func TestRetryConfig() RetryConfig

TestRetryConfig returns a retry configuration optimized for tests with minimal delays to speed up test execution

func (RetryConfig) WithBackoffFactor

func (c RetryConfig) WithBackoffFactor(backoffFactor float64) RetryConfig

WithBackoffFactor sets the multiplier for the delay after each retry

func (RetryConfig) WithInitialDelay

func (c RetryConfig) WithInitialDelay(initialDelay time.Duration) RetryConfig

WithInitialDelay sets the initial delay between retries

func (RetryConfig) WithMaxDelay

func (c RetryConfig) WithMaxDelay(maxDelay time.Duration) RetryConfig

WithMaxDelay sets the maximum delay between retries

func (RetryConfig) WithMaxRetries

func (c RetryConfig) WithMaxRetries(maxRetries int) RetryConfig

WithMaxRetries sets the maximum number of retry attempts

func (RetryConfig) WithOutWriter

func (c RetryConfig) WithOutWriter(outWriter io.Writer) RetryConfig

WithOutWriter sets where retry status messages are written

func (RetryConfig) WithRetryableCheck

func (c RetryConfig) WithRetryableCheck(retryableCheck func(error) bool) RetryConfig

WithRetryableCheck sets the function that determines if an error is retryable

type ReusableWorkflowParser

type ReusableWorkflowParser struct {
}

ReusableWorkflowParser handles parsing of GitHub Actions reusable workflow YAML files. It implements the WorkflowParserInterface for consistent integration with the processor.

func NewReusableWorkflowParser

func NewReusableWorkflowParser() *ReusableWorkflowParser

NewReusableWorkflowParser creates a new ReusableWorkflowParser instance.

func (*ReusableWorkflowParser) Parse

func (p *ReusableWorkflowParser) Parse(content []byte) (*WorkflowFile, error)

Parse reads and parses a GitHub Actions reusable workflow file from disk. content: Byte array containing the workflow file content (.yml or .yaml) Returns: - *WorkflowFile: Structured representation of the workflow - error: Parsing errors, file IO errors, or invalid YAML structure

type Runs

type Runs struct {
	Using string `yaml:"using"`
	Steps []Step `yaml:"steps"`
}

Runs represents the execution configuration of a composite action.

type Step

type Step struct {
	Name       string `yaml:"name"`
	Uses       string `yaml:"uses"`
	LineNumber int    `yaml:"-"` // Add line number tracking
}

Step represents an individual step within a GitHub Actions job. Tracks the step name, action reference (uses), and original line number.

type WorkflowFile

type WorkflowFile struct {
	Name string         `yaml:"name"`
	On   any            `yaml:"on"`
	Jobs map[string]Job `yaml:"jobs"`
}

WorkflowFile represents a GitHub Actions workflow file structure parsed from YAML. Contains the workflow name, trigger events ('on'), and map of jobs.

type WorkflowParser

type WorkflowParser struct {
}

WorkflowParser handles parsing of GitHub Actions workflow YAML files. Preserves line number information and maintains original structure while extracting key workflow elements like jobs and steps.

func NewParser

func NewParser(opts ...ParserOption) *WorkflowParser

NewParser creates a new WorkflowParser instance with optional configuration. opts: Variadic list of ParserOption functions to customize parsing behavior Returns a configured WorkflowParser ready to parse workflow files

func (*WorkflowParser) Parse

func (p *WorkflowParser) Parse(content []byte) (*WorkflowFile, error)

Parse reads and parses a GitHub Actions workflow file from disk. content: Byte array containing the workflow file content (.yml or .yaml) Returns: - *WorkflowFile: Structured representation of the workflow - error: Parsing errors, file IO errors, or invalid YAML structure

The parser: - Maintains original line numbers for precise error reporting - Handles both simple and complex trigger (on) definitions - Ignores unsupported fields while preserving valid structure - Validates basic YAML structure but not workflow semantics

type WorkflowParserInterface

type WorkflowParserInterface interface {
	Parse(content []byte) (*WorkflowFile, error)
}

WorkflowParserInterface defines the interface for parsing GitHub Actions workflow files. Implementations must provide a Parse method to convert YAML files to WorkflowFile structures.

Jump to

Keyboard shortcuts

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