parse

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package parse turns GitHub Actions workflow files into an internal model that keeps the line and column of everything it reports. A finding that cannot point at file:line is not actionable, so positions are not optional here.

Parsing needs no token: workflow files are on disk.

Index

Constants

View Source
const WorkflowsDir = ".github/workflows"

WorkflowsDir is where GitHub looks for workflow files.

Variables

This section is empty.

Functions

func LoadDir

func LoadDir(root string) ([]*Workflow, []LoadError, error)

LoadDir parses every workflow file in <root>/.github/workflows.

It returns the workflows it could parse and, separately, the files it could not. A missing workflows directory is not an error: it yields no workflows.

Types

type Access added in v0.2.0

type Access string

Access is the level granted to one permission scope.

const (
	AccessNone  Access = "none"
	AccessRead  Access = "read"
	AccessWrite Access = "write"
)

type Environment

type Environment struct {
	Name string
	Loc  Location
}

Environment is a deployment environment referenced by a job.

type Job

type Job struct {
	ID   string
	Name string
	Loc  Location

	// Steps are the job's steps, in order.
	Steps []Step

	// Environments lists the deployment environments whose secrets this job
	// can read. GitHub allows one environment per job; this stays a slice so
	// that a matrix over environments can be modelled later.
	Environments []Environment

	// EnvironmentDynamic is set when the environment name is an expression we
	// cannot resolve, for example environment: ${{ inputs.target }}. Secrets
	// for such a job cannot be verified.
	EnvironmentDynamic bool

	// Uses is set when the job calls a reusable workflow.
	Uses string

	// Permissions is the job's own permissions block, if it declares one.
	Permissions Permissions
}

Job is one entry of the workflow's jobs mapping.

type LoadError

type LoadError struct {
	Path string
	Err  error
}

LoadError records a workflow file that could not be read or parsed. A broken file is reported to the user, never silently dropped.

func (LoadError) Error

func (e LoadError) Error() string

func (LoadError) Unwrap

func (e LoadError) Unwrap() error

type Location

type Location struct {
	File string
	Line int
	Col  int
}

Location is a position inside a workflow file. Line and Col are 1-based.

func (Location) Short

func (l Location) Short() string

Short renders file:line, which is what the terminal report shows.

func (Location) String

func (l Location) String() string

type Permissions added in v0.2.0

type Permissions struct {
	// Declared is false when the workflow or job has no permissions block.
	Declared bool
	// Loc is where the block sits, meaningful only when Declared.
	Loc Location
	// Dynamic is set when the block is an expression we cannot resolve.
	Dynamic bool
	// contains filtered or unexported fields
}

Permissions is a `permissions:` block.

Two GitHub rules make this worth modelling precisely, because together they are what allows a missing scope to be proven rather than guessed:

  • a job's block replaces the workflow's block entirely, it does not merge;
  • naming any scope sets every scope that is not named to none.

Declared separates "permissions: {}" (declared, grants nothing) from no block at all (not declared, the effective grant comes from a repository setting we cannot see offline).

func (Permissions) Allows added in v0.2.0

func (p Permissions) Allows(scope string, want Access) bool

Allows reports whether the block grants at least the wanted level.

func (Permissions) Grants added in v0.2.0

func (p Permissions) Grants(scope string) Access

Grants reports the level for a scope. Because naming any scope sets the rest to none, an undeclared scope in a declared block is none.

func (Permissions) Scopes added in v0.2.0

func (p Permissions) Scopes() []string

Scopes returns the named scopes, sorted.

type Reference

type Reference struct {
	expr.Ref
	Loc   Location
	JobID string // empty when the reference sits outside any job
	Field string // dotted YAML path, e.g. jobs.deploy.steps[1].env.TOKEN
}

Reference is one context reference found in a workflow, located precisely and attributed to the job that contains it. The job matters because secrets resolve differently depending on the job's deployment environment.

type Step added in v0.2.0

type Step struct {
	Loc  Location
	Name string
	// Uses is the action reference, e.g. actions/checkout@v4. Empty for a
	// `run:` step.
	Uses string

	// Run is the shell script of a `run:` step, empty for a `uses:` step.
	//
	// Controls must be careful with this: inferring intent from shell is where
	// false positives come from. Searching it for a specific literal command is
	// safe; deducing what a script "does" is not.
	Run string
	// contains filtered or unexported fields
}

Step is one entry of a job's steps sequence. Only what the controls need is modelled: which action it runs and which inputs it was given.

func (Step) HasInput added in v0.2.0

func (s Step) HasInput(name string) bool

HasInput reports whether the step passed the named input under `with:`.

func (Step) Input added in v0.2.0

func (s Step) Input(name string) string

Input returns the raw value of a `with:` input.

func (Step) UsesDefaultToken added in v0.2.0

func (s Step) UsesDefaultToken() bool

UsesDefaultToken reports whether the step acts with the workflow's own GITHUB_TOKEN.

This decides whether the `permissions:` block governs the step at all. An action handed a personal access token or a GitHub App token authenticates as something else entirely, and the block says nothing about what it may do — concluding anything there would be a false positive.

A step that passes no token input at all gets GITHUB_TOKEN by convention, so the block does govern it.

type Unresolved

type Unresolved struct {
	expr.Unresolved
	Loc   Location
	JobID string
	Field string
}

Unresolved is an expression that could not be parsed or resolved statically. It is reported as UNKNOWN and must never be turned into a finding.

type Workflow

type Workflow struct {
	Path string // path as displayed, relative to the repository root
	Name string

	Jobs     map[string]*Job
	JobOrder []string

	References []Reference
	Unresolved []Unresolved

	// Reusable is true when the workflow declares on.workflow_call. Secrets in
	// a reusable workflow may be supplied by the caller, so they cannot be
	// verified against this repository alone.
	Reusable bool

	// CallSecrets holds the secret names declared under on.workflow_call.secrets.
	// They are parameters, not repository secrets.
	CallSecrets map[string]Environment

	// Permissions is the workflow-level permissions block, if it declares one.
	// It applies to every job that does not declare its own.
	Permissions Permissions

	Root *yaml.Node
	// contains filtered or unexported fields
}

Workflow is a parsed workflow file.

func LoadFile

func LoadFile(path, displayPath string) (*Workflow, error)

LoadFile parses a single workflow file. displayPath is the path shown in findings; when empty, path is used.

func Parse

func Parse(displayPath string, src []byte) (*Workflow, error)

Parse builds a Workflow from the contents of a workflow file. displayPath is used verbatim in every reported location.

func (*Workflow) EffectivePermissions added in v0.2.0

func (w *Workflow) EffectivePermissions(job *Job) Permissions

EffectivePermissions returns the permissions that actually apply to the job: its own block when it declares one, otherwise the workflow's.

func (*Workflow) Job

func (w *Workflow) Job(id string) *Job

Job returns the job with the given id.

Jump to

Keyboard shortcuts

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