Documentation
¶
Overview ¶
Package jobgraph models a workflow's jobs as a directed acyclic graph and propagates taint across it.
Existing analysis in Flowlyt is per-step or per-job. That misses a real and exploited attack shape: one job writes attacker-controlled data into a job output, and a second, more privileged job consumes it and executes it. Neither job looks dangerous on its own — the vulnerability exists only in the edge between them.
Jobs form a DAG through `needs:`, and data flows only from a dependency to its dependents. Processing jobs in topological order therefore guarantees that every input to a job is fully resolved before the job is examined, so a single pass suffices and no iteration to a fixpoint is required. That is the property this package is built around.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ExpressionsIn ¶
ExpressionsIn returns the inner text of every ${{ }} expression, for callers that need to inspect them individually.
func HasUntrustedExpression ¶
HasUntrustedExpression reports whether a text interpolates an attacker-controllable context. Exported for reuse by rules.
Types ¶
type Analyzer ¶
type Analyzer struct {
// contains filtered or unexported fields
}
Analyzer performs cross-job taint propagation over a job graph.
func NewAnalyzer ¶
NewAnalyzer prepares an analyzer for a workflow's job graph.
func (*Analyzer) Analyze ¶
Analyze propagates taint across the graph and returns the flows that reach a dangerous sink in a different job from where they originated.
Complexity is O(V + E + T), where T is the total size of the expressions and scripts in the workflow: each job is visited once, in topological order.
type Flow ¶
type Flow struct {
// Origin is where the data became untrusted.
Origin Origin
// Carrier is the job output that transported it across the boundary.
Carrier Slot
// SinkJob and SinkStep are where it was consumed.
SinkJob string
SinkStep string
// SinkKind describes how it was consumed, e.g. "run script".
SinkKind string
// SinkExpression is the reference that consumed it, e.g.
// needs.build.outputs.version.
SinkExpression string
// Path is the job chain from origin to sink, for explaining the finding.
Path []string
}
Flow is one confirmed path from an untrusted source to a dangerous sink that crosses a job boundary.
type Graph ¶
type Graph struct {
// Nodes is keyed by job ID.
Nodes map[string]*Node
// contains filtered or unexported fields
}
Graph is the job dependency DAG for a single workflow.
func Build ¶
Build constructs the dependency graph for a workflow.
A `needs:` entry naming a job that does not exist is ignored rather than treated as an error: the workflow is invalid, but the remaining edges are still worth analysing.
Runs in O(V + E).
func (*Graph) Ancestors ¶
Ancestors returns every job that the given job transitively depends on.
Used to explain a finding: the path from where data became untrusted to where it was executed.
func (*Graph) TopologicalOrder ¶
TopologicalOrder returns job IDs ordered so that every job appears after all of its dependencies.
Jobs involved in a dependency cycle are excluded; use Cyclic to retrieve them.
type Node ¶
type Node struct {
ID string
Job parser.Job
// Needs are the job IDs this job depends on, in declaration order.
Needs []string
// Dependents are the job IDs that depend on this job.
Dependents []string
}
Node is one job in the graph.