From e17bfa5148444840506476584e6b31e36b70ad18 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 29 Sep 2026 16:18:30 +0100 Subject: [PATCH 1/4] Actions: migrate to shared CFG library Replace the legacy Actions CFG implementation with the shared CFG pipeline, keep data-flow nodes canonical, and ensure callable roots are connected without scalar-expression cycles. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ql/consistency-queries/CfgConsistency.ql | 2 + actions/ql/consistency-queries/qlpack.yml | 5 + .../lib/change-notes/2026-09-29-shared-cfg.md | 4 + actions/ql/lib/codeql/actions/Cfg.qll | 5 +- .../actions/controlflow/BasicBlocks.qll | 406 +---------- .../actions/controlflow/internal/Cfg.qll | 645 +++++++++++------- .../dataflow/internal/DataFlowPrivate.qll | 22 +- .../ql/test/library-tests/basic/test.expected | 222 +++++- actions/ql/test/library-tests/basic/test.ql | 49 ++ .../CONSISTENCY/CfgConsistency.expected | 7 + .../CONSISTENCY/CfgConsistency.expected | 20 + .../CONSISTENCY/CfgConsistency.expected | 71 ++ .../CONSISTENCY/CfgConsistency.expected | 72 ++ 13 files changed, 893 insertions(+), 637 deletions(-) create mode 100644 actions/ql/consistency-queries/CfgConsistency.ql create mode 100644 actions/ql/consistency-queries/qlpack.yml create mode 100644 actions/ql/lib/change-notes/2026-09-29-shared-cfg.md create mode 100644 actions/ql/test/query-tests/Security/CWE-078/CONSISTENCY/CfgConsistency.expected create mode 100644 actions/ql/test/query-tests/Security/CWE-088/CONSISTENCY/CfgConsistency.expected create mode 100644 actions/ql/test/query-tests/Security/CWE-094/CONSISTENCY/CfgConsistency.expected create mode 100644 actions/ql/test/query-tests/Security/CWE-829/CONSISTENCY/CfgConsistency.expected diff --git a/actions/ql/consistency-queries/CfgConsistency.ql b/actions/ql/consistency-queries/CfgConsistency.ql new file mode 100644 index 000000000000..76d0384ce010 --- /dev/null +++ b/actions/ql/consistency-queries/CfgConsistency.ql @@ -0,0 +1,2 @@ +import codeql.actions.Cfg +import ControlFlow::Consistency diff --git a/actions/ql/consistency-queries/qlpack.yml b/actions/ql/consistency-queries/qlpack.yml new file mode 100644 index 000000000000..41594962c859 --- /dev/null +++ b/actions/ql/consistency-queries/qlpack.yml @@ -0,0 +1,5 @@ +name: codeql/actions-consistency-queries +groups: [actions, test, consistency-queries] +dependencies: + codeql/actions-all: ${workspace} +warnOnImplicitThis: true diff --git a/actions/ql/lib/change-notes/2026-09-29-shared-cfg.md b/actions/ql/lib/change-notes/2026-09-29-shared-cfg.md new file mode 100644 index 000000000000..dfab6367a8ce --- /dev/null +++ b/actions/ql/lib/change-notes/2026-09-29-shared-cfg.md @@ -0,0 +1,4 @@ +--- +category: breaking +--- +* The GitHub Actions control flow graph (CFG) now uses the shared CFG library. The CFG includes explicit before and after nodes and uses the shared entry and exit node representations. Existing code that relies on specific CFG nodes, edges, textual representations, or basic block boundaries may need to be updated. The legacy `Completion`, `NormalCompletion`, `SimpleCompletion`, `BooleanCompletion`, and `ReturnCompletion` classes have been removed because completions are no longer part of the Actions CFG API. Code that inspected completions should inspect CFG edge labels such as `DirectSuccessor`, `BooleanSuccessor`, and `ReturnSuccessor` instead. diff --git a/actions/ql/lib/codeql/actions/Cfg.qll b/actions/ql/lib/codeql/actions/Cfg.qll index 8ccc8de1d445..695dc55bd1ee 100644 --- a/actions/ql/lib/codeql/actions/Cfg.qll +++ b/actions/ql/lib/codeql/actions/Cfg.qll @@ -1,6 +1,3 @@ /** Provides classes representing the control flow graph. */ -private import codeql.actions.controlflow.internal.Cfg as CfgInternal -import CfgInternal::Completion -import CfgInternal::CfgScope -import CfgInternal::CfgImpl +import codeql.actions.controlflow.internal.Cfg::CfgImpl diff --git a/actions/ql/lib/codeql/actions/controlflow/BasicBlocks.qll b/actions/ql/lib/codeql/actions/controlflow/BasicBlocks.qll index 2dcfd81a47dc..5ca4f19eff62 100644 --- a/actions/ql/lib/codeql/actions/controlflow/BasicBlocks.qll +++ b/actions/ql/lib/codeql/actions/controlflow/BasicBlocks.qll @@ -1,379 +1,66 @@ /** Provides classes representing basic blocks. */ -private import codeql.actions.Cfg -private import codeql.actions.Ast -private import codeql.Locations +private import codeql.actions.Cfg as Cfg /** * A basic block, that is, a maximal straight-line sequence of control flow nodes * without branches or joins. */ -class BasicBlock extends TBasicBlockStart { - /** Gets the scope of this basic block. */ - final CfgScope getScope() { result = this.getFirstNode().getScope() } - +class BasicBlock extends Cfg::BasicBlock { /** Gets an immediate successor of this basic block, if any. */ - BasicBlock getASuccessor() { result = this.getASuccessor(_) } + BasicBlock getASuccessor() { result = super.getASuccessor() } /** Gets an immediate successor of this basic block of a given type, if any. */ - BasicBlock getASuccessor(SuccessorType t) { - result.getFirstNode() = this.getLastNode().getASuccessor(t) - } + BasicBlock getASuccessor(Cfg::SuccessorType t) { result = super.getASuccessor(t) } /** Gets an immediate predecessor of this basic block, if any. */ - BasicBlock getAPredecessor() { result.getASuccessor() = this } + BasicBlock getAPredecessor() { result = super.getAPredecessor() } /** Gets an immediate predecessor of this basic block of a given type, if any. */ - BasicBlock getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this } + BasicBlock getAPredecessor(Cfg::SuccessorType t) { result = super.getAPredecessor(t) } /** Gets the control flow node at a specific (zero-indexed) position in this basic block. */ - Node getNode(int pos) { bbIndex(this.getFirstNode(), result, pos) } + Cfg::Node getNode(int pos) { result = super.getNode(pos) } /** Gets a control flow node in this basic block. */ - Node getANode() { result = this.getNode(_) } + Cfg::Node getANode() { result = super.getANode() } /** Gets the first control flow node in this basic block. */ - Node getFirstNode() { this = TBasicBlockStart(result) } + Cfg::Node getFirstNode() { result = super.getFirstNode() } /** Gets the last control flow node in this basic block. */ - Node getLastNode() { result = this.getNode(this.length() - 1) } - - /** Gets the length of this basic block. */ - int length() { result = strictcount(this.getANode()) } - - /** - * Holds if this basic block immediately dominates basic block `bb`. - * - * That is, all paths reaching basic block `bb` from some entry point - * basic block must go through this basic block (which is an immediate - * predecessor of `bb`). - * - * Example: - * - * ```rb - * def m b - * if b - * return 0 - * end - * return 1 - * end - * ``` - * - * The basic block starting on line 2 immediately dominates the - * basic block on line 5 (all paths from the entry point of `m` - * to `return 1` must go through the `if` block). - */ - predicate immediatelyDominates(BasicBlock bb) { bbIDominates(this, bb) } - - /** - * Holds if this basic block strictly dominates basic block `bb`. - * - * That is, all paths reaching basic block `bb` from some entry point - * basic block must go through this basic block (which must be different - * from `bb`). - * - * Example: - * - * ```rb - * def m b - * if b - * return 0 - * end - * return 1 - * end - * ``` - * - * The basic block starting on line 2 strictly dominates the - * basic block on line 5 (all paths from the entry point of `m` - * to `return 1` must go through the `if` block). - */ - predicate strictlyDominates(BasicBlock bb) { bbIDominates+(this, bb) } - - /** - * Holds if this basic block dominates basic block `bb`. - * - * That is, all paths reaching basic block `bb` from some entry point - * basic block must go through this basic block. - * - * Example: - * - * ```rb - * def m b - * if b - * return 0 - * end - * return 1 - * end - * ``` - * - * The basic block starting on line 2 dominates the basic - * basic block on line 5 (all paths from the entry point of `m` - * to `return 1` must go through the `if` block). - */ - predicate dominates(BasicBlock bb) { - bb = this or - this.strictlyDominates(bb) - } - - /** - * Holds if `df` is in the dominance frontier of this basic block. - * That is, this basic block dominates a predecessor of `df`, but - * does not dominate `df` itself. - * - * Example: - * - * ```rb - * def m x - * if x < 0 - * x = -x - * if x > 10 - * x = x - 1 - * end - * end - * puts x - * end - * ``` - * - * The basic block on line 8 is in the dominance frontier - * of the basic block starting on line 3 because that block - * dominates the basic block on line 4, which is a predecessor of - * `puts x`. Also, the basic block starting on line 3 does not - * dominate the basic block on line 8. - */ - predicate inDominanceFrontier(BasicBlock df) { - this.dominatesPredecessor(df) and - not this.strictlyDominates(df) - } + Cfg::Node getLastNode() { result = super.getLastNode() } - /** - * Holds if this basic block dominates a predecessor of `df`. - */ - private predicate dominatesPredecessor(BasicBlock df) { this.dominates(df.getAPredecessor()) } + predicate immediatelyDominates(BasicBlock bb) { super.immediatelyDominates(bb) } - /** - * Gets the basic block that immediately dominates this basic block, if any. - * - * That is, all paths reaching this basic block from some entry point - * basic block must go through the result, which is an immediate basic block - * predecessor of this basic block. - * - * Example: - * - * ```rb - * def m b - * if b - * return 0 - * end - * return 1 - * end - * ``` - * - * The basic block starting on line 2 is an immediate dominator of - * the basic block on line 5 (all paths from the entry point of `m` - * to `return 1` must go through the `if` block, and the `if` block - * is an immediate predecessor of `return 1`). - */ - BasicBlock getImmediateDominator() { bbIDominates(result, this) } + predicate strictlyDominates(BasicBlock bb) { super.strictlyDominates(bb) } - /** - * Holds if this basic block strictly post-dominates basic block `bb`. - * - * That is, all paths reaching a normal exit point basic block from basic - * block `bb` must go through this basic block (which must be different - * from `bb`). - * - * Example: - * - * ```rb - * def m b - * if b - * puts "b" - * end - * puts "m" - * end - * ``` - * - * The basic block on line 5 strictly post-dominates the basic block on - * line 3 (all paths to the exit point of `m` from `puts "b"` must go - * through `puts "m"`). - */ - predicate strictlyPostDominates(BasicBlock bb) { bbIPostDominates+(this, bb) } + predicate dominates(BasicBlock bb) { super.dominates(bb) } - /** - * Holds if this basic block post-dominates basic block `bb`. - * - * That is, all paths reaching a normal exit point basic block from basic - * block `bb` must go through this basic block. - * - * Example: - * - * ```rb - * def m b - * if b - * puts "b" - * end - * puts "m" - * end - * ``` - * - * The basic block on line 5 post-dominates the basic block on line 3 - * (all paths to the exit point of `m` from `puts "b"` must go through - * `puts "m"`). - */ - predicate postDominates(BasicBlock bb) { - this.strictlyPostDominates(bb) or - this = bb - } + predicate inDominanceFrontier(BasicBlock df) { super.inDominanceFrontier(df) } - /** Holds if this basic block is in a loop in the control flow graph. */ - predicate inLoop() { this.getASuccessor+() = this } + BasicBlock getImmediateDominator() { result = super.getImmediateDominator() } - /** Gets a textual representation of this basic block. */ - string toString() { result = this.getFirstNode().toString() } + predicate strictlyPostDominates(BasicBlock bb) { super.strictlyPostDominates(bb) } - /** Gets the location of this basic block. */ - Location getLocation() { result = this.getFirstNode().getLocation() } + predicate postDominates(BasicBlock bb) { super.postDominates(bb) } } -cached -private module Cached { - /** Internal representation of basic blocks. */ - cached - newtype TBasicBlock = TBasicBlockStart(Node cfn) { startsBB(cfn) } - - /** Holds if `cfn` starts a new basic block. */ - private predicate startsBB(Node cfn) { - not exists(cfn.getAPredecessor()) and exists(cfn.getASuccessor()) - or - cfn.isJoin() - or - cfn.getAPredecessor().isBranch() - or - /* - * In cases such as - * - * ```rb - * if x or y - * foo - * else - * bar - * ``` - * - * we have a CFG that looks like - * - * x --false--> [false] x or y --false--> bar - * \ | - * --true--> y --false-- - * \ - * --true--> [true] x or y --true--> foo - * - * and we want to ensure that both `foo` and `bar` start a new basic block, - * in order to get a `ConditionalBlock` out of the disjunction. - */ - - exists(cfn.getAPredecessor(any(BooleanSuccessor s))) - } - - /** - * Holds if `succ` is a control flow successor of `pred` within - * the same basic block. - */ - private predicate intraBBSucc(Node pred, Node succ) { - succ = pred.getASuccessor() and - not startsBB(succ) - } - - /** - * Holds if `cfn` is the `i`th node in basic block `bb`. - * - * In other words, `i` is the shortest distance from a node `bbStart` - * that starts a basic block to `cfn` along the `intraBBSucc` relation. - */ - cached - predicate bbIndex(Node bbStart, Node cfn, int i) = - shortestDistances(startsBB/1, intraBBSucc/2)(bbStart, cfn, i) - - /** - * Holds if the first node of basic block `succ` is a control flow - * successor of the last node of basic block `pred`. - */ - private predicate succBB(BasicBlock pred, BasicBlock succ) { succ = pred.getASuccessor() } - - /** Holds if `dom` is an immediate dominator of `bb`. */ - cached - predicate bbIDominates(BasicBlock dom, BasicBlock bb) = - idominance(entryBB/1, succBB/2)(_, dom, bb) - - /** Holds if `pred` is a basic block predecessor of `succ`. */ - private predicate predBB(BasicBlock succ, BasicBlock pred) { succBB(pred, succ) } - - /** Holds if `bb` is an exit basic block that represents normal exit. */ - private predicate normalExitBB(BasicBlock bb) { bb.getANode().(AnnotatedExitNode).isNormal() } - - /** Holds if `dom` is an immediate post-dominator of `bb`. */ - cached - predicate bbIPostDominates(BasicBlock dom, BasicBlock bb) = - idominance(normalExitBB/1, predBB/2)(_, dom, bb) - - /** - * Gets the `i`th predecessor of join block `jb`, with respect to some - * arbitrary order. - */ - cached - JoinBlockPredecessor getJoinBlockPredecessor(JoinBlock jb, int i) { - none() - /* - * result = - * rank[i + 1](JoinBlockPredecessor jbp | - * jbp = jb.getAPredecessor() - * | - * jbp order by JoinBlockPredecessors::getId(jbp), JoinBlockPredecessors::getSplitString(jbp) - * ) - */ - - } - - cached - predicate immediatelyControls(ConditionBlock cb, BasicBlock succ, BooleanSuccessor s) { - succ = cb.getASuccessor(s) and - forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != cb | succ.dominates(pred)) - } - - cached - predicate controls(ConditionBlock cb, BasicBlock controlled, BooleanSuccessor s) { - exists(BasicBlock succ | cb.immediatelyControls(succ, s) | succ.dominates(controlled)) - } -} - -private import Cached - -/** Holds if `bb` is an entry basic block. */ -private predicate entryBB(BasicBlock bb) { bb.getFirstNode() instanceof EntryNode } - /** * An entry basic block, that is, a basic block whose first node is * an entry node. */ -class EntryBasicBlock extends BasicBlock { - EntryBasicBlock() { entryBB(this) } -} +class EntryBasicBlock extends BasicBlock, Cfg::EntryBasicBlock { } /** - * An annotated exit basic block, that is, a basic block whose last node is - * an annotated exit node. + * An annotated exit basic block, that is, a basic block that contains an + * annotated exit node. */ class AnnotatedExitBasicBlock extends BasicBlock { - private boolean normal; - - AnnotatedExitBasicBlock() { - exists(AnnotatedExitNode n | - n = this.getANode() and - if n.isNormal() then normal = true else normal = false - ) - } + AnnotatedExitBasicBlock() { this.getANode() instanceof Cfg::AnnotatedExitNode } - /** Holds if this block represent a normal exit. */ - final predicate isNormal() { normal = true } + /** Holds if this block represents a normal exit. */ + final predicate isNormal() { this.getANode() instanceof Cfg::NormalExitNode } } /** @@ -381,39 +68,18 @@ class AnnotatedExitBasicBlock extends BasicBlock { * an exit node. */ class ExitBasicBlock extends BasicBlock { - ExitBasicBlock() { this.getLastNode() instanceof ExitNode } + ExitBasicBlock() { this.getLastNode() instanceof Cfg::ExitNode } } -/* - * private module JoinBlockPredecessors { - * private predicate id(AstNode x, AstNode y) { x = y } - * - * private predicate idOf(AstNode x, int y) = equivalenceRelation(id/2)(x, y) - * - * int getId(JoinBlockPredecessor jbp) { - * idOf(Ast::toTreeSitter(jbp.getFirstNode().(AstCfgNode).getAstNode()), result) - * or - * idOf(Ast::toTreeSitter(jbp.(EntryBasicBlock).getScope()), result) - * } - * - * string getSplitString(JoinBlockPredecessor jbp) { - * result = jbp.getFirstNode().(AstCfgNode).getSplitsString() - * or - * not exists(jbp.getFirstNode().(AstCfgNode).getSplitsString()) and - * result = "" - * } - * } - */ - /** A basic block with more than one predecessor. */ class JoinBlock extends BasicBlock { - JoinBlock() { this.getFirstNode().isJoin() } + JoinBlock() { strictcount(this.getFirstNode().getAPredecessor()) > 1 } /** * Gets the `i`th predecessor of this join block, with respect to some * arbitrary order. */ - JoinBlockPredecessor getJoinBlockPredecessor(int i) { result = getJoinBlockPredecessor(this, i) } + JoinBlockPredecessor getJoinBlockPredecessor(int i) { none() } } /** A basic block that is an immediate predecessor of a join block. */ @@ -423,22 +89,24 @@ class JoinBlockPredecessor extends BasicBlock { /** A basic block that terminates in a condition, splitting the subsequent control flow. */ class ConditionBlock extends BasicBlock { - ConditionBlock() { this.getLastNode().isCondition() } + ConditionBlock() { + exists(this.getLastNode().getASuccessor(any(Cfg::BooleanSuccessor successor))) + } /** * Holds if basic block `succ` is immediately controlled by this basic - * block with conditional value `s`. That is, `succ` is an immediate - * successor of this block, and `succ` can only be reached from - * the callable entry point by going via the `s` edge out of this basic block. + * block with conditional value `s`. */ - predicate immediatelyControls(BasicBlock succ, BooleanSuccessor s) { - immediatelyControls(this, succ, s) + predicate immediatelyControls(BasicBlock succ, Cfg::BooleanSuccessor s) { + succ = this.getASuccessor(s) and + forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != this | succ.dominates(pred)) } /** * Holds if basic block `controlled` is controlled by this basic block with - * conditional value `s`. That is, `controlled` can only be reached from - * the callable entry point by going via the `s` edge out of this basic block. + * conditional value `s`. */ - predicate controls(BasicBlock controlled, BooleanSuccessor s) { controls(this, controlled, s) } + predicate controls(BasicBlock controlled, Cfg::BooleanSuccessor s) { + exists(BasicBlock succ | this.immediatelyControls(succ, s) and succ.dominates(controlled)) + } } diff --git a/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll b/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll index 38ce9e7e03db..72e56dc6fc2f 100644 --- a/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll +++ b/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll @@ -1,288 +1,475 @@ private import codeql.actions.Ast -private import codeql.controlflow.Cfg as CfgShared +private import codeql.controlflow.ControlFlowGraph as CfgShared private import codeql.Locations +private import codeql.util.Void -module Completion { - import codeql.controlflow.SuccessorType +private class ActionsAstNode = AstNode; - private newtype TCompletion = - TSimpleCompletion() or - TBooleanCompletion(boolean b) { b in [false, true] } or - TReturnCompletion() +module CfgImpl { + private predicate isCfgChild(AstNode parent, AstNode child) { + exists(CompositeAction action | + parent = action and + (child = action.getAnInput() or child = action.getOutputs() or child = action.getRuns()) + ) + or + exists(ReusableWorkflow workflow | + parent = workflow and + ( + child = workflow.getAnInput() or + child = workflow.getOutputs() or + child = workflow.getStrategy() or + child = workflow.getAJob() + ) + ) + or + exists(Workflow workflow | + parent = workflow and + not workflow instanceof ReusableWorkflow and + (child = workflow.getStrategy() or child = workflow.getAJob()) + ) + or + exists(Runs runs | parent = runs and child = runs.getStep(_)) + or + exists(Outputs outputs | parent = outputs and child = outputs.getAnOutputExpr()) + or + exists(Strategy strategy | parent = strategy and child = strategy.getAMatrixVarExpr()) + or + exists(LocalJob job | + parent = job and + (child = job.getAStep() or child = job.getOutputs() or child = job.getStrategy()) + ) + or + exists(ExternalJob job | + parent = job and + ( + child = job.getArgumentExpr(_) or + child = job.getInScopeEnvVarExpr(_) or + child = job.getOutputs() or + child = job.getStrategy() + ) + ) + or + exists(UsesStep uses | + parent = uses and + (child = uses.getArgumentExpr(_) or child = uses.getInScopeEnvVarExpr(_)) + ) + or + exists(Run run | + parent = run and + ( + child = run.getInScopeEnvVarExpr(_) or + child = run.getAnScriptExpr() or + child = run.getScript() + ) + ) + } - abstract class Completion extends TCompletion { - abstract string toString(); + private AstNode getCfgChild(AstNode parent, int index) { + result = + rank[index](AstNode child, Location l | + isCfgChild(parent, child) and l = child.getLocation() + | + child + order by + l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString() + ) + } - predicate isValidForSpecific(AstNode e) { none() } + private AstNode getLastCfgAstNode(AstNode node) { + not exists(getCfgChild(node, _)) and result = node + or + exists(AstNode child, int index | + child = getCfgChild(node, index) and + not exists(int later | later > index and exists(getCfgChild(node, later))) and + result = getLastCfgAstNode(child) + ) + } - predicate isValidFor(AstNode e) { this.isValidForSpecific(e) } + private module CfgAst implements CfgShared::AstSig { + class AstNode = ActionsAstNode; - abstract SuccessorType getAMatchingSuccessorType(); - } + AstNode getChild(AstNode node, int index) { result = getCfgChild(node, index) } - abstract class NormalCompletion extends Completion { } + class Callable extends AstNode { + Callable() { this instanceof Workflow or this instanceof CompositeAction } + } - class SimpleCompletion extends NormalCompletion, TSimpleCompletion { - override string toString() { result = "SimpleCompletion" } + AstNode callableGetBody(Callable callable) { result = callable } - override predicate isValidFor(AstNode e) { not any(Completion c).isValidForSpecific(e) } + Callable getEnclosingCallable(AstNode node) { + result = node.(Callable) + or + result = getEnclosingCallable(node.getParentNode()) + } - override DirectSuccessor getAMatchingSuccessorType() { any() } - } + class Parameter extends AstNode { + Parameter() { none() } - class BooleanCompletion extends NormalCompletion, TBooleanCompletion { - boolean value; + AstNode getPattern() { none() } - BooleanCompletion() { this = TBooleanCompletion(value) } + Expr getDefaultValue() { none() } + } - override string toString() { result = "BooleanCompletion(" + value + ")" } + Parameter callableGetParameter(Callable callable, int index) { none() } - override predicate isValidForSpecific(AstNode e) { none() } + class Stmt extends AstNode { + Stmt() { none() } + } - override BooleanSuccessor getAMatchingSuccessorType() { result.getValue() = value } + class LabeledStmt extends Stmt { + LabeledStmt() { none() } - final boolean getValue() { result = value } - } + Stmt getStmt() { none() } + } - class ReturnCompletion extends Completion, TReturnCompletion { - override string toString() { result = "ReturnCompletion" } + class Expr extends AstNode { + Expr() { none() } + } - override predicate isValidForSpecific(AstNode e) { none() } + class BlockStmt extends Stmt { + BlockStmt() { none() } - override ReturnSuccessor getAMatchingSuccessorType() { any() } - } -} + Stmt getStmt(int index) { none() } -module CfgScope { - abstract class CfgScope extends AstNode { } + Stmt getLastStmt() { none() } + } - class WorkflowScope extends CfgScope instanceof Workflow { } + class ExprStmt extends Stmt { + ExprStmt() { none() } - class CompositeActionScope extends CfgScope instanceof CompositeAction { } -} + Expr getExpr() { none() } + } -private module Implementation implements CfgShared::InputSig { - import codeql.actions.Ast - import Completion - import CfgScope + class IfStmt extends Stmt { + IfStmt() { none() } - predicate completionIsNormal(Completion c) { not c instanceof ReturnCompletion } + Expr getCondition() { none() } - // Not using CFG splitting, so the following are just dummy types. - private newtype TUnit = Unit() + Stmt getThen() { none() } - additional class SplitKindBase = TUnit; + Stmt getElse() { none() } + } - additional class Split extends TUnit { - abstract string toString(); - } + class LoopStmt extends Stmt { + LoopStmt() { none() } - predicate completionIsSimple(Completion c) { c instanceof SimpleCompletion } + Stmt getBody() { none() } + } - predicate completionIsValidFor(Completion c, AstNode e) { c.isValidFor(e) } + class WhileStmt extends LoopStmt { + WhileStmt() { none() } - CfgScope getCfgScope(AstNode e) { - exists(AstNode p | p = e.getParentNode() | - result = p - or - not p instanceof CfgScope and result = getCfgScope(p) - ) - } + Expr getCondition() { none() } + } - additional int maxSplits() { result = 0 } + class DoStmt extends LoopStmt { + DoStmt() { none() } - predicate scopeFirst(CfgScope scope, AstNode e) { - first(scope.(Workflow), e) or - first(scope.(CompositeAction), e) - } + Expr getCondition() { none() } + } - predicate scopeLast(CfgScope scope, AstNode e, Completion c) { - last(scope.(Workflow), e, c) or - last(scope.(CompositeAction), e, c) - } + class UntilStmt extends LoopStmt { + UntilStmt() { none() } - SuccessorType getAMatchingSuccessorType(Completion c) { result = c.getAMatchingSuccessorType() } + Expr getCondition() { none() } + } - int idOfAstNode(AstNode node) { none() } + class ForStmt extends LoopStmt { + ForStmt() { none() } - int idOfCfgScope(CfgScope scope) { none() } -} + AstNode getInit(int index) { none() } -module CfgImpl = CfgShared::Make; + Expr getCondition() { none() } -private import CfgImpl -private import Completion -private import CfgScope + AstNode getUpdate(int index) { none() } + } -private class CompositeActionTree extends StandardPreOrderTree instanceof CompositeAction { - override ControlFlowTree getChildNode(int i) { - result = - rank[i](AstNode child, Location l | - ( - child = this.(CompositeAction).getAnInput() or - child = this.(CompositeAction).getOutputs() or - child = this.(CompositeAction).getRuns() - ) and - l = child.getLocation() - | - child - order by - l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString() - ) - } -} + class ForEachStmt extends LoopStmt { + ForEachStmt() { none() } -private class RunsTree extends StandardPreOrderTree instanceof Runs { - override ControlFlowTree getChildNode(int i) { result = super.getStep(i) } -} + Expr getVariable() { none() } -private class WorkflowTree extends StandardPreOrderTree instanceof Workflow { - override ControlFlowTree getChildNode(int i) { - if this instanceof ReusableWorkflow - then - result = - rank[i](AstNode child, Location l | - ( - child = this.(ReusableWorkflow).getAnInput() or - child = this.(ReusableWorkflow).getOutputs() or - child = this.(ReusableWorkflow).getStrategy() or - child = this.(ReusableWorkflow).getAJob() - ) and - l = child.getLocation() - | - child - order by - l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString() - ) - else - result = - rank[i](AstNode child, Location l | - ( - child = super.getStrategy() or - child = super.getAJob() - ) and - l = child.getLocation() - | - child - order by - l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString() - ) - } -} + Expr getCollection() { none() } + } -private class OutputsTree extends StandardPreOrderTree instanceof Outputs { - override ControlFlowTree getChildNode(int i) { - result = - rank[i](AstNode child, Location l | - child = super.getAnOutputExpr() and l = child.getLocation() - | - child - order by - l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString() - ) - } -} + class BreakStmt extends Stmt { + BreakStmt() { none() } + } -private class StrategyTree extends StandardPreOrderTree instanceof Strategy { - override ControlFlowTree getChildNode(int i) { - result = - rank[i](AstNode child, Location l | - child = super.getAMatrixVarExpr() and l = child.getLocation() - | - child - order by - l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString() - ) - } -} + class ContinueStmt extends Stmt { + ContinueStmt() { none() } + } -private class JobTree extends StandardPreOrderTree instanceof LocalJob { - override ControlFlowTree getChildNode(int i) { - result = - rank[i](AstNode child, Location l | - ( - child = super.getAStep() or - child = super.getOutputs() or - child = super.getStrategy() - ) and - l = child.getLocation() - | - child - order by - l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString() - ) + class GotoStmt extends Stmt { + GotoStmt() { none() } + } + + class ReturnStmt extends Stmt { + ReturnStmt() { none() } + + Expr getExpr() { none() } + } + + class Throw extends AstNode { + Throw() { none() } + + Expr getExpr() { none() } + } + + class TryStmt extends Stmt { + TryStmt() { none() } + + AstNode getBody(int index) { none() } + + CatchClause getCatch(int index) { none() } + + Stmt getFinally() { none() } + } + + class CatchClause extends AstNode { + CatchClause() { none() } + + AstNode getPattern() { none() } + + AstNode getVariable() { none() } + + Expr getCondition() { none() } + + Stmt getBody() { none() } + } + + class Switch extends AstNode { + Switch() { none() } + + Expr getExpr() { none() } + + Case getCase(int index) { none() } + + Stmt getStmt(int index) { none() } + } + + class Case extends AstNode { + Case() { none() } + + AstNode getPattern(int index) { none() } + + Expr getGuard() { none() } + + AstNode getBody() { none() } + } + + class DefaultCase extends Case { + DefaultCase() { none() } + } + + class ConditionalExpr extends Expr { + ConditionalExpr() { none() } + + Expr getCondition() { none() } + + Expr getThen() { none() } + + Expr getElse() { none() } + } + + class BinaryExpr extends Expr { + BinaryExpr() { none() } + + Expr getLeftOperand() { none() } + + Expr getRightOperand() { none() } + } + + class LogicalAndExpr extends BinaryExpr { + LogicalAndExpr() { none() } + } + + class LogicalOrExpr extends BinaryExpr { + LogicalOrExpr() { none() } + } + + class NullCoalescingExpr extends BinaryExpr { + NullCoalescingExpr() { none() } + } + + class UnaryExpr extends Expr { + UnaryExpr() { none() } + + Expr getOperand() { none() } + } + + class LogicalNotExpr extends UnaryExpr { + LogicalNotExpr() { none() } + } + + class Assignment extends BinaryExpr { + Assignment() { none() } + } + + class AssignExpr extends Assignment { + AssignExpr() { none() } + } + + class CompoundAssignment extends Assignment { + CompoundAssignment() { none() } + } + + class AssignLogicalAndExpr extends CompoundAssignment { + AssignLogicalAndExpr() { none() } + } + + class AssignLogicalOrExpr extends CompoundAssignment { + AssignLogicalOrExpr() { none() } + } + + class AssignNullCoalescingExpr extends CompoundAssignment { + AssignNullCoalescingExpr() { none() } + } + + class BooleanLiteral extends Expr { + BooleanLiteral() { none() } + + boolean getValue() { none() } + } + + class PatternMatchExpr extends Expr { + PatternMatchExpr() { none() } + + Expr getExpr() { none() } + + AstNode getPattern() { none() } + } } -} -private class ExternalJobTree extends StandardPreOrderTree instanceof ExternalJob { - override ControlFlowTree getChildNode(int i) { - result = - rank[i](AstNode child, Location l | - ( - child = super.getArgumentExpr(_) or - child = super.getInScopeEnvVarExpr(_) or - child = super.getOutputs() or - child = super.getStrategy() - ) and - l = child.getLocation() - | - child - order by - l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString() - ) + private module Cfg0 = CfgShared::Make0; + + private module Input1 implements Cfg0::InputSig1 { + predicate cfgCachedStageRef() { CfgCachedStage::ref() } + + class Label = Void; + + class CallableContext = Void; } -} -private class UsesTree extends StandardPreOrderTree instanceof UsesStep { - override ControlFlowTree getChildNode(int i) { - result = - rank[i](AstNode child, Location l | - (child = super.getArgumentExpr(_) or child = super.getInScopeEnvVarExpr(_)) and - l = child.getLocation() - | - child - order by - l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString() - ) + private module Cfg1 = Cfg0::Make1; + + private module Input2 implements Cfg1::InputSig2 { + predicate beginAbruptCompletion( + AstNode ast, PreControlFlowNode node, AbruptCompletion completion, boolean always + ) { + none() + } + + predicate endAbruptCompletion(AstNode ast, PreControlFlowNode node, AbruptCompletion completion) { + none() + } + + predicate step(PreControlFlowNode predecessor, PreControlFlowNode successor) { none() } } -} -private class RunTree extends StandardPreOrderTree instanceof Run { - override ControlFlowTree getChildNode(int i) { - result = - rank[i](AstNode child, Location l | - ( - child = super.getInScopeEnvVarExpr(_) or - child = super.getAnScriptExpr() or - child = super.getScript() - ) and - l = child.getLocation() - | - child - order by - l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString() - ) + private module Cfg2 = Cfg1::Make2; + + private import Cfg0 + private import Cfg1 + private import Cfg2 + import Public + import ControlFlow + + class CfgScope = CfgAst::Callable; + + /** A CFG scope for a workflow. */ + class WorkflowScope extends CfgScope instanceof Workflow { } + + /** A CFG scope for a composite action. */ + class CompositeActionScope extends CfgScope instanceof CompositeAction { } + + /** + * A control flow node. + * + * Only nodes that can be reached from an entry point are included in the CFG. + */ + class Node extends ControlFlowNode { + /** Gets the CFG scope containing this node. */ + CfgScope getScope() { result = this.getEnclosingCallable() } + + Node getASuccessor(SuccessorType type) { result = super.getASuccessor(type) } + + Node getASuccessor() { result = super.getASuccessor() } + + /** Gets an immediate predecessor connected by an edge of type `type`, if any. */ + Node getAPredecessor(SuccessorType type) { result.getASuccessor(type) = this } + + Node getAPredecessor() { result = super.getAPredecessor() } + + /** Holds if this node has a conditional successor. */ + predicate isCondition() { exists(this.getASuccessor(any(ConditionalSuccessor successor))) } + + /** Holds if this node has more than one predecessor. */ + predicate isJoin() { strictcount(this.getAPredecessor()) > 1 } + + /** Holds if this node has more than one successor. */ + predicate isBranch() { strictcount(this.getASuccessor()) > 1 } } -} -private class ScalarValueTree extends StandardPreOrderTree instanceof ScalarValue { - override ControlFlowTree getChildNode(int i) { - result = - rank[i](Expression child, Location l | - child = super.getAChildNode() and - l = child.getLocation() - | - child - order by - l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString() - ) + /** The control flow node at the entry point of a scope. */ + class EntryNode extends Node, ControlFlow::EntryNode { } + + /** A control flow node indicating normal or exceptional termination of a scope. */ + class AnnotatedExitNode extends Node, ControlFlow::AnnotatedExitNode { + /** Holds if this node represents a normal exit. */ + predicate isNormal() { this instanceof NormalExitNode } } -} -private class UsesLeaf extends LeafTree instanceof Uses { } + /** A control flow node indicating normal termination of a scope. */ + class NormalExitNode extends AnnotatedExitNode, ControlFlow::NormalExitNode { } -private class InputTree extends LeafTree instanceof Input { } + /** A control flow node indicating exceptional termination of a scope. */ + class ExceptionalExitNode extends AnnotatedExitNode, ControlFlow::ExceptionalExitNode { } -private class ScalarValueLeaf extends LeafTree instanceof ScalarValue { } + /** A control flow node indicating the termination of a scope. */ + class ExitNode extends Node, ControlFlow::ExitNode { } -private class ExpressionLeaf extends LeafTree instanceof Expression { } + /** The empty split type retained for compatibility with the legacy Actions CFG. */ + class Split = Void; + + /** + * A node that uniquely represents an AST node. + * + * Unreachable AST nodes do not have an `AstCfgNode`. + */ + class AstCfgNode extends Node { + AstCfgNode() { this.injects(_) } + + AstNode getAstNode() { this.injects(result) } + + /** Gets a comma-separated list of splits in this node, if any. */ + string getSplitsString() { none() } + + /** Gets a split for this control flow node, if any. */ + Split getASplit() { none() } + } + + /** + * If needed, call this predicate to force a stage dependency on the cached CFG stage. + */ + cached + predicate forceCachingInSameStage() { CfgCachedStage::ref() } + + /** Gets the first AST node executed within `node`. */ + cached + AstNode getAControlFlowEntryNode(AstNode node) { + result = node and + exists(Node cfgNode | cfgNode.injects(node)) + } + + /** Gets a potential last AST node executed within `node`. */ + cached + AstNode getAControlFlowExitNode(AstNode node) { + exists(Node cfgNode | cfgNode.injects(node)) and + result = getLastCfgAstNode(node) + } + + /** Gets the CFG scope of `node`. */ + cached + CfgScope getNodeCfgScope(Node node) { result = node.getScope() } +} diff --git a/actions/ql/lib/codeql/actions/dataflow/internal/DataFlowPrivate.qll b/actions/ql/lib/codeql/actions/dataflow/internal/DataFlowPrivate.qll index cf95292588c3..084acb587768 100644 --- a/actions/ql/lib/codeql/actions/dataflow/internal/DataFlowPrivate.qll +++ b/actions/ql/lib/codeql/actions/dataflow/internal/DataFlowPrivate.qll @@ -59,13 +59,16 @@ predicate nodeIsHidden(Node node) { none() } class DataFlowExpr extends Cfg::Node { DataFlowExpr() { - this.getAstNode() instanceof Job or - this.getAstNode() instanceof Expression or - this.getAstNode() instanceof Uses or - this.getAstNode() instanceof Run or - this.getAstNode() instanceof Outputs or - this.getAstNode() instanceof Input or - this.getAstNode() instanceof ScalarValue + this.injects(this.getAstNode()) and + ( + this.getAstNode() instanceof Job or + this.getAstNode() instanceof Expression or + this.getAstNode() instanceof Uses or + this.getAstNode() instanceof Run or + this.getAstNode() instanceof Outputs or + this.getAstNode() instanceof Input or + this.getAstNode() instanceof ScalarValue + ) } } @@ -73,7 +76,10 @@ class DataFlowExpr extends Cfg::Node { * A call corresponds to a Uses steps where a composite action or a reusable workflow get called */ class DataFlowCall instanceof Cfg::Node { - DataFlowCall() { super.getAstNode() instanceof Uses } + DataFlowCall() { + this.injects(this.getAstNode()) and + super.getAstNode() instanceof Uses + } /** Gets a textual representation of this element. */ string toString() { result = super.toString() } diff --git a/actions/ql/test/library-tests/basic/test.expected b/actions/ql/test/library-tests/basic/test.expected index 7c1c560c87e1..947b249f3791 100644 --- a/actions/ql/test/library-tests/basic/test.expected +++ b/actions/ql/test/library-tests/basic/test.expected @@ -931,254 +931,369 @@ parentNodes | .github/workflows/test.yml:40:14:40:52 | echo ${{needs.job1.outputs.job_output}} | .github/workflows/test.yml:39:9:40:53 | Run Step: sink | | .github/workflows/test.yml:40:20:40:53 | needs.job1.outputs.job_output | .github/workflows/test.yml:40:14:40:52 | echo ${{needs.job1.outputs.job_output}} | cfgNodes -| .github/workflows/commands.yml:1:1:39:30 | enter on: push | -| .github/workflows/commands.yml:1:1:39:30 | exit on: push | -| .github/workflows/commands.yml:1:1:39:30 | exit on: push (normal) | +| .github/workflows/commands.yml:1:1:39:30 | After on: push | +| .github/workflows/commands.yml:1:1:39:30 | Entry | +| .github/workflows/commands.yml:1:1:39:30 | Exit | +| .github/workflows/commands.yml:1:1:39:30 | Normal Exit | | .github/workflows/commands.yml:1:1:39:30 | on: push | +| .github/workflows/commands.yml:9:5:31:2 | After Job: local_commands | | .github/workflows/commands.yml:9:5:31:2 | Job: local_commands | +| .github/workflows/commands.yml:15:9:18:6 | After Run Step | | .github/workflows/commands.yml:15:9:18:6 | Run Step | | .github/workflows/commands.yml:16:14:17:30 | command1 ; command2\n | +| .github/workflows/commands.yml:18:9:20:6 | After Run Step | | .github/workflows/commands.yml:18:9:20:6 | Run Step | | .github/workflows/commands.yml:18:14:19:30 | command3 \| command4\n | +| .github/workflows/commands.yml:20:9:22:6 | After Run Step | | .github/workflows/commands.yml:20:9:22:6 | Run Step | | .github/workflows/commands.yml:20:14:21:33 | command5 "$(command6)"\n | +| .github/workflows/commands.yml:22:9:24:6 | After Run Step | | .github/workflows/commands.yml:22:9:24:6 | Run Step | | .github/workflows/commands.yml:22:14:23:31 | command7 && command8\n | +| .github/workflows/commands.yml:24:9:26:6 | After Run Step | | .github/workflows/commands.yml:24:9:26:6 | Run Step | | .github/workflows/commands.yml:24:14:25:32 | command9 \|\| command10\n | +| .github/workflows/commands.yml:26:9:28:6 | After Run Step | | .github/workflows/commands.yml:26:9:28:6 | Run Step | | .github/workflows/commands.yml:26:14:27:34 | command11 "`command12`"\n | +| .github/workflows/commands.yml:28:9:31:2 | After Run Step | | .github/workflows/commands.yml:28:9:31:2 | Run Step | | .github/workflows/commands.yml:28:14:29:50 | command13 "`command14` $(date \| wc -l)"\n | +| .github/workflows/commands.yml:32:5:39:30 | After Job: local_commands2 | | .github/workflows/commands.yml:32:5:39:30 | Job: local_commands2 | +| .github/workflows/commands.yml:34:9:37:6 | After Run Step | | .github/workflows/commands.yml:34:9:37:6 | Run Step | | .github/workflows/commands.yml:35:14:36:30 | command1 ; command2\n | +| .github/workflows/commands.yml:37:9:39:30 | After Run Step | | .github/workflows/commands.yml:37:9:39:30 | Run Step | | .github/workflows/commands.yml:38:14:39:30 | command3 \| command4\n | -| .github/workflows/controlcheck.yml:1:1:16:25 | enter on: | -| .github/workflows/controlcheck.yml:1:1:16:25 | exit on: | -| .github/workflows/controlcheck.yml:1:1:16:25 | exit on: (normal) | +| .github/workflows/controlcheck.yml:1:1:16:25 | After on: | +| .github/workflows/controlcheck.yml:1:1:16:25 | Entry | +| .github/workflows/controlcheck.yml:1:1:16:25 | Exit | +| .github/workflows/controlcheck.yml:1:1:16:25 | Normal Exit | | .github/workflows/controlcheck.yml:1:1:16:25 | on: | +| .github/workflows/controlcheck.yml:6:5:11:2 | After Job: test1 | | .github/workflows/controlcheck.yml:6:5:11:2 | Job: test1 | +| .github/workflows/controlcheck.yml:9:9:11:2 | After Run Step | | .github/workflows/controlcheck.yml:9:9:11:2 | Run Step | | .github/workflows/controlcheck.yml:10:14:10:25 | echo "test1" | +| .github/workflows/controlcheck.yml:12:5:16:25 | After Job: test2 | | .github/workflows/controlcheck.yml:12:5:16:25 | Job: test2 | +| .github/workflows/controlcheck.yml:15:9:16:25 | After Run Step | | .github/workflows/controlcheck.yml:15:9:16:25 | Run Step | | .github/workflows/controlcheck.yml:16:14:16:25 | echo "test2" | -| .github/workflows/expression_nodes.yml:1:1:21:47 | enter on: issue_comment | -| .github/workflows/expression_nodes.yml:1:1:21:47 | exit on: issue_comment | -| .github/workflows/expression_nodes.yml:1:1:21:47 | exit on: issue_comment (normal) | +| .github/workflows/expression_nodes.yml:1:1:21:47 | After on: issue_comment | +| .github/workflows/expression_nodes.yml:1:1:21:47 | Entry | +| .github/workflows/expression_nodes.yml:1:1:21:47 | Exit | +| .github/workflows/expression_nodes.yml:1:1:21:47 | Normal Exit | | .github/workflows/expression_nodes.yml:1:1:21:47 | on: issue_comment | +| .github/workflows/expression_nodes.yml:5:5:21:47 | After Job: echo-chamber | | .github/workflows/expression_nodes.yml:5:5:21:47 | Job: echo-chamber | +| .github/workflows/expression_nodes.yml:7:9:8:6 | After Run Step | | .github/workflows/expression_nodes.yml:7:9:8:6 | Run Step | | .github/workflows/expression_nodes.yml:7:14:7:58 | LINE 1echo '${{ github.event.comment.body }}' | | .github/workflows/expression_nodes.yml:7:27:7:58 | github.event.comment.body | +| .github/workflows/expression_nodes.yml:8:9:10:6 | After Run Step | | .github/workflows/expression_nodes.yml:8:9:10:6 | Run Step | | .github/workflows/expression_nodes.yml:8:14:9:57 | LINE 1 echo '${{ github.event.comment.body }}'\n | | .github/workflows/expression_nodes.yml:9:25:9:56 | github.event.comment.body | +| .github/workflows/expression_nodes.yml:10:9:13:6 | After Run Step | | .github/workflows/expression_nodes.yml:10:9:13:6 | Run Step | | .github/workflows/expression_nodes.yml:10:14:12:53 | LINE 1 echo '${{ github.event.comment.body }}'\nLINE 2 echo '${{github.event.issue.body}}'\n | | .github/workflows/expression_nodes.yml:11:25:11:56 | github.event.comment.body | | .github/workflows/expression_nodes.yml:12:24:12:51 | github.event.issue.body | +| .github/workflows/expression_nodes.yml:13:9:16:6 | After Run Step | | .github/workflows/expression_nodes.yml:13:9:16:6 | Run Step | | .github/workflows/expression_nodes.yml:13:14:15:46 | LINE 1 echo '${{ github.event.comment.body }}' echo '${{github.event.issue.body}}'\n | | .github/workflows/expression_nodes.yml:14:9:15:46 | github.event.comment.body | | .github/workflows/expression_nodes.yml:14:9:15:46 | github.event.issue.body | +| .github/workflows/expression_nodes.yml:16:9:20:6 | After Run Step | | .github/workflows/expression_nodes.yml:16:9:20:6 | Run Step | | .github/workflows/expression_nodes.yml:16:14:19:57 | LINE 1 echo '${{ github.event.comment.body }}'\nLINE 2 echo '${{github.event.issue.body}}'\nLINE 3 echo '${{ github.event.comment.body }}'\n | | .github/workflows/expression_nodes.yml:17:25:17:56 | github.event.comment.body | | .github/workflows/expression_nodes.yml:18:24:18:51 | github.event.issue.body | | .github/workflows/expression_nodes.yml:19:24:19:55 | github.event.comment.body | +| .github/workflows/expression_nodes.yml:20:9:21:47 | After Run Step | | .github/workflows/expression_nodes.yml:20:9:21:47 | Run Step | | .github/workflows/expression_nodes.yml:20:14:21:46 | LINE 1 echo '${{ github.event.comment.body }}' echo '${{github.event.issue.body}}' | | .github/workflows/expression_nodes.yml:20:14:21:46 | github.event.comment.body | | .github/workflows/expression_nodes.yml:20:14:21:46 | github.event.issue.body | -| .github/workflows/many_strings.yml:1:1:18:1211 | enter on: | -| .github/workflows/many_strings.yml:1:1:18:1211 | exit on: | -| .github/workflows/many_strings.yml:1:1:18:1211 | exit on: (normal) | +| .github/workflows/many_strings.yml:1:1:18:1211 | After on: | +| .github/workflows/many_strings.yml:1:1:18:1211 | Entry | +| .github/workflows/many_strings.yml:1:1:18:1211 | Exit | +| .github/workflows/many_strings.yml:1:1:18:1211 | Normal Exit | | .github/workflows/many_strings.yml:1:1:18:1211 | on: | +| .github/workflows/many_strings.yml:9:5:18:1211 | After Job: Test | | .github/workflows/many_strings.yml:9:5:18:1211 | Job: Test | +| .github/workflows/many_strings.yml:11:9:18:1211 | After Run Step | | .github/workflows/many_strings.yml:11:9:18:1211 | Run Step | | .github/workflows/many_strings.yml:11:14:18:1211 | # Avoid choking on large chunks of data containing quotes\necho '["string1", "string2", "string3", "string4", "string5", "string6", "string7", "string8", "string9", "string10", "string11", "string12", "string13", "string14", "string15", "string16", "string17", "string18", "string19", "string20", "string21", "string22", "string23", "string24", "string25", "string26", "string27", "string28", "string29", "string30", "string31", "string32", "string33", "string34", "string35", "string36", "string37", "string38", "string39", "string40", "string41", "string42", "string43", "string44", "string45", "string46", "string47", "string48", "string49", "string50", "string51", "string52", "string53", "string54", "string55", "string56", "string57", "string58", "string59", "string60", "string61", "string62", "string63", "string64", "string65", "string66", "string67", "string68", "string69", "string70", "string71", "string72", "string73", "string74", "string75", "string76", "string77", "string78", "string79", "string80", "string81", "string82", "string83", "string84", "string85", "string86", "string87", "string88", "string89", "string90", "string91", "string92", "string93", "string94", "string95", "string96", "string97", "string98", "string99", "string100"]'\necho "['string1', 'string2', 'string3', 'string4', 'string5', 'string6', 'string7', 'string8', 'string9', 'string10', 'string11', 'string12', 'string13', 'string14', 'string15', 'string16', 'string17', 'string18', 'string19', 'string20', 'string21', 'string22', 'string23', 'string24', 'string25', 'string26', 'string27', 'string28', 'string29', 'string30', 'string31', 'string32', 'string33', 'string34', 'string35', 'string36', 'string37', 'string38', 'string39', 'string40', 'string41', 'string42', 'string43', 'string44', 'string45', 'string46', 'string47', 'string48', 'string49', 'string50', 'string51', 'string52', 'string53', 'string54', 'string55', 'string56', 'string57', 'string58', 'string59', 'string60', 'string61', 'string62', 'string63', 'string64', 'string65', 'string66', 'string67', 'string68', 'string69', 'string70', 'string71', 'string72', 'string73', 'string74', 'string75', 'string76', 'string77', 'string78', 'string79', 'string80', 'string81', 'string82', 'string83', 'string84', 'string85', 'string86', 'string87', 'string88', 'string89', 'string90', 'string91', 'string92', 'string93', 'string94', 'string95', 'string96', 'string97', 'string98', 'string99', 'string100']"\n\n# Same as above but where each line has an unbalanced internal quote near the end\necho '["string1", "string2", "string3", "string4", "string5", "string6", "string7", "string8", "string9", "string10", "string11", "string12", "string13", "string14", "string15", "string16", "string17", "string18", "string19", "string20", "string21", "string22", "string23", "string24", "string25", "string26", "string27", "string28", "string29", "string30", "string31", "string32", "string33", "string34", "string35", "string36", "string37", "string38", "string39", "string40", "string41", "string42", "string43", "string44", "string45", "string46", "string47", "string48", "string49", "string50", "string51", "string52", "string53", "string54", "string55", "string56", "string57", "string58", "string59", "string60", "string61", "string62", "string63", "string64", "string65", "string66", "string67", "string68", "string69", "string70", "string71", "string72", "string73", "string74", "string75", "string76", "string77", "string78", "string79", "string80", "string81", "string82", "string83", "string84", "string85", "string86", "string87", "string88", "string89", "string90", "string91", "string92", "string93", "string94", "string95", "string96", "string97", "string98", "string99", "string100"]"'\necho "['string1', 'string2', 'string3', 'string4', 'string5', 'string6', 'string7', 'string8', 'string9', 'string10', 'string11', 'string12', 'string13', 'string14', 'string15', 'string16', 'string17', 'string18', 'string19', 'string20', 'string21', 'string22', 'string23', 'string24', 'string25', 'string26', 'string27', 'string28', 'string29', 'string30', 'string31', 'string32', 'string33', 'string34', 'string35', 'string36', 'string37', 'string38', 'string39', 'string40', 'string41', 'string42', 'string43', 'string44', 'string45', 'string46', 'string47', 'string48', 'string49', 'string50', 'string51', 'string52', 'string53', 'string54', 'string55', 'string56', 'string57', 'string58', 'string59', 'string60', 'string61', 'string62', 'string63', 'string64', 'string65', 'string66', 'string67', 'string68', 'string69', 'string70', 'string71', 'string72', 'string73', 'string74', 'string75', 'string76', 'string77', 'string78', 'string79', 'string80', 'string81', 'string82', 'string83', 'string84', 'string85', 'string86', 'string87', 'string88', 'string89', 'string90', 'string91', 'string92', 'string93', 'string94', 'string95', 'string96', 'string97', 'string98', 'string99', 'string100']'"\n | -| .github/workflows/multiline2.yml:1:1:89:35 | enter on: | -| .github/workflows/multiline2.yml:1:1:89:35 | exit on: | -| .github/workflows/multiline2.yml:1:1:89:35 | exit on: (normal) | +| .github/workflows/multiline2.yml:1:1:89:35 | After on: | +| .github/workflows/multiline2.yml:1:1:89:35 | Entry | +| .github/workflows/multiline2.yml:1:1:89:35 | Exit | +| .github/workflows/multiline2.yml:1:1:89:35 | Normal Exit | | .github/workflows/multiline2.yml:1:1:89:35 | on: | +| .github/workflows/multiline2.yml:9:5:89:35 | After Job: Test | | .github/workflows/multiline2.yml:9:5:89:35 | Job: Test | +| .github/workflows/multiline2.yml:11:9:15:6 | After Run Step | | .github/workflows/multiline2.yml:11:9:15:6 | Run Step | | .github/workflows/multiline2.yml:11:14:14:54 | echo "changelog< event.json\n ${{ toJson(github.event) }}\nEOF\n | | .github/workflows/multiline2.yml:32:13:32:39 | toJson(github.event) | +| .github/workflows/multiline2.yml:34:9:40:6 | After Run Step | | .github/workflows/multiline2.yml:34:9:40:6 | Run Step | | .github/workflows/multiline2.yml:35:14:39:14 | cat \| tee -a $GITHUB_ENV << EOL\n${ISSUE_BODY}\nFOO\nEOL\n | +| .github/workflows/multiline2.yml:40:9:46:6 | After Run Step | | .github/workflows/multiline2.yml:40:9:46:6 | Run Step | | .github/workflows/multiline2.yml:41:14:45:14 | cat > issue.txt << EOL\n${ISSUE_BODY}\nFOO\nEOL\n | +| .github/workflows/multiline2.yml:46:9:52:6 | After Run Step | | .github/workflows/multiline2.yml:46:9:52:6 | Run Step | | .github/workflows/multiline2.yml:47:14:51:14 | cat << EOL \| tee -a $GITHUB_ENV\n${ISSUE_BODY}\nFOO\nEOL\n | +| .github/workflows/multiline2.yml:52:9:58:6 | After Run Step | | .github/workflows/multiline2.yml:52:9:58:6 | Run Step | | .github/workflows/multiline2.yml:53:14:57:14 | cat < file.txt\nHello\nWorld\nEOF\n | +| .github/workflows/multiline2.yml:58:9:63:6 | After Run Step | | .github/workflows/multiline2.yml:58:9:63:6 | Run Step | | .github/workflows/multiline2.yml:59:14:62:14 | cat <<-EOF \| tee -a "$GITHUB_ENV"\necho "FOO=$TITLE"\nEOF\n | +| .github/workflows/multiline2.yml:63:9:66:6 | After Run Step | | .github/workflows/multiline2.yml:63:9:66:6 | Run Step | | .github/workflows/multiline2.yml:64:14:65:142 | echo REPO_NAME=$(cat issue.txt \| sed 's/\\\\r/\\\\n/g' \| grep -ioE '\\\\s*[a-z0-9_-]+/[a-z0-9_-]+\\\\s*$' \| tr -d ' ') \| tee -a $GITHUB_ENV\n | +| .github/workflows/multiline2.yml:66:9:71:6 | After Run Step | | .github/workflows/multiline2.yml:66:9:71:6 | Run Step | | .github/workflows/multiline2.yml:67:14:70:42 | echo "PR_TITLE<> $GITHUB_OUTPUT\necho -e "$FILTERED_CHANGELOG" >> $GITHUB_OUTPUT\necho "CHANGELOGEOF" >> $GITHUB_OUTPUT\n | +| .github/workflows/multiline.yml:15:9:20:6 | After Run Step | | .github/workflows/multiline.yml:15:9:20:6 | Run Step | | .github/workflows/multiline.yml:15:14:19:40 | EOF=$(dd if=/dev/urandom bs=15 count=1 status=none \| base64)\necho "status<<$EOF" >> $GITHUB_OUTPUT\necho "$(cat status.output.json)" >> $GITHUB_OUTPUT\necho "$EOF" >> $GITHUB_OUTPUT\n | +| .github/workflows/multiline.yml:20:9:24:6 | After Run Step | | .github/workflows/multiline.yml:20:9:24:6 | Run Step | | .github/workflows/multiline.yml:20:14:23:40 | echo "response<<$EOF" >> $GITHUB_OUTPUT\necho $output >> $GITHUB_OUTPUT\necho "$EOF" >> $GITHUB_OUTPUT\n | +| .github/workflows/multiline.yml:24:9:30:6 | After Run Step | | .github/workflows/multiline.yml:24:9:30:6 | Run Step | | .github/workflows/multiline.yml:24:14:29:29 | {\n echo 'JSON_RESPONSE<> "$GITHUB_ENV"\n | +| .github/workflows/multiline.yml:30:9:34:6 | After Run Step | | .github/workflows/multiline.yml:30:9:34:6 | Run Step | | .github/workflows/multiline.yml:30:14:33:14 | cat <<-"EOF" > event.json\n ${{ toJson(github.event) }}\nEOF\n | | .github/workflows/multiline.yml:32:13:32:39 | toJson(github.event) | +| .github/workflows/multiline.yml:34:9:40:6 | After Run Step | | .github/workflows/multiline.yml:34:9:40:6 | Run Step | | .github/workflows/multiline.yml:35:14:39:14 | cat >> $GITHUB_ENV << EOL\n${ISSUE_BODY}\nFOO\nEOL\n | +| .github/workflows/multiline.yml:40:9:46:6 | After Run Step | | .github/workflows/multiline.yml:40:9:46:6 | Run Step | | .github/workflows/multiline.yml:41:14:45:14 | cat > issue.txt << EOL\n${ISSUE_BODY}\nFOO\nEOL\n | +| .github/workflows/multiline.yml:46:9:52:6 | After Run Step | | .github/workflows/multiline.yml:46:9:52:6 | Run Step | | .github/workflows/multiline.yml:47:14:51:14 | cat << EOL >> $GITHUB_ENV\n${ISSUE_BODY}\nFOO\nEOL\n | +| .github/workflows/multiline.yml:52:9:58:6 | After Run Step | | .github/workflows/multiline.yml:52:9:58:6 | Run Step | | .github/workflows/multiline.yml:53:14:57:14 | cat < file.txt\nHello\nWorld\nEOF\n | +| .github/workflows/multiline.yml:58:9:63:6 | After Run Step | | .github/workflows/multiline.yml:58:9:63:6 | Run Step | | .github/workflows/multiline.yml:59:14:62:14 | cat <<-EOF >> "$GITHUB_ENV"\necho "FOO=$TITLE"\nEOF\n | +| .github/workflows/multiline.yml:63:9:66:6 | After Run Step | | .github/workflows/multiline.yml:63:9:66:6 | Run Step | | .github/workflows/multiline.yml:64:14:65:136 | echo REPO_NAME=$(cat issue.txt \| sed 's/\\\\r/\\\\n/g' \| grep -ioE '\\\\s*[a-z0-9_-]+/[a-z0-9_-]+\\\\s*$' \| tr -d ' ') >> $GITHUB_ENV\n | +| .github/workflows/multiline.yml:66:9:71:6 | After Run Step | | .github/workflows/multiline.yml:66:9:71:6 | Run Step | | .github/workflows/multiline.yml:67:14:70:36 | echo "PR_TITLE<> $GITHUB_ENV\necho "$TITLE" >> $GITHUB_ENV\necho "EOF" >> $GITHUB_ENV\n | +| .github/workflows/multiline.yml:71:9:78:6 | After Run Step | | .github/workflows/multiline.yml:71:9:78:6 | Run Step | | .github/workflows/multiline.yml:72:14:77:29 | {\n echo 'JSON_RESPONSE<> "$GITHUB_ENV"\n | +| .github/workflows/multiline.yml:78:9:85:6 | After Run Step | | .github/workflows/multiline.yml:78:9:85:6 | Run Step | | .github/workflows/multiline.yml:79:14:84:29 | {\n echo 'JSON_RESPONSE<> "$GITHUB_ENV"\n | +| .github/workflows/multiline.yml:85:9:89:29 | After Run Step | | .github/workflows/multiline.yml:85:9:89:29 | Run Step | | .github/workflows/multiline.yml:86:14:89:29 | {\n echo 'JSON_RESPONSE<> "$GITHUB_ENV"\n | -| .github/workflows/poisonable_steps.yml:1:1:46:111 | enter on: push | -| .github/workflows/poisonable_steps.yml:1:1:46:111 | exit on: push | -| .github/workflows/poisonable_steps.yml:1:1:46:111 | exit on: push (normal) | +| .github/workflows/poisonable_steps.yml:1:1:46:111 | After on: push | +| .github/workflows/poisonable_steps.yml:1:1:46:111 | Entry | +| .github/workflows/poisonable_steps.yml:1:1:46:111 | Exit | +| .github/workflows/poisonable_steps.yml:1:1:46:111 | Normal Exit | | .github/workflows/poisonable_steps.yml:1:1:46:111 | on: push | +| .github/workflows/poisonable_steps.yml:5:5:46:111 | After Job: local_commands | | .github/workflows/poisonable_steps.yml:5:5:46:111 | Job: local_commands | +| .github/workflows/poisonable_steps.yml:7:9:8:6 | After Run Step | | .github/workflows/poisonable_steps.yml:7:9:8:6 | Run Step | | .github/workflows/poisonable_steps.yml:7:14:7:30 | venv/bin/activate | +| .github/workflows/poisonable_steps.yml:8:9:13:6 | After Uses Step | | .github/workflows/poisonable_steps.yml:8:9:13:6 | Uses Step | | .github/workflows/poisonable_steps.yml:11:53:11:75 | github.workspace | +| .github/workflows/poisonable_steps.yml:13:9:14:6 | After Run Step | | .github/workflows/poisonable_steps.yml:13:9:14:6 | Run Step | | .github/workflows/poisonable_steps.yml:13:14:13:32 | . venv/bin/activate | +| .github/workflows/poisonable_steps.yml:14:9:15:6 | After Run Step | | .github/workflows/poisonable_steps.yml:14:9:15:6 | Run Step | | .github/workflows/poisonable_steps.yml:14:14:14:42 | echo foo; . venv/bin/activate | +| .github/workflows/poisonable_steps.yml:15:9:16:6 | After Run Step | | .github/workflows/poisonable_steps.yml:15:9:16:6 | Run Step | | .github/workflows/poisonable_steps.yml:15:14:15:41 | echo foo;. venv/bin/activate | +| .github/workflows/poisonable_steps.yml:16:9:17:6 | After Run Step | | .github/workflows/poisonable_steps.yml:16:9:17:6 | Run Step | | .github/workflows/poisonable_steps.yml:16:14:16:42 | echo foo \|. venv/bin/activate | +| .github/workflows/poisonable_steps.yml:17:9:18:6 | After Run Step | | .github/workflows/poisonable_steps.yml:17:9:18:6 | Run Step | | .github/workflows/poisonable_steps.yml:17:14:17:32 | ./venv/bin/activate | +| .github/workflows/poisonable_steps.yml:18:9:19:6 | After Run Step | | .github/workflows/poisonable_steps.yml:18:9:19:6 | Run Step | | .github/workflows/poisonable_steps.yml:18:14:18:36 | sh venv/bin/activate.sh | +| .github/workflows/poisonable_steps.yml:19:9:20:6 | After Run Step | | .github/workflows/poisonable_steps.yml:19:9:20:6 | Run Step | | .github/workflows/poisonable_steps.yml:19:14:19:44 | echo $(sh venv/bin/activate.sh) | +| .github/workflows/poisonable_steps.yml:20:9:21:6 | After Run Step | | .github/workflows/poisonable_steps.yml:20:9:21:6 | Run Step | | .github/workflows/poisonable_steps.yml:20:14:20:56 | echo foo; sh venv/bin/activate.sh; echo bar | +| .github/workflows/poisonable_steps.yml:21:9:22:6 | After Run Step | | .github/workflows/poisonable_steps.yml:21:9:22:6 | Run Step | | .github/workflows/poisonable_steps.yml:21:14:21:56 | echo foo \| sh venv/bin/activate.sh > output | +| .github/workflows/poisonable_steps.yml:22:9:23:6 | After Run Step | | .github/workflows/poisonable_steps.yml:22:9:23:6 | Run Step | | .github/workflows/poisonable_steps.yml:22:14:22:40 | python venv/bin/activate.py | +| .github/workflows/poisonable_steps.yml:23:9:24:6 | After Run Step | | .github/workflows/poisonable_steps.yml:23:9:24:6 | Run Step | | .github/workflows/poisonable_steps.yml:23:14:23:50 | echo foo; python venv/bin/activate.py | +| .github/workflows/poisonable_steps.yml:24:9:25:6 | After Run Step | | .github/workflows/poisonable_steps.yml:24:9:25:6 | Run Step | | .github/workflows/poisonable_steps.yml:24:14:24:29 | pnpm run test:ct | +| .github/workflows/poisonable_steps.yml:25:9:26:6 | After Run Step | | .github/workflows/poisonable_steps.yml:25:9:26:6 | Run Step | | .github/workflows/poisonable_steps.yml:25:14:25:73 | pip install nbformat && python scripts/generate_notebooks.py | +| .github/workflows/poisonable_steps.yml:26:9:27:6 | After Run Step | | .github/workflows/poisonable_steps.yml:26:9:27:6 | Run Step | | .github/workflows/poisonable_steps.yml:26:14:26:78 | python scripts/generate_theme.py --outfile js/storybook/theme.css | +| .github/workflows/poisonable_steps.yml:27:9:28:6 | After Run Step | | .github/workflows/poisonable_steps.yml:27:9:28:6 | Run Step | | .github/workflows/poisonable_steps.yml:27:14:27:76 | ruby scripts/generate_theme.rb --outfile js/storybook/theme.css | +| .github/workflows/poisonable_steps.yml:28:9:29:6 | After Run Step | | .github/workflows/poisonable_steps.yml:28:9:29:6 | Run Step | | .github/workflows/poisonable_steps.yml:28:14:28:92 | bundle run exec ruby scripts/generate_theme.rb --outfile js/storybook/theme.css | +| .github/workflows/poisonable_steps.yml:29:9:30:6 | After Run Step | | .github/workflows/poisonable_steps.yml:29:9:30:6 | Run Step | | .github/workflows/poisonable_steps.yml:29:14:29:42 | xvfb-run ./mvnw clean package | +| .github/workflows/poisonable_steps.yml:30:9:31:6 | After Run Step | | .github/workflows/poisonable_steps.yml:30:9:31:6 | Run Step | | .github/workflows/poisonable_steps.yml:30:14:30:46 | echo "foo" && npm i && echo "bar" | +| .github/workflows/poisonable_steps.yml:31:9:32:6 | After Run Step | | .github/workflows/poisonable_steps.yml:31:9:32:6 | Run Step | | .github/workflows/poisonable_steps.yml:31:14:31:44 | echo "foo" \| npm i \| echo "bar" | +| .github/workflows/poisonable_steps.yml:32:9:33:6 | After Run Step | | .github/workflows/poisonable_steps.yml:32:9:33:6 | Run Step | | .github/workflows/poisonable_steps.yml:32:14:32:44 | echo "foo" \| npm i \| echo "bar" | +| .github/workflows/poisonable_steps.yml:33:9:34:6 | After Run Step | | .github/workflows/poisonable_steps.yml:33:9:34:6 | Run Step | | .github/workflows/poisonable_steps.yml:33:14:33:35 | echo "foo `npm i` bar" | +| .github/workflows/poisonable_steps.yml:34:9:35:6 | After Run Step | | .github/workflows/poisonable_steps.yml:34:9:35:6 | Run Step | | .github/workflows/poisonable_steps.yml:34:14:34:52 | dotnet test foo/Tests.csproj -c Release | +| .github/workflows/poisonable_steps.yml:35:9:36:6 | After Run Step | | .github/workflows/poisonable_steps.yml:35:9:36:6 | Run Step | | .github/workflows/poisonable_steps.yml:35:14:35:26 | go run foo.go | +| .github/workflows/poisonable_steps.yml:36:9:37:6 | After Run Step | | .github/workflows/poisonable_steps.yml:36:9:37:6 | Run Step | | .github/workflows/poisonable_steps.yml:36:14:36:86 | sed -i "s\|git_branch = .*\|git_branch = \\"$GITHUB_HEAD_REF\\"\|" config.json | +| .github/workflows/poisonable_steps.yml:37:9:38:6 | After Run Step | | .github/workflows/poisonable_steps.yml:37:9:38:6 | Run Step | | .github/workflows/poisonable_steps.yml:37:14:37:51 | sed -f ./config.sed file.txt > foo.txt | +| .github/workflows/poisonable_steps.yml:38:9:39:6 | After Run Step | | .github/workflows/poisonable_steps.yml:38:9:39:6 | Run Step | | .github/workflows/poisonable_steps.yml:38:14:38:45 | sed -f config file.txt > foo.txt | +| .github/workflows/poisonable_steps.yml:39:9:40:6 | After Run Step | | .github/workflows/poisonable_steps.yml:39:9:40:6 | Run Step | | .github/workflows/poisonable_steps.yml:39:14:39:55 | echo "foo" \| awk -f ./config.awk > foo.txt | +| .github/workflows/poisonable_steps.yml:40:9:41:6 | After Run Step | | .github/workflows/poisonable_steps.yml:40:9:41:6 | Run Step | | .github/workflows/poisonable_steps.yml:40:14:40:73 | gcloud builds submit --quiet --substitutions="COMMIT_SHA=foo | +| .github/workflows/poisonable_steps.yml:41:9:42:6 | After Run Step | | .github/workflows/poisonable_steps.yml:41:9:42:6 | Run Step | | .github/workflows/poisonable_steps.yml:41:14:41:22 | ./foo/cmd | +| .github/workflows/poisonable_steps.yml:42:9:46:111 | After Run Step | | .github/workflows/poisonable_steps.yml:42:9:46:111 | Run Step | | .github/workflows/poisonable_steps.yml:42:14:46:111 | sed -e 's##TITLE#' \\\n -e 's##${{ env.sot_repo }}#' \\\n -e 's##${TITLE}#' \\\n .github/workflows/common-copybara.bara.sky.template > .github/workflows/common-copybara.bara.sky\n | | .github/workflows/poisonable_steps.yml:44:32:44:50 | env.sot_repo | -| .github/workflows/shell.yml:1:1:22:32 | enter on: push | -| .github/workflows/shell.yml:1:1:22:32 | exit on: push | -| .github/workflows/shell.yml:1:1:22:32 | exit on: push (normal) | +| .github/workflows/shell.yml:1:1:22:32 | After on: push | +| .github/workflows/shell.yml:1:1:22:32 | Entry | +| .github/workflows/shell.yml:1:1:22:32 | Exit | +| .github/workflows/shell.yml:1:1:22:32 | Normal Exit | | .github/workflows/shell.yml:1:1:22:32 | on: push | +| .github/workflows/shell.yml:5:5:9:2 | After Job: job1 | | .github/workflows/shell.yml:5:5:9:2 | Job: job1 | +| .github/workflows/shell.yml:7:9:9:2 | After Run Step | | .github/workflows/shell.yml:7:9:9:2 | Run Step | | .github/workflows/shell.yml:8:14:8:31 | Write-Output "foo" | +| .github/workflows/shell.yml:10:5:14:2 | After Job: job2 | | .github/workflows/shell.yml:10:5:14:2 | Job: job2 | +| .github/workflows/shell.yml:12:9:14:2 | After Run Step | | .github/workflows/shell.yml:12:9:14:2 | Run Step | | .github/workflows/shell.yml:12:14:12:23 | echo "foo" | +| .github/workflows/shell.yml:15:5:19:2 | After Job: job3 | | .github/workflows/shell.yml:15:5:19:2 | Job: job3 | +| .github/workflows/shell.yml:17:9:19:2 | After Run Step | | .github/workflows/shell.yml:17:9:19:2 | Run Step | | .github/workflows/shell.yml:18:14:18:23 | echo "foo" | +| .github/workflows/shell.yml:20:5:22:32 | After Job: job4 | | .github/workflows/shell.yml:20:5:22:32 | Job: job4 | +| .github/workflows/shell.yml:22:9:22:32 | After Run Step | | .github/workflows/shell.yml:22:9:22:32 | Run Step | | .github/workflows/shell.yml:22:14:22:31 | Write-Output "foo" | -| .github/workflows/test.yml:1:1:40:53 | enter on: push | -| .github/workflows/test.yml:1:1:40:53 | exit on: push | -| .github/workflows/test.yml:1:1:40:53 | exit on: push (normal) | +| .github/workflows/test.yml:1:1:40:53 | After on: push | +| .github/workflows/test.yml:1:1:40:53 | Entry | +| .github/workflows/test.yml:1:1:40:53 | Exit | +| .github/workflows/test.yml:1:1:40:53 | Normal Exit | | .github/workflows/test.yml:1:1:40:53 | on: push | +| .github/workflows/test.yml:5:5:31:2 | After Job: job1 | | .github/workflows/test.yml:5:5:31:2 | Job: job1 | +| .github/workflows/test.yml:8:7:10:4 | After Job outputs node | | .github/workflows/test.yml:8:7:10:4 | Job outputs node | | .github/workflows/test.yml:8:20:8:50 | steps.step.outputs.value | | .github/workflows/test.yml:11:9:15:6 | Uses Step | | .github/workflows/test.yml:15:9:19:6 | Uses Step: source | +| .github/workflows/test.yml:19:9:26:6 | After Uses Step: step | | .github/workflows/test.yml:19:9:26:6 | Uses Step: step | | .github/workflows/test.yml:23:20:23:64 | steps.source.outputs.all_changed_files | +| .github/workflows/test.yml:26:9:28:6 | After Run Step: simplesink1 | | .github/workflows/test.yml:26:9:28:6 | Run Step: simplesink1 | | .github/workflows/test.yml:27:14:27:63 | echo ${{ steps.source.outputs.all_changed_files }} | | .github/workflows/test.yml:27:20:27:64 | steps.source.outputs.all_changed_files | +| .github/workflows/test.yml:28:9:31:2 | After Run Step: simplesink2 | | .github/workflows/test.yml:28:9:31:2 | Run Step: simplesink2 | | .github/workflows/test.yml:29:14:29:54 | ${{ github.event.pull_request.head.ref }} | | .github/workflows/test.yml:29:15:29:55 | github.event.pull_request.head.ref | +| .github/workflows/test.yml:32:5:40:53 | After Job: job2 | | .github/workflows/test.yml:32:5:40:53 | Job: job2 | +| .github/workflows/test.yml:39:9:40:53 | After Run Step: sink | | .github/workflows/test.yml:39:9:40:53 | Run Step: sink | | .github/workflows/test.yml:40:14:40:52 | echo ${{needs.job1.outputs.job_output}} | | .github/workflows/test.yml:40:20:40:53 | needs.job1.outputs.job_output | +cfgCycles +cfgDeadEnds dfNodes | .github/workflows/commands.yml:9:5:31:2 | Job: local_commands | | .github/workflows/commands.yml:15:9:18:6 | Run Step | @@ -1621,6 +1736,59 @@ scopes | .github/workflows/poisonable_steps.yml:1:1:46:111 | on: push | | .github/workflows/shell.yml:1:1:22:32 | on: push | | .github/workflows/test.yml:1:1:40:53 | on: push | +workflowScopes +| .github/workflows/commands.yml:1:1:39:30 | on: push | +| .github/workflows/controlcheck.yml:1:1:16:25 | on: | +| .github/workflows/expression_nodes.yml:1:1:21:47 | on: issue_comment | +| .github/workflows/many_strings.yml:1:1:18:1211 | on: | +| .github/workflows/multiline2.yml:1:1:89:35 | on: | +| .github/workflows/multiline.yml:1:1:89:29 | on: | +| .github/workflows/poisonable_steps.yml:1:1:46:111 | on: push | +| .github/workflows/shell.yml:1:1:22:32 | on: push | +| .github/workflows/test.yml:1:1:40:53 | on: push | +compositeActionScopes +workflowCfgBounds +| .github/workflows/commands.yml:1:1:39:30 | on: push | 1 | 38 | +| .github/workflows/controlcheck.yml:1:1:16:25 | on: | 1 | 16 | +| .github/workflows/expression_nodes.yml:1:1:21:47 | on: issue_comment | 1 | 20 | +| .github/workflows/many_strings.yml:1:1:18:1211 | on: | 1 | 11 | +| .github/workflows/multiline2.yml:1:1:89:35 | on: | 1 | 86 | +| .github/workflows/multiline.yml:1:1:89:29 | on: | 1 | 86 | +| .github/workflows/poisonable_steps.yml:1:1:46:111 | on: push | 1 | 44 | +| .github/workflows/shell.yml:1:1:22:32 | on: push | 1 | 22 | +| .github/workflows/test.yml:1:1:40:53 | on: push | 1 | 40 | +workflowCfgNodes +| .github/workflows/commands.yml:1:1:39:30 | on: push | +| .github/workflows/controlcheck.yml:1:1:16:25 | on: | +| .github/workflows/expression_nodes.yml:1:1:21:47 | on: issue_comment | +| .github/workflows/many_strings.yml:1:1:18:1211 | on: | +| .github/workflows/multiline2.yml:1:1:89:35 | on: | +| .github/workflows/multiline.yml:1:1:89:29 | on: | +| .github/workflows/poisonable_steps.yml:1:1:46:111 | on: push | +| .github/workflows/shell.yml:1:1:22:32 | on: push | +| .github/workflows/test.yml:1:1:40:53 | on: push | +entryScopes +| .github/workflows/commands.yml:1:1:39:30 | Entry | .github/workflows/commands.yml:1:1:39:30 | on: push | +| .github/workflows/controlcheck.yml:1:1:16:25 | Entry | .github/workflows/controlcheck.yml:1:1:16:25 | on: | +| .github/workflows/expression_nodes.yml:1:1:21:47 | Entry | .github/workflows/expression_nodes.yml:1:1:21:47 | on: issue_comment | +| .github/workflows/many_strings.yml:1:1:18:1211 | Entry | .github/workflows/many_strings.yml:1:1:18:1211 | on: | +| .github/workflows/multiline2.yml:1:1:89:35 | Entry | .github/workflows/multiline2.yml:1:1:89:35 | on: | +| .github/workflows/multiline.yml:1:1:89:29 | Entry | .github/workflows/multiline.yml:1:1:89:29 | on: | +| .github/workflows/poisonable_steps.yml:1:1:46:111 | Entry | .github/workflows/poisonable_steps.yml:1:1:46:111 | on: push | +| .github/workflows/shell.yml:1:1:22:32 | Entry | .github/workflows/shell.yml:1:1:22:32 | on: push | +| .github/workflows/test.yml:1:1:40:53 | Entry | .github/workflows/test.yml:1:1:40:53 | on: push | +normalExitNodes +| .github/workflows/commands.yml:1:1:39:30 | Normal Exit | +| .github/workflows/controlcheck.yml:1:1:16:25 | Normal Exit | +| .github/workflows/expression_nodes.yml:1:1:21:47 | Normal Exit | +| .github/workflows/many_strings.yml:1:1:18:1211 | Normal Exit | +| .github/workflows/multiline2.yml:1:1:89:35 | Normal Exit | +| .github/workflows/multiline.yml:1:1:89:29 | Normal Exit | +| .github/workflows/poisonable_steps.yml:1:1:46:111 | Normal Exit | +| .github/workflows/shell.yml:1:1:22:32 | Normal Exit | +| .github/workflows/test.yml:1:1:40:53 | Normal Exit | +legacyNodeProperties +legacyCfgSplits sources | AvraamMavridis/files-changed-action | * | output.CHANGED_FILES | filename | manual | | AvraamMavridis/files-changed-action | * | output.CHANGED_FILES_EXTENSIONS | filename | manual | diff --git a/actions/ql/test/library-tests/basic/test.ql b/actions/ql/test/library-tests/basic/test.ql index e4c1d9e443d0..5e6749da288b 100644 --- a/actions/ql/test/library-tests/basic/test.ql +++ b/actions/ql/test/library-tests/basic/test.ql @@ -37,6 +37,13 @@ query predicate parentNodes(AstNode child, AstNode parent) { child.getParentNode query predicate cfgNodes(Cfg::Node n) { any() } +query predicate cfgCycles(Cfg::Node n) { n.getASuccessor+() = n } + +query predicate cfgDeadEnds(Cfg::Node n) { + not n instanceof Cfg::ExitNode and + not exists(n.getASuccessor()) +} + query predicate dfNodes(DataFlow::Node e) { any() } query predicate argumentNodes(DataFlow::ArgumentNode e) { any() } @@ -47,6 +54,48 @@ query predicate nodeLocations(DataFlow::Node n, Location l) { n.getLocation() = query predicate scopes(Cfg::CfgScope c) { any() } +query predicate workflowScopes(Cfg::WorkflowScope c) { any() } + +query predicate compositeActionScopes(Cfg::CompositeActionScope c) { any() } + +query predicate workflowCfgBounds(Workflow workflow, int entryLine, int exitLine) { + exists(AstNode entry, AstNode exit | + entry = Cfg::getAControlFlowEntryNode(workflow) and + exit = Cfg::getAControlFlowExitNode(workflow) and + entryLine = entry.getLocation().getStartLine() and + exitLine = exit.getLocation().getStartLine() + ) +} + +query predicate workflowCfgNodes(Cfg::AstCfgNode node) { + Cfg::forceCachingInSameStage() and + node.getAstNode() instanceof Workflow and + Cfg::getNodeCfgScope(node) = node.getAstNode() +} + +query predicate entryScopes(Cfg::EntryNode entry, Cfg::CfgScope scope) { scope = entry.getScope() } + +query predicate normalExitNodes(Cfg::AnnotatedExitNode exit) { exit.isNormal() } + +query predicate legacyNodeProperties( + Cfg::Node node, Cfg::SuccessorType successorType, string property +) { + node = node.getASuccessor(successorType) and property = "successor" + or + node = node.getAPredecessor(successorType) and property = "predecessor" + or + node.isCondition() and property = "condition" + or + node.isJoin() and property = "join" + or + node.isBranch() and property = "branch" +} + +query predicate legacyCfgSplits(Cfg::AstCfgNode node) { + exists(node.getSplitsString()) or + exists(node.getASplit()) +} + query predicate sources(string action, string version, string output, string kind, string provenance) { actionsSourceModel(action, version, output, kind, provenance) } diff --git a/actions/ql/test/query-tests/Security/CWE-078/CONSISTENCY/CfgConsistency.expected b/actions/ql/test/query-tests/Security/CWE-078/CONSISTENCY/CfgConsistency.expected new file mode 100644 index 000000000000..ddee66dafee9 --- /dev/null +++ b/actions/ql/test/query-tests/Security/CWE-078/CONSISTENCY/CfgConsistency.expected @@ -0,0 +1,7 @@ +consistencyOverview +| multipleSuccessors | 4 | +multipleSuccessors +| .github/workflows/test1.yml:20:16:20:130 | github.event_name == 'workflow_dispatch' && github.event.inputs.action \|\| github.event.client_payload.action | successor | .github/workflows/test1.yml:23:14:27:13 | if [[ "${ACTION}" != "promote" && "${ACTION}" != "rollback" ]]; then\n echo "Invalid action: ${ACTION}"\n exit 1\nfi\n | +| .github/workflows/test1.yml:20:16:20:130 | github.event_name == 'workflow_dispatch' && github.event.inputs.action \|\| github.event.client_payload.action | successor | .github/workflows/test1.yml:29:9:31:6 | After Uses Step | +| .github/workflows/test1.yml:20:16:20:130 | github.event_name == 'workflow_dispatch' && github.event.inputs.action \|\| github.event.client_payload.action | successor | .github/workflows/test1.yml:37:32:37:66 | secrets.DAGGER_CLOUD_TOKEN_2 | +| .github/workflows/test1.yml:20:16:20:130 | github.event_name == 'workflow_dispatch' && github.event.inputs.action \|\| github.event.client_payload.action | successor | .github/workflows/test1.yml:53:32:53:66 | secrets.DAGGER_CLOUD_TOKEN_2 | diff --git a/actions/ql/test/query-tests/Security/CWE-088/CONSISTENCY/CfgConsistency.expected b/actions/ql/test/query-tests/Security/CWE-088/CONSISTENCY/CfgConsistency.expected new file mode 100644 index 000000000000..49fcd64e161b --- /dev/null +++ b/actions/ql/test/query-tests/Security/CWE-088/CONSISTENCY/CfgConsistency.expected @@ -0,0 +1,20 @@ +consistencyOverview +| multipleSuccessors | 17 | +multipleSuccessors +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:15:17:15:57 | github.event.pull_request.head.ref | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:16:14:18:32 | # NOT VULNERABLE\necho "s/FOO/$TITLE/g"\n | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:19:14:21:31 | # VULNERABLE\nsed "s/FOO/$TITLE/g"\n | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:22:14:24:50 | # VULNERABLE\necho "foo" \| sed "s/FOO/$TITLE/g" > bar\n | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:25:14:27:58 | # VULNERABLE\necho $(echo "foo" \| sed "s/FOO/$TITLE/g" > bar)\n | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:28:14:30:31 | # VULNERABLE\nawk "BEGIN {$TITLE}"\n | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:31:14:33:84 | # VULNERABLE\nsed -i "s/git_branch = .*/git_branch = \\"$GITHUB_HEAD_REF\\"/" config.json\n | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:34:14:36:84 | # VULNERABLE\nsed -i "s\|git_branch = .*\|git_branch = \\"$GITHUB_HEAD_REF\\"\|" config.json\n | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:37:14:42:111 | # VULNERABLE\nsed -e 's##${TITLE}#' \\\n -e 's##${{ env.sot_repo }}#' \\\n -e 's##TITLE#' \\\n .github/workflows/common-copybara.bara.sky.template > .github/workflows/common-copybara.bara.sky\n | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:43:14:48:111 | # VULNERABLE\nsed -e 's##TITLE#' \\\n -e 's##${{ env.sot_repo }}#' \\\n -e 's##${TITLE}#' \\\n .github/workflows/common-copybara.bara.sky.template > .github/workflows/common-copybara.bara.sky\n | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:49:14:52:41 | # VULNERABLE\nBODY=$(git log --format=%s)\nsed "s/FOO/$BODY/g" > /tmp/foo\n | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:53:14:56:41 | # VULNERABLE\nBODY=$(git diff --name-only HEAD)\nsed "s/FOO/$BODY/g" > /tmp/foo\n | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:57:14:60:41 | # VULNERABLE\nBODY=$(git diff --name-only HEAD )\nsed "s/FOO/$BODY/g" > /tmp/foo\n | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:61:14:64:41 | # VULNERABLE\nBODY=$(git diff --name-only HEAD^ \| xargs)\nsed "s/FOO/$BODY/g" > /tmp/foo\n | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:65:14:67:67 | # NOT VULNERABLE\necho "value=$(git log -1 --pretty=%s)" >> $GITHUB_OUTPUT\n | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:68:14:70:33 | # NOT VULNERABLE\ngit log -1 --pretty=%s\n | +| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:71:14:74:41 | # NOT VULNERABLE\nBODY=$(git log --format=%s)\nsed -E 's/\\s+/\\n/g' <<<"$BODY"\n | diff --git a/actions/ql/test/query-tests/Security/CWE-094/CONSISTENCY/CfgConsistency.expected b/actions/ql/test/query-tests/Security/CWE-094/CONSISTENCY/CfgConsistency.expected new file mode 100644 index 000000000000..e5b7819f54a4 --- /dev/null +++ b/actions/ql/test/query-tests/Security/CWE-094/CONSISTENCY/CfgConsistency.expected @@ -0,0 +1,71 @@ +consistencyOverview +| multipleSuccessors | 68 | +multipleSuccessors +| .github/workflows/cross1.yml:18:20:18:47 | github.event.issue.body | successor | .github/workflows/cross1.yml:22:9:25:6 | After Uses Step | +| .github/workflows/cross1.yml:18:20:18:47 | github.event.issue.body | successor | .github/workflows/cross1.yml:29:18:29:46 | github.event.issue.title | +| .github/workflows/cross1.yml:18:20:18:47 | github.event.issue.body | successor | .github/workflows/cross1.yml:36:14:38:17 | echo "Bad Issue Title Format"\nexit 1\n | +| .github/workflows/cross1.yml:18:20:18:47 | github.event.issue.body | successor | .github/workflows/cross1.yml:46:21:46:40 | env.ISSUE_TITLE | +| .github/workflows/cross1.yml:18:20:18:47 | github.event.issue.body | successor | .github/workflows/cross1.yml:54:32:54:76 | steps.remove_quotations.outputs.replaced | +| .github/workflows/cross1.yml:18:20:18:47 | github.event.issue.body | successor | .github/workflows/cross1.yml:62:14:64:3 | curl -v -u admin:${{ secrets.DYNAMOBOTTOKEN }} -d '{"labels": ["${{env.content_analysis_response}}"]}' ${{ github.event.issue.url }}/labels\n | +| .github/workflows/cross1.yml:83:22:84:86 | github.actor | successor | .github/workflows/cross1.yml:112:9:115:6 | After Uses Step | +| .github/workflows/cross1.yml:83:22:84:86 | github.actor | successor | .github/workflows/cross1.yml:117:24:117:51 | github.event.issue.body | +| .github/workflows/cross1.yml:83:22:84:86 | github.actor | successor | .github/workflows/cross1.yml:130:24:130:70 | steps.remove_quotations.outputs.replaced | +| .github/workflows/cross1.yml:83:22:84:86 | github.actor | successor | .github/workflows/cross1.yml:137:14:140:7 | curl -v -u admin:${{ secrets.GITHUB_TOKEN }} -d '{"body": "${{env.comment_intro}} ${{env.close_issue_comment}} ${{env.info_needed}}"}' ${{ github.event.issue.url }}/comments\ncurl -v -u admin:${{ secrets.GITHUB_TOKEN }} -X PATCH -d '{"state": "closed"}' ${{ github.event.issue.url }}\n | +| .github/workflows/cross1.yml:83:22:84:86 | github.actor | successor | .github/workflows/cross1.yml:144:14:147:7 | curl -v -u admin:${{ secrets.GITHUB_TOKEN }} -d '{"labels": ["${{env.issue_label}}"]}' ${{ github.event.issue.url }}/labels\ncurl -v -u admin:${{ secrets.GITHUB_TOKEN }} -d '{"body": "${{env.comment_intro}} ${{env.needs_more_info_comment}} ${{env.specific_info}} ${{env.analysis_response}}.\\n\\n${{env.info_needed}}"}' ${{ github.event.issue.url }}/comments\n | +| .github/workflows/cross1.yml:83:22:84:86 | github.actor | successor | .github/workflows/cross1.yml:151:14:154:7 | echo urldecode ${{env.issue_label}}\ncurl -v -u admin:${{ secrets.GITHUB_TOKEN }} -X DELETE ${{ github.event.issue.url }}/labels/$(echo -ne "${{env.issue_label}}" \| xxd -plain \| tr -d '\\n' \| sed 's/\\(..\\)/%\\1/g')\n | +| .github/workflows/cross1.yml:83:22:84:86 | github.actor | successor | .github/workflows/cross1.yml:158:14:161:3 | curl -v -u admin:${{ secrets.GITHUB_TOKEN }} -d '{"body": "${{env.greetings_comment}}"}' ${{ github.event.issue.url }}/comments\n | +| .github/workflows/cross3.yml:18:26:18:48 | github.event.after | successor | .github/workflows/cross3.yml:22:7:27:4 | After Uses Step | +| .github/workflows/cross3.yml:18:26:18:48 | github.event.after | successor | .github/workflows/cross3.yml:32:18:32:53 | github.event.commits[0].message | +| .github/workflows/cross3.yml:18:26:18:48 | github.event.after | successor | .github/workflows/cross3.yml:39:31:39:75 | steps.remove_quotations.outputs.replaced | +| .github/workflows/cross3.yml:18:26:18:48 | github.event.after | successor | .github/workflows/cross3.yml:47:12:53:109 | git config user.name "${{env.user_name}}"\ngit fetch --all\ngit checkout -b ${{env.auto_branch}} origin/${{env.destination_branch}}\ngit cherry-pick -x ${{github.event.after}} --strategy-option theirs\ngit push -u origin ${{env.auto_branch}}\nhub pull-request -b "${{env.destination_branch}}" -h "${{env.auto_branch}}" -m "${{env.pr_message}}"\n | +| .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:34:87:34:112 | github.event.number | successor | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:36:14:36:39 | echo "${{ inputs.taint }}" | +| .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:34:87:34:112 | github.event.number | successor | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:39:17:39:57 | github.event.pull_request.head.ref | +| .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:34:87:34:112 | github.event.number | successor | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:44:19:44:56 | github.event.pull_request.title | +| .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:34:87:34:112 | github.event.number | successor | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:81:9:86:6 | After Uses Step | +| .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:34:87:34:112 | github.event.number | successor | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:88:14:95:19 | npm ci\nnpx prettier --write ${{ env.file }}\ngit config user.name github-actions[bot]\ngit config user.email github-actions[bot]@users.noreply.github.com\ngit add ${{ env.file }}\ngit commit -m "update ${{ env.file }}"\ngit push\n | +| .github/workflows/interpolation.yml:41:7:41:41 | github.event.inputs.string-b | successor | .github/workflows/interpolation.yml:49:12:51:43 | echo "region=region" >> $GITHUB_OUTPUT\necho "zone=zone" >> $GITHUB_OUTPUT\n | +| .github/workflows/interpolation.yml:41:7:41:41 | github.event.inputs.string-b | successor | .github/workflows/interpolation.yml:56:20:56:54 | github.event.inputs.choice-a | +| .github/workflows/issues.yaml:10:17:10:47 | github.event.issue.title | successor | .github/workflows/issues.yaml:13:12:13:49 | echo '${{ github.event.issue.title }}' | +| .github/workflows/issues.yaml:10:17:10:47 | github.event.issue.title | successor | .github/workflows/issues.yaml:14:12:14:48 | echo '${{ github.event.issue.body }}' | +| .github/workflows/issues.yaml:10:17:10:47 | github.event.issue.title | successor | .github/workflows/issues.yaml:15:12:15:39 | echo '${{ env.global_env }}' | +| .github/workflows/issues.yaml:10:17:10:47 | github.event.issue.title | successor | .github/workflows/issues.yaml:16:12:16:33 | echo '${{ env.test }}' | +| .github/workflows/issues.yaml:10:17:10:47 | github.event.issue.title | successor | .github/workflows/issues.yaml:17:12:17:36 | echo '${{ env.job_env }}' | +| .github/workflows/issues.yaml:10:17:10:47 | github.event.issue.title | successor | .github/workflows/issues.yaml:18:12:18:37 | echo '${{ env.step_env }}' | +| .github/workflows/level0.yml:31:38:31:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_FRIES | successor | .github/workflows/level0.yml:33:9:36:6 | After Uses Step | +| .github/workflows/level0.yml:31:38:31:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_FRIES | successor | .github/workflows/level0.yml:36:9:39:6 | After Uses Step | +| .github/workflows/level0.yml:31:38:31:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_FRIES | successor | .github/workflows/level0.yml:41:14:50:13 | echo "Checking issue body for profanities..."\nPROFANITIES_LIST="bad\|disguting\|horrible"\nif echo "${{ github.event.issue.body }}" \| grep -qiE "$PROFANITIES_LIST"; then\n echo "Profanity detected in issue body. Please clean up the language."\n exit 1\nelse\n echo "No profanities found in issue body."\n exit 0\nfi\n | +| .github/workflows/level0.yml:60:40:60:84 | secrets.FLAG_GRAVY_OVERFLOW_L0_CHEDDAR | successor | .github/workflows/level0.yml:62:9:65:6 | After Uses Step | +| .github/workflows/level0.yml:60:40:60:84 | secrets.FLAG_GRAVY_OVERFLOW_L0_CHEDDAR | successor | .github/workflows/level0.yml:69:35:69:66 | github.event.comment.body | +| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:96:9:99:6 | After Uses Step | +| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:102:17:102:57 | github.event.pull_request.head.sha | +| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:103:9:107:6 | After Uses Step | +| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:107:14:110:20 | npm install\nnpm run lint\nnpm start\n | +| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:122:9:125:6 | After Uses Step | +| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:128:17:128:57 | github.event.pull_request.head.sha | +| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:129:9:133:6 | After Uses Step | +| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:133:14:135:23 | npm install\nnpm run lint\n | +| .github/workflows/level1.yml:25:51:25:106 | secrets.FLAG_GRAVY_OVERFLOW_L1_TOPPINGS_FOR_REALZ | successor | .github/workflows/level1.yml:27:9:30:6 | After Uses Step | +| .github/workflows/level1.yml:25:51:25:106 | secrets.FLAG_GRAVY_OVERFLOW_L1_TOPPINGS_FOR_REALZ | successor | .github/workflows/level1.yml:32:24:32:50 | secrets.GITHUB_TOKEN | +| .github/workflows/level1.yml:25:51:25:106 | secrets.FLAG_GRAVY_OVERFLOW_L1_TOPPINGS_FOR_REALZ | successor | .github/workflows/level1.yml:36:14:37:160 | echo "Lint ran for branch ${{ github.event.workflow_run.head_branch }} in a PR from ${{ github.actor }}. Please check the logs for more information."\n | +| .github/workflows/reusable-workflow-1.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-1.yml:36:14:36:39 | echo "${{ inputs.taint }}" | +| .github/workflows/reusable-workflow-1.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-1.yml:39:17:39:57 | github.event.pull_request.head.ref | +| .github/workflows/reusable-workflow-1.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-1.yml:44:19:44:56 | github.event.pull_request.title | +| .github/workflows/reusable-workflow-1.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-1.yml:81:9:86:6 | After Uses Step | +| .github/workflows/reusable-workflow-1.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-1.yml:88:14:95:19 | npm ci\nnpx prettier --write ${{ env.file }}\ngit config user.name github-actions[bot]\ngit config user.email github-actions[bot]@users.noreply.github.com\ngit add ${{ env.file }}\ngit commit -m "update ${{ env.file }}"\ngit push\n | +| .github/workflows/reusable-workflow-2.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-2.yml:36:14:36:39 | echo "${{ inputs.taint }}" | +| .github/workflows/reusable-workflow-2.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-2.yml:39:17:39:57 | github.event.pull_request.head.ref | +| .github/workflows/reusable-workflow-2.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-2.yml:44:19:44:56 | github.event.pull_request.title | +| .github/workflows/reusable-workflow-2.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-2.yml:81:9:86:6 | After Uses Step | +| .github/workflows/reusable-workflow-2.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-2.yml:88:14:95:19 | npm ci\nnpx prettier --write ${{ env.file }}\ngit config user.name github-actions[bot]\ngit config user.email github-actions[bot]@users.noreply.github.com\ngit add ${{ env.file }}\ngit commit -m "update ${{ env.file }}"\ngit push\n | +| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:17:9:25:6 | After Uses Step: changed | +| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:26:14:28:61 | echo changed: ${{ steps.changed.outputs.locale_files }}\necho changed: ${{ steps.changed.outputs.changes }}\n | +| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:29:9:37:6 | After Uses Step: changed2 | +| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:38:14:40:62 | echo changed:${{ steps.changed2.outputs.locale_files }}\necho changed: ${{ steps.changed2.outputs.changes }}\n | +| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:41:9:49:6 | After Uses Step: changed3 | +| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:50:14:52:62 | echo changed:${{ steps.changed3.outputs.locale_files }}\necho changed: ${{ steps.changed3.outputs.changes }}\n | +| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:53:9:61:6 | After Uses Step: changed4 | +| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:62:14:64:62 | echo changed:${{ steps.changed4.outputs.locale_files }}\necho changed: ${{ steps.changed4.outputs.changes }}\n | +| .github/workflows/test26.yml:11:14:11:32 | github.token | successor | .github/workflows/test26.yml:20:11:20:140 | echo "body=$(gh issue view ${{ inputs.issue_number }} --repo ${{ github.repository }} --json body --jq '.body')" >> $GITHUB_OUTPUT | +| .github/workflows/test26.yml:11:14:11:32 | github.token | successor | .github/workflows/test26.yml:26:18:26:58 | steps.read_issue_body.outputs.body | +| .github/workflows/test26.yml:11:14:11:32 | github.token | successor | .github/workflows/test26.yml:28:14:28:49 | echo ${{ steps.parse.outputs.data }} | +| .github/workflows/test26.yml:11:14:11:32 | github.token | successor | .github/workflows/test26.yml:29:14:29:57 | echo ${{ toJSON(steps.parse.outputs.data) }} | diff --git a/actions/ql/test/query-tests/Security/CWE-829/CONSISTENCY/CfgConsistency.expected b/actions/ql/test/query-tests/Security/CWE-829/CONSISTENCY/CfgConsistency.expected new file mode 100644 index 000000000000..5291875efcee --- /dev/null +++ b/actions/ql/test/query-tests/Security/CWE-829/CONSISTENCY/CfgConsistency.expected @@ -0,0 +1,72 @@ +consistencyOverview +| multipleSuccessors | 69 | +multipleSuccessors +| .github/workflows/level0.yml:31:38:31:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_FRIES | successor | .github/workflows/level0.yml:33:9:36:6 | After Uses Step | +| .github/workflows/level0.yml:31:38:31:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_FRIES | successor | .github/workflows/level0.yml:36:9:39:6 | After Uses Step | +| .github/workflows/level0.yml:31:38:31:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_FRIES | successor | .github/workflows/level0.yml:41:14:50:13 | echo "Checking issue body for profanities..."\nPROFANITIES_LIST="bad\|disguting\|horrible"\nif echo "${{ github.event.issue.body }}" \| grep -qiE "$PROFANITIES_LIST"; then\n echo "Profanity detected in issue body. Please clean up the language."\n exit 1\nelse\n echo "No profanities found in issue body."\n exit 0\nfi\n | +| .github/workflows/level0.yml:60:40:60:84 | secrets.FLAG_GRAVY_OVERFLOW_L0_CHEDDAR | successor | .github/workflows/level0.yml:62:9:65:6 | After Uses Step | +| .github/workflows/level0.yml:60:40:60:84 | secrets.FLAG_GRAVY_OVERFLOW_L0_CHEDDAR | successor | .github/workflows/level0.yml:69:35:69:66 | github.event.comment.body | +| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:96:9:99:6 | After Uses Step | +| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:102:17:102:57 | github.event.pull_request.head.sha | +| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:103:9:107:6 | After Uses Step | +| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:107:14:110:20 | npm install\nnpm run lint\nnpm start\n | +| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:122:9:125:6 | After Uses Step | +| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:128:17:128:57 | github.event.pull_request.head.sha | +| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:129:9:133:6 | After Uses Step | +| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:133:14:135:23 | npm install\nnpm run lint\n | +| .github/workflows/mend.yml:7:13:7:60 | secrets.API_KEY != '' && secrets.API_KEY | successor | .github/workflows/mend.yml:15:14:20:13 | if [[ "${{ github.event_name }}" == "pull_request_target" ]]; then\n echo "ref=${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT\nelse\n echo "ref=${{ github.ref }}" >> $GITHUB_OUTPUT\nfi\n | +| .github/workflows/mend.yml:7:13:7:60 | secrets.API_KEY != '' && secrets.API_KEY | successor | .github/workflows/mend.yml:27:17:27:48 | steps.set_ref.outputs.ref | +| .github/workflows/mend.yml:7:13:7:60 | secrets.API_KEY != '' && secrets.API_KEY | successor | .github/workflows/mend.yml:29:9:33:28 | After Uses Step | +| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:22:27:22:58 | github.event.issue.number | +| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:24:9:27:6 | After Uses Step | +| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:27:9:33:6 | After Uses Step | +| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:34:14:34:25 | pnpm install | +| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:37:14:37:27 | pnpm run build | +| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:43:21:43:52 | github.event.comment.body | +| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:51:14:57:44 | result=$(pnpm run --silent benchmark ${{ steps.bench-command.outputs.bench }})\nprocessed=$(node ./benchmark/ci-helper.js "$result")\necho "BENCH_RESULT<> $GITHUB_OUTPUT\necho "### PR Benchmark" >> $GITHUB_OUTPUT\necho "$processed" >> $GITHUB_OUTPUT\necho "BENCHEOF" >> $GITHUB_OUTPUT\n | +| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:59:14:59:29 | python2.7 foo.py | +| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:60:14:60:36 | pip install --no-deps . | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:44:14:45:50 | echo "$SUMMARY" >> $GITHUB_STEP_SUMMARY\n | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:47:9:49:6 | After Uses Step | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:53:20:53:54 | secrets.JP_LAUNCH_CONTROL_ID | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:61:17:61:57 | github.event.workflow_run.head_sha | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:75:9:77:6 | After Uses Step | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:81:20:81:54 | secrets.JP_LAUNCH_CONTROL_ID | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:87:16:87:55 | needs.setup.outputs.upgrade_check | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:106:9:108:6 | After Uses Step | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:111:19:111:37 | github.token | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:145:20:145:54 | secrets.JP_LAUNCH_CONTROL_ID | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:151:16:151:55 | needs.setup.outputs.upgrade_check | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:167:9:169:6 | After Uses Step | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:173:20:173:54 | secrets.JP_LAUNCH_CONTROL_ID | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:179:16:179:55 | needs.setup.outputs.upgrade_check | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:218:9:221:6 | After Uses Step | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:223:17:223:63 | github.event.workflow_run.head_commit.id | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:233:20:233:54 | secrets.JP_LAUNCH_CONTROL_ID | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:239:16:239:55 | needs.setup.outputs.upgrade_check | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:250:19:250:37 | github.token | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:271:14:271:60 | trunk/.github/files/test-plugin-update/setup.sh | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:275:14:275:67 | trunk/.github/files/test-plugin-update/prepare-zips.sh | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:279:14:279:59 | trunk/.github/files/test-plugin-update/test.sh | +| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:285:16:285:55 | needs.setup.outputs.upgrade_check | +| .github/workflows/test19.yml:14:22:14:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test19.yml:20:15:20:55 | github.event.pull_request.head.ref | +| .github/workflows/test19.yml:14:22:14:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test19.yml:21:12:22:14 | ./cmd\n | +| .github/workflows/test20.yml:14:22:14:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test20.yml:20:15:20:55 | github.event.pull_request.head.sha | +| .github/workflows/test20.yml:14:22:14:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test20.yml:21:12:22:14 | ./cmd\n | +| .github/workflows/untrusted_checkout.yml:21:14:21:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout.yml:25:17:25:31 | env.HEAD | +| .github/workflows/untrusted_checkout.yml:21:14:21:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout.yml:26:9:30:6 | After Uses Step | +| .github/workflows/untrusted_checkout.yml:21:14:21:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout.yml:30:14:32:23 | npm install\nnpm run lint\n | +| .github/workflows/untrusted_checkout_5.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_5.yml:13:17:13:57 | github.event.pull_request.head.sha | +| .github/workflows/untrusted_checkout_5.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_5.yml:16:17:16:31 | env.HEAD | +| .github/workflows/untrusted_checkout_5.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_5.yml:17:9:21:6 | After Uses Step | +| .github/workflows/untrusted_checkout_5.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_5.yml:21:14:23:23 | npm install\nnpm run lint\n | +| .github/workflows/untrusted_checkout_6.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_6.yml:13:17:13:57 | github.event.pull_request.head.sha | +| .github/workflows/untrusted_checkout_6.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_6.yml:16:17:16:31 | env.HEAD | +| .github/workflows/untrusted_checkout_6.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_6.yml:17:9:21:6 | After Uses Step | +| .github/workflows/untrusted_checkout_6.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_6.yml:21:14:23:23 | npm install\nnpm run lint\n | +| .github/workflows/workflow_run_untrusted_checkout.yml:11:14:11:54 | github.event.workflow_run.head.sha | successor | .github/workflows/workflow_run_untrusted_checkout.yml:15:17:15:57 | github.event.workflow_run.head.sha | +| .github/workflows/workflow_run_untrusted_checkout.yml:11:14:11:54 | github.event.workflow_run.head.sha | successor | .github/workflows/workflow_run_untrusted_checkout.yml:18:17:18:31 | env.HEAD | +| .github/workflows/workflow_run_untrusted_checkout_2.yml:11:14:11:54 | github.event.workflow_run.head.sha | successor | .github/workflows/workflow_run_untrusted_checkout_2.yml:15:17:15:57 | github.event.workflow_run.head.sha | +| .github/workflows/workflow_run_untrusted_checkout_2.yml:11:14:11:54 | github.event.workflow_run.head.sha | successor | .github/workflows/workflow_run_untrusted_checkout_2.yml:18:17:18:31 | env.HEAD | +| .github/workflows/workflow_run_untrusted_checkout_3.yml:11:14:11:54 | github.event.workflow_run.head.sha | successor | .github/workflows/workflow_run_untrusted_checkout_3.yml:15:17:15:57 | github.event.workflow_run.head.sha | +| .github/workflows/workflow_run_untrusted_checkout_3.yml:11:14:11:54 | github.event.workflow_run.head.sha | successor | .github/workflows/workflow_run_untrusted_checkout_3.yml:18:17:18:31 | env.HEAD | From 95efef371e1ba1859a3433cbf0bd713517e15d48 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 29 Sep 2026 16:27:35 +0100 Subject: [PATCH 2/4] Actions: model environment expressions at declarations Attach environment expressions once to their declaring workflow, job, or step so shared CFG nodes do not acquire multiple parents or form cycles. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../actions/controlflow/internal/Cfg.qll | 22 +++--- .../.github/workflows/test.yml | 21 ++++++ .../cfg-environment/test.expected | 7 ++ .../library-tests/cfg-environment/test.ql | 19 +++++ .../CONSISTENCY/CfgConsistency.expected | 7 -- .../CONSISTENCY/CfgConsistency.expected | 20 ------ .../CONSISTENCY/CfgConsistency.expected | 71 ------------------ .../CONSISTENCY/CfgConsistency.expected | 72 ------------------- 8 files changed, 59 insertions(+), 180 deletions(-) create mode 100644 actions/ql/test/library-tests/cfg-environment/.github/workflows/test.yml create mode 100644 actions/ql/test/library-tests/cfg-environment/test.expected create mode 100644 actions/ql/test/library-tests/cfg-environment/test.ql diff --git a/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll b/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll index 72e56dc6fc2f..2fabd484ba22 100644 --- a/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll +++ b/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll @@ -6,7 +6,17 @@ private import codeql.util.Void private class ActionsAstNode = AstNode; module CfgImpl { + private predicate isDeclaredEnvExpr(AstNode parent, AstNode child) { + exists(Workflow workflow | parent = workflow and child = workflow.getEnv().getAnEnvVarExpr()) + or + exists(Job job | parent = job and child = job.getEnv().getAnEnvVarExpr()) + or + exists(Step step | parent = step and child = step.getEnv().getAnEnvVarExpr()) + } + private predicate isCfgChild(AstNode parent, AstNode child) { + isDeclaredEnvExpr(parent, child) + or exists(CompositeAction action | parent = action and (child = action.getAnInput() or child = action.getOutputs() or child = action.getRuns()) @@ -43,24 +53,16 @@ module CfgImpl { parent = job and ( child = job.getArgumentExpr(_) or - child = job.getInScopeEnvVarExpr(_) or child = job.getOutputs() or child = job.getStrategy() ) ) or - exists(UsesStep uses | - parent = uses and - (child = uses.getArgumentExpr(_) or child = uses.getInScopeEnvVarExpr(_)) - ) + exists(UsesStep uses | parent = uses and child = uses.getArgumentExpr(_)) or exists(Run run | parent = run and - ( - child = run.getInScopeEnvVarExpr(_) or - child = run.getAnScriptExpr() or - child = run.getScript() - ) + (child = run.getAnScriptExpr() or child = run.getScript()) ) } diff --git a/actions/ql/test/library-tests/cfg-environment/.github/workflows/test.yml b/actions/ql/test/library-tests/cfg-environment/.github/workflows/test.yml new file mode 100644 index 000000000000..1bd23112154a --- /dev/null +++ b/actions/ql/test/library-tests/cfg-environment/.github/workflows/test.yml @@ -0,0 +1,21 @@ +on: issues + +env: + GLOBAL_VALUE: ${{ github.event.issue.title }} + +jobs: + local: + runs-on: ubuntu-latest + env: + JOB_VALUE: ${{ github.event.issue.body }} + steps: + - uses: actions/checkout@v4 + - run: echo "$GLOBAL_VALUE $JOB_VALUE" + - run: echo "$GLOBAL_VALUE $JOB_VALUE $STEP_VALUE" + env: + STEP_VALUE: ${{ github.actor }} + + external: + uses: octo/example/.github/workflows/reusable.yml@main + with: + value: ${{ github.event.issue.number }} diff --git a/actions/ql/test/library-tests/cfg-environment/test.expected b/actions/ql/test/library-tests/cfg-environment/test.expected new file mode 100644 index 000000000000..4b9b38e9fdb8 --- /dev/null +++ b/actions/ql/test/library-tests/cfg-environment/test.expected @@ -0,0 +1,7 @@ +envCfgNodes +| .github/workflows/test.yml:4:18:4:48 | github.event.issue.title | +| .github/workflows/test.yml:10:19:10:48 | github.event.issue.body | +| .github/workflows/test.yml:16:24:16:42 | github.actor | +cfgCycles +cfgDeadEnds +cfgConsistency diff --git a/actions/ql/test/library-tests/cfg-environment/test.ql b/actions/ql/test/library-tests/cfg-environment/test.ql new file mode 100644 index 000000000000..99f1c23009b3 --- /dev/null +++ b/actions/ql/test/library-tests/cfg-environment/test.ql @@ -0,0 +1,19 @@ +import codeql.actions.Ast +import codeql.actions.Cfg as Cfg + +query predicate envCfgNodes(Expression expression) { + expression = any(Env env).getAnEnvVarExpr() and + exists(Cfg::AstCfgNode node | node.getAstNode() = expression) +} + +query predicate cfgCycles(Cfg::Node node) { node.getASuccessor+() = node } + +query predicate cfgDeadEnds(Cfg::Node node) { + not node instanceof Cfg::ExitNode and + not exists(node.getASuccessor()) +} + +query predicate cfgConsistency(string query, int results) { + Cfg::Consistency::consistencyOverview(query, results) and + results != 0 +} diff --git a/actions/ql/test/query-tests/Security/CWE-078/CONSISTENCY/CfgConsistency.expected b/actions/ql/test/query-tests/Security/CWE-078/CONSISTENCY/CfgConsistency.expected index ddee66dafee9..e69de29bb2d1 100644 --- a/actions/ql/test/query-tests/Security/CWE-078/CONSISTENCY/CfgConsistency.expected +++ b/actions/ql/test/query-tests/Security/CWE-078/CONSISTENCY/CfgConsistency.expected @@ -1,7 +0,0 @@ -consistencyOverview -| multipleSuccessors | 4 | -multipleSuccessors -| .github/workflows/test1.yml:20:16:20:130 | github.event_name == 'workflow_dispatch' && github.event.inputs.action \|\| github.event.client_payload.action | successor | .github/workflows/test1.yml:23:14:27:13 | if [[ "${ACTION}" != "promote" && "${ACTION}" != "rollback" ]]; then\n echo "Invalid action: ${ACTION}"\n exit 1\nfi\n | -| .github/workflows/test1.yml:20:16:20:130 | github.event_name == 'workflow_dispatch' && github.event.inputs.action \|\| github.event.client_payload.action | successor | .github/workflows/test1.yml:29:9:31:6 | After Uses Step | -| .github/workflows/test1.yml:20:16:20:130 | github.event_name == 'workflow_dispatch' && github.event.inputs.action \|\| github.event.client_payload.action | successor | .github/workflows/test1.yml:37:32:37:66 | secrets.DAGGER_CLOUD_TOKEN_2 | -| .github/workflows/test1.yml:20:16:20:130 | github.event_name == 'workflow_dispatch' && github.event.inputs.action \|\| github.event.client_payload.action | successor | .github/workflows/test1.yml:53:32:53:66 | secrets.DAGGER_CLOUD_TOKEN_2 | diff --git a/actions/ql/test/query-tests/Security/CWE-088/CONSISTENCY/CfgConsistency.expected b/actions/ql/test/query-tests/Security/CWE-088/CONSISTENCY/CfgConsistency.expected index 49fcd64e161b..e69de29bb2d1 100644 --- a/actions/ql/test/query-tests/Security/CWE-088/CONSISTENCY/CfgConsistency.expected +++ b/actions/ql/test/query-tests/Security/CWE-088/CONSISTENCY/CfgConsistency.expected @@ -1,20 +0,0 @@ -consistencyOverview -| multipleSuccessors | 17 | -multipleSuccessors -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:15:17:15:57 | github.event.pull_request.head.ref | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:16:14:18:32 | # NOT VULNERABLE\necho "s/FOO/$TITLE/g"\n | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:19:14:21:31 | # VULNERABLE\nsed "s/FOO/$TITLE/g"\n | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:22:14:24:50 | # VULNERABLE\necho "foo" \| sed "s/FOO/$TITLE/g" > bar\n | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:25:14:27:58 | # VULNERABLE\necho $(echo "foo" \| sed "s/FOO/$TITLE/g" > bar)\n | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:28:14:30:31 | # VULNERABLE\nawk "BEGIN {$TITLE}"\n | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:31:14:33:84 | # VULNERABLE\nsed -i "s/git_branch = .*/git_branch = \\"$GITHUB_HEAD_REF\\"/" config.json\n | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:34:14:36:84 | # VULNERABLE\nsed -i "s\|git_branch = .*\|git_branch = \\"$GITHUB_HEAD_REF\\"\|" config.json\n | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:37:14:42:111 | # VULNERABLE\nsed -e 's##${TITLE}#' \\\n -e 's##${{ env.sot_repo }}#' \\\n -e 's##TITLE#' \\\n .github/workflows/common-copybara.bara.sky.template > .github/workflows/common-copybara.bara.sky\n | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:43:14:48:111 | # VULNERABLE\nsed -e 's##TITLE#' \\\n -e 's##${{ env.sot_repo }}#' \\\n -e 's##${TITLE}#' \\\n .github/workflows/common-copybara.bara.sky.template > .github/workflows/common-copybara.bara.sky\n | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:49:14:52:41 | # VULNERABLE\nBODY=$(git log --format=%s)\nsed "s/FOO/$BODY/g" > /tmp/foo\n | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:53:14:56:41 | # VULNERABLE\nBODY=$(git diff --name-only HEAD)\nsed "s/FOO/$BODY/g" > /tmp/foo\n | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:57:14:60:41 | # VULNERABLE\nBODY=$(git diff --name-only HEAD )\nsed "s/FOO/$BODY/g" > /tmp/foo\n | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:61:14:64:41 | # VULNERABLE\nBODY=$(git diff --name-only HEAD^ \| xargs)\nsed "s/FOO/$BODY/g" > /tmp/foo\n | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:65:14:67:67 | # NOT VULNERABLE\necho "value=$(git log -1 --pretty=%s)" >> $GITHUB_OUTPUT\n | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:68:14:70:33 | # NOT VULNERABLE\ngit log -1 --pretty=%s\n | -| .github/workflows/arg_injection.yml:10:15:10:50 | github.event.pull_request.title | successor | .github/workflows/arg_injection.yml:71:14:74:41 | # NOT VULNERABLE\nBODY=$(git log --format=%s)\nsed -E 's/\\s+/\\n/g' <<<"$BODY"\n | diff --git a/actions/ql/test/query-tests/Security/CWE-094/CONSISTENCY/CfgConsistency.expected b/actions/ql/test/query-tests/Security/CWE-094/CONSISTENCY/CfgConsistency.expected index e5b7819f54a4..e69de29bb2d1 100644 --- a/actions/ql/test/query-tests/Security/CWE-094/CONSISTENCY/CfgConsistency.expected +++ b/actions/ql/test/query-tests/Security/CWE-094/CONSISTENCY/CfgConsistency.expected @@ -1,71 +0,0 @@ -consistencyOverview -| multipleSuccessors | 68 | -multipleSuccessors -| .github/workflows/cross1.yml:18:20:18:47 | github.event.issue.body | successor | .github/workflows/cross1.yml:22:9:25:6 | After Uses Step | -| .github/workflows/cross1.yml:18:20:18:47 | github.event.issue.body | successor | .github/workflows/cross1.yml:29:18:29:46 | github.event.issue.title | -| .github/workflows/cross1.yml:18:20:18:47 | github.event.issue.body | successor | .github/workflows/cross1.yml:36:14:38:17 | echo "Bad Issue Title Format"\nexit 1\n | -| .github/workflows/cross1.yml:18:20:18:47 | github.event.issue.body | successor | .github/workflows/cross1.yml:46:21:46:40 | env.ISSUE_TITLE | -| .github/workflows/cross1.yml:18:20:18:47 | github.event.issue.body | successor | .github/workflows/cross1.yml:54:32:54:76 | steps.remove_quotations.outputs.replaced | -| .github/workflows/cross1.yml:18:20:18:47 | github.event.issue.body | successor | .github/workflows/cross1.yml:62:14:64:3 | curl -v -u admin:${{ secrets.DYNAMOBOTTOKEN }} -d '{"labels": ["${{env.content_analysis_response}}"]}' ${{ github.event.issue.url }}/labels\n | -| .github/workflows/cross1.yml:83:22:84:86 | github.actor | successor | .github/workflows/cross1.yml:112:9:115:6 | After Uses Step | -| .github/workflows/cross1.yml:83:22:84:86 | github.actor | successor | .github/workflows/cross1.yml:117:24:117:51 | github.event.issue.body | -| .github/workflows/cross1.yml:83:22:84:86 | github.actor | successor | .github/workflows/cross1.yml:130:24:130:70 | steps.remove_quotations.outputs.replaced | -| .github/workflows/cross1.yml:83:22:84:86 | github.actor | successor | .github/workflows/cross1.yml:137:14:140:7 | curl -v -u admin:${{ secrets.GITHUB_TOKEN }} -d '{"body": "${{env.comment_intro}} ${{env.close_issue_comment}} ${{env.info_needed}}"}' ${{ github.event.issue.url }}/comments\ncurl -v -u admin:${{ secrets.GITHUB_TOKEN }} -X PATCH -d '{"state": "closed"}' ${{ github.event.issue.url }}\n | -| .github/workflows/cross1.yml:83:22:84:86 | github.actor | successor | .github/workflows/cross1.yml:144:14:147:7 | curl -v -u admin:${{ secrets.GITHUB_TOKEN }} -d '{"labels": ["${{env.issue_label}}"]}' ${{ github.event.issue.url }}/labels\ncurl -v -u admin:${{ secrets.GITHUB_TOKEN }} -d '{"body": "${{env.comment_intro}} ${{env.needs_more_info_comment}} ${{env.specific_info}} ${{env.analysis_response}}.\\n\\n${{env.info_needed}}"}' ${{ github.event.issue.url }}/comments\n | -| .github/workflows/cross1.yml:83:22:84:86 | github.actor | successor | .github/workflows/cross1.yml:151:14:154:7 | echo urldecode ${{env.issue_label}}\ncurl -v -u admin:${{ secrets.GITHUB_TOKEN }} -X DELETE ${{ github.event.issue.url }}/labels/$(echo -ne "${{env.issue_label}}" \| xxd -plain \| tr -d '\\n' \| sed 's/\\(..\\)/%\\1/g')\n | -| .github/workflows/cross1.yml:83:22:84:86 | github.actor | successor | .github/workflows/cross1.yml:158:14:161:3 | curl -v -u admin:${{ secrets.GITHUB_TOKEN }} -d '{"body": "${{env.greetings_comment}}"}' ${{ github.event.issue.url }}/comments\n | -| .github/workflows/cross3.yml:18:26:18:48 | github.event.after | successor | .github/workflows/cross3.yml:22:7:27:4 | After Uses Step | -| .github/workflows/cross3.yml:18:26:18:48 | github.event.after | successor | .github/workflows/cross3.yml:32:18:32:53 | github.event.commits[0].message | -| .github/workflows/cross3.yml:18:26:18:48 | github.event.after | successor | .github/workflows/cross3.yml:39:31:39:75 | steps.remove_quotations.outputs.replaced | -| .github/workflows/cross3.yml:18:26:18:48 | github.event.after | successor | .github/workflows/cross3.yml:47:12:53:109 | git config user.name "${{env.user_name}}"\ngit fetch --all\ngit checkout -b ${{env.auto_branch}} origin/${{env.destination_branch}}\ngit cherry-pick -x ${{github.event.after}} --strategy-option theirs\ngit push -u origin ${{env.auto_branch}}\nhub pull-request -b "${{env.destination_branch}}" -h "${{env.auto_branch}}" -m "${{env.pr_message}}"\n | -| .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:34:87:34:112 | github.event.number | successor | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:36:14:36:39 | echo "${{ inputs.taint }}" | -| .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:34:87:34:112 | github.event.number | successor | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:39:17:39:57 | github.event.pull_request.head.ref | -| .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:34:87:34:112 | github.event.number | successor | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:44:19:44:56 | github.event.pull_request.title | -| .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:34:87:34:112 | github.event.number | successor | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:81:9:86:6 | After Uses Step | -| .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:34:87:34:112 | github.event.number | successor | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable-workflow.yml:88:14:95:19 | npm ci\nnpx prettier --write ${{ env.file }}\ngit config user.name github-actions[bot]\ngit config user.email github-actions[bot]@users.noreply.github.com\ngit add ${{ env.file }}\ngit commit -m "update ${{ env.file }}"\ngit push\n | -| .github/workflows/interpolation.yml:41:7:41:41 | github.event.inputs.string-b | successor | .github/workflows/interpolation.yml:49:12:51:43 | echo "region=region" >> $GITHUB_OUTPUT\necho "zone=zone" >> $GITHUB_OUTPUT\n | -| .github/workflows/interpolation.yml:41:7:41:41 | github.event.inputs.string-b | successor | .github/workflows/interpolation.yml:56:20:56:54 | github.event.inputs.choice-a | -| .github/workflows/issues.yaml:10:17:10:47 | github.event.issue.title | successor | .github/workflows/issues.yaml:13:12:13:49 | echo '${{ github.event.issue.title }}' | -| .github/workflows/issues.yaml:10:17:10:47 | github.event.issue.title | successor | .github/workflows/issues.yaml:14:12:14:48 | echo '${{ github.event.issue.body }}' | -| .github/workflows/issues.yaml:10:17:10:47 | github.event.issue.title | successor | .github/workflows/issues.yaml:15:12:15:39 | echo '${{ env.global_env }}' | -| .github/workflows/issues.yaml:10:17:10:47 | github.event.issue.title | successor | .github/workflows/issues.yaml:16:12:16:33 | echo '${{ env.test }}' | -| .github/workflows/issues.yaml:10:17:10:47 | github.event.issue.title | successor | .github/workflows/issues.yaml:17:12:17:36 | echo '${{ env.job_env }}' | -| .github/workflows/issues.yaml:10:17:10:47 | github.event.issue.title | successor | .github/workflows/issues.yaml:18:12:18:37 | echo '${{ env.step_env }}' | -| .github/workflows/level0.yml:31:38:31:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_FRIES | successor | .github/workflows/level0.yml:33:9:36:6 | After Uses Step | -| .github/workflows/level0.yml:31:38:31:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_FRIES | successor | .github/workflows/level0.yml:36:9:39:6 | After Uses Step | -| .github/workflows/level0.yml:31:38:31:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_FRIES | successor | .github/workflows/level0.yml:41:14:50:13 | echo "Checking issue body for profanities..."\nPROFANITIES_LIST="bad\|disguting\|horrible"\nif echo "${{ github.event.issue.body }}" \| grep -qiE "$PROFANITIES_LIST"; then\n echo "Profanity detected in issue body. Please clean up the language."\n exit 1\nelse\n echo "No profanities found in issue body."\n exit 0\nfi\n | -| .github/workflows/level0.yml:60:40:60:84 | secrets.FLAG_GRAVY_OVERFLOW_L0_CHEDDAR | successor | .github/workflows/level0.yml:62:9:65:6 | After Uses Step | -| .github/workflows/level0.yml:60:40:60:84 | secrets.FLAG_GRAVY_OVERFLOW_L0_CHEDDAR | successor | .github/workflows/level0.yml:69:35:69:66 | github.event.comment.body | -| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:96:9:99:6 | After Uses Step | -| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:102:17:102:57 | github.event.pull_request.head.sha | -| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:103:9:107:6 | After Uses Step | -| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:107:14:110:20 | npm install\nnpm run lint\nnpm start\n | -| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:122:9:125:6 | After Uses Step | -| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:128:17:128:57 | github.event.pull_request.head.sha | -| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:129:9:133:6 | After Uses Step | -| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:133:14:135:23 | npm install\nnpm run lint\n | -| .github/workflows/level1.yml:25:51:25:106 | secrets.FLAG_GRAVY_OVERFLOW_L1_TOPPINGS_FOR_REALZ | successor | .github/workflows/level1.yml:27:9:30:6 | After Uses Step | -| .github/workflows/level1.yml:25:51:25:106 | secrets.FLAG_GRAVY_OVERFLOW_L1_TOPPINGS_FOR_REALZ | successor | .github/workflows/level1.yml:32:24:32:50 | secrets.GITHUB_TOKEN | -| .github/workflows/level1.yml:25:51:25:106 | secrets.FLAG_GRAVY_OVERFLOW_L1_TOPPINGS_FOR_REALZ | successor | .github/workflows/level1.yml:36:14:37:160 | echo "Lint ran for branch ${{ github.event.workflow_run.head_branch }} in a PR from ${{ github.actor }}. Please check the logs for more information."\n | -| .github/workflows/reusable-workflow-1.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-1.yml:36:14:36:39 | echo "${{ inputs.taint }}" | -| .github/workflows/reusable-workflow-1.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-1.yml:39:17:39:57 | github.event.pull_request.head.ref | -| .github/workflows/reusable-workflow-1.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-1.yml:44:19:44:56 | github.event.pull_request.title | -| .github/workflows/reusable-workflow-1.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-1.yml:81:9:86:6 | After Uses Step | -| .github/workflows/reusable-workflow-1.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-1.yml:88:14:95:19 | npm ci\nnpx prettier --write ${{ env.file }}\ngit config user.name github-actions[bot]\ngit config user.email github-actions[bot]@users.noreply.github.com\ngit add ${{ env.file }}\ngit commit -m "update ${{ env.file }}"\ngit push\n | -| .github/workflows/reusable-workflow-2.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-2.yml:36:14:36:39 | echo "${{ inputs.taint }}" | -| .github/workflows/reusable-workflow-2.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-2.yml:39:17:39:57 | github.event.pull_request.head.ref | -| .github/workflows/reusable-workflow-2.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-2.yml:44:19:44:56 | github.event.pull_request.title | -| .github/workflows/reusable-workflow-2.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-2.yml:81:9:86:6 | After Uses Step | -| .github/workflows/reusable-workflow-2.yml:34:87:34:112 | github.event.number | successor | .github/workflows/reusable-workflow-2.yml:88:14:95:19 | npm ci\nnpx prettier --write ${{ env.file }}\ngit config user.name github-actions[bot]\ngit config user.email github-actions[bot]@users.noreply.github.com\ngit add ${{ env.file }}\ngit commit -m "update ${{ env.file }}"\ngit push\n | -| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:17:9:25:6 | After Uses Step: changed | -| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:26:14:28:61 | echo changed: ${{ steps.changed.outputs.locale_files }}\necho changed: ${{ steps.changed.outputs.changes }}\n | -| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:29:9:37:6 | After Uses Step: changed2 | -| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:38:14:40:62 | echo changed:${{ steps.changed2.outputs.locale_files }}\necho changed: ${{ steps.changed2.outputs.changes }}\n | -| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:41:9:49:6 | After Uses Step: changed3 | -| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:50:14:52:62 | echo changed:${{ steps.changed3.outputs.locale_files }}\necho changed: ${{ steps.changed3.outputs.changes }}\n | -| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:53:9:61:6 | After Uses Step: changed4 | -| .github/workflows/test2.yml:15:22:15:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test2.yml:62:14:64:62 | echo changed:${{ steps.changed4.outputs.locale_files }}\necho changed: ${{ steps.changed4.outputs.changes }}\n | -| .github/workflows/test26.yml:11:14:11:32 | github.token | successor | .github/workflows/test26.yml:20:11:20:140 | echo "body=$(gh issue view ${{ inputs.issue_number }} --repo ${{ github.repository }} --json body --jq '.body')" >> $GITHUB_OUTPUT | -| .github/workflows/test26.yml:11:14:11:32 | github.token | successor | .github/workflows/test26.yml:26:18:26:58 | steps.read_issue_body.outputs.body | -| .github/workflows/test26.yml:11:14:11:32 | github.token | successor | .github/workflows/test26.yml:28:14:28:49 | echo ${{ steps.parse.outputs.data }} | -| .github/workflows/test26.yml:11:14:11:32 | github.token | successor | .github/workflows/test26.yml:29:14:29:57 | echo ${{ toJSON(steps.parse.outputs.data) }} | diff --git a/actions/ql/test/query-tests/Security/CWE-829/CONSISTENCY/CfgConsistency.expected b/actions/ql/test/query-tests/Security/CWE-829/CONSISTENCY/CfgConsistency.expected index 5291875efcee..e69de29bb2d1 100644 --- a/actions/ql/test/query-tests/Security/CWE-829/CONSISTENCY/CfgConsistency.expected +++ b/actions/ql/test/query-tests/Security/CWE-829/CONSISTENCY/CfgConsistency.expected @@ -1,72 +0,0 @@ -consistencyOverview -| multipleSuccessors | 69 | -multipleSuccessors -| .github/workflows/level0.yml:31:38:31:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_FRIES | successor | .github/workflows/level0.yml:33:9:36:6 | After Uses Step | -| .github/workflows/level0.yml:31:38:31:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_FRIES | successor | .github/workflows/level0.yml:36:9:39:6 | After Uses Step | -| .github/workflows/level0.yml:31:38:31:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_FRIES | successor | .github/workflows/level0.yml:41:14:50:13 | echo "Checking issue body for profanities..."\nPROFANITIES_LIST="bad\|disguting\|horrible"\nif echo "${{ github.event.issue.body }}" \| grep -qiE "$PROFANITIES_LIST"; then\n echo "Profanity detected in issue body. Please clean up the language."\n exit 1\nelse\n echo "No profanities found in issue body."\n exit 0\nfi\n | -| .github/workflows/level0.yml:60:40:60:84 | secrets.FLAG_GRAVY_OVERFLOW_L0_CHEDDAR | successor | .github/workflows/level0.yml:62:9:65:6 | After Uses Step | -| .github/workflows/level0.yml:60:40:60:84 | secrets.FLAG_GRAVY_OVERFLOW_L0_CHEDDAR | successor | .github/workflows/level0.yml:69:35:69:66 | github.event.comment.body | -| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:96:9:99:6 | After Uses Step | -| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:102:17:102:57 | github.event.pull_request.head.sha | -| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:103:9:107:6 | After Uses Step | -| .github/workflows/level0.yml:94:38:94:80 | secrets.FLAG_GRAVY_OVERFLOW_L0_GRAVY | successor | .github/workflows/level0.yml:107:14:110:20 | npm install\nnpm run lint\nnpm start\n | -| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:122:9:125:6 | After Uses Step | -| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:128:17:128:57 | github.event.pull_request.head.sha | -| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:129:9:133:6 | After Uses Step | -| .github/workflows/level0.yml:120:41:120:86 | secrets.FLAG_GRAVY_OVERFLOW_L0_TOPPINGS | successor | .github/workflows/level0.yml:133:14:135:23 | npm install\nnpm run lint\n | -| .github/workflows/mend.yml:7:13:7:60 | secrets.API_KEY != '' && secrets.API_KEY | successor | .github/workflows/mend.yml:15:14:20:13 | if [[ "${{ github.event_name }}" == "pull_request_target" ]]; then\n echo "ref=${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT\nelse\n echo "ref=${{ github.ref }}" >> $GITHUB_OUTPUT\nfi\n | -| .github/workflows/mend.yml:7:13:7:60 | secrets.API_KEY != '' && secrets.API_KEY | successor | .github/workflows/mend.yml:27:17:27:48 | steps.set_ref.outputs.ref | -| .github/workflows/mend.yml:7:13:7:60 | secrets.API_KEY != '' && secrets.API_KEY | successor | .github/workflows/mend.yml:29:9:33:28 | After Uses Step | -| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:22:27:22:58 | github.event.issue.number | -| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:24:9:27:6 | After Uses Step | -| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:27:9:33:6 | After Uses Step | -| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:34:14:34:25 | pnpm install | -| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:37:14:37:27 | pnpm run build | -| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:43:21:43:52 | github.event.comment.body | -| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:51:14:57:44 | result=$(pnpm run --silent benchmark ${{ steps.bench-command.outputs.bench }})\nprocessed=$(node ./benchmark/ci-helper.js "$result")\necho "BENCH_RESULT<> $GITHUB_OUTPUT\necho "### PR Benchmark" >> $GITHUB_OUTPUT\necho "$processed" >> $GITHUB_OUTPUT\necho "BENCHEOF" >> $GITHUB_OUTPUT\n | -| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:59:14:59:29 | python2.7 foo.py | -| .github/workflows/test7.yml:9:16:9:40 | secrets.TURBO_TEAM | successor | .github/workflows/test7.yml:60:14:60:36 | pip install --no-deps . | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:44:14:45:50 | echo "$SUMMARY" >> $GITHUB_STEP_SUMMARY\n | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:47:9:49:6 | After Uses Step | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:53:20:53:54 | secrets.JP_LAUNCH_CONTROL_ID | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:61:17:61:57 | github.event.workflow_run.head_sha | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:75:9:77:6 | After Uses Step | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:81:20:81:54 | secrets.JP_LAUNCH_CONTROL_ID | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:87:16:87:55 | needs.setup.outputs.upgrade_check | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:106:9:108:6 | After Uses Step | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:111:19:111:37 | github.token | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:145:20:145:54 | secrets.JP_LAUNCH_CONTROL_ID | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:151:16:151:55 | needs.setup.outputs.upgrade_check | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:167:9:169:6 | After Uses Step | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:173:20:173:54 | secrets.JP_LAUNCH_CONTROL_ID | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:179:16:179:55 | needs.setup.outputs.upgrade_check | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:218:9:221:6 | After Uses Step | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:223:17:223:63 | github.event.workflow_run.head_commit.id | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:233:20:233:54 | secrets.JP_LAUNCH_CONTROL_ID | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:239:16:239:55 | needs.setup.outputs.upgrade_check | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:250:19:250:37 | github.token | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:271:14:271:60 | trunk/.github/files/test-plugin-update/setup.sh | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:275:14:275:67 | trunk/.github/files/test-plugin-update/prepare-zips.sh | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:279:14:279:59 | trunk/.github/files/test-plugin-update/test.sh | -| .github/workflows/test16.yml:15:190:15:230 | github.event.workflow_run.html_url | successor | .github/workflows/test16.yml:285:16:285:55 | needs.setup.outputs.upgrade_check | -| .github/workflows/test19.yml:14:22:14:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test19.yml:20:15:20:55 | github.event.pull_request.head.ref | -| .github/workflows/test19.yml:14:22:14:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test19.yml:21:12:22:14 | ./cmd\n | -| .github/workflows/test20.yml:14:22:14:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test20.yml:20:15:20:55 | github.event.pull_request.head.sha | -| .github/workflows/test20.yml:14:22:14:48 | secrets.GITHUB_TOKEN | successor | .github/workflows/test20.yml:21:12:22:14 | ./cmd\n | -| .github/workflows/untrusted_checkout.yml:21:14:21:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout.yml:25:17:25:31 | env.HEAD | -| .github/workflows/untrusted_checkout.yml:21:14:21:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout.yml:26:9:30:6 | After Uses Step | -| .github/workflows/untrusted_checkout.yml:21:14:21:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout.yml:30:14:32:23 | npm install\nnpm run lint\n | -| .github/workflows/untrusted_checkout_5.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_5.yml:13:17:13:57 | github.event.pull_request.head.sha | -| .github/workflows/untrusted_checkout_5.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_5.yml:16:17:16:31 | env.HEAD | -| .github/workflows/untrusted_checkout_5.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_5.yml:17:9:21:6 | After Uses Step | -| .github/workflows/untrusted_checkout_5.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_5.yml:21:14:23:23 | npm install\nnpm run lint\n | -| .github/workflows/untrusted_checkout_6.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_6.yml:13:17:13:57 | github.event.pull_request.head.sha | -| .github/workflows/untrusted_checkout_6.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_6.yml:16:17:16:31 | env.HEAD | -| .github/workflows/untrusted_checkout_6.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_6.yml:17:9:21:6 | After Uses Step | -| .github/workflows/untrusted_checkout_6.yml:9:14:9:54 | github.event.pull_request.head.sha | successor | .github/workflows/untrusted_checkout_6.yml:21:14:23:23 | npm install\nnpm run lint\n | -| .github/workflows/workflow_run_untrusted_checkout.yml:11:14:11:54 | github.event.workflow_run.head.sha | successor | .github/workflows/workflow_run_untrusted_checkout.yml:15:17:15:57 | github.event.workflow_run.head.sha | -| .github/workflows/workflow_run_untrusted_checkout.yml:11:14:11:54 | github.event.workflow_run.head.sha | successor | .github/workflows/workflow_run_untrusted_checkout.yml:18:17:18:31 | env.HEAD | -| .github/workflows/workflow_run_untrusted_checkout_2.yml:11:14:11:54 | github.event.workflow_run.head.sha | successor | .github/workflows/workflow_run_untrusted_checkout_2.yml:15:17:15:57 | github.event.workflow_run.head.sha | -| .github/workflows/workflow_run_untrusted_checkout_2.yml:11:14:11:54 | github.event.workflow_run.head.sha | successor | .github/workflows/workflow_run_untrusted_checkout_2.yml:18:17:18:31 | env.HEAD | -| .github/workflows/workflow_run_untrusted_checkout_3.yml:11:14:11:54 | github.event.workflow_run.head.sha | successor | .github/workflows/workflow_run_untrusted_checkout_3.yml:15:17:15:57 | github.event.workflow_run.head.sha | -| .github/workflows/workflow_run_untrusted_checkout_3.yml:11:14:11:54 | github.event.workflow_run.head.sha | successor | .github/workflows/workflow_run_untrusted_checkout_3.yml:18:17:18:31 | env.HEAD | From 755e78bdaaa1623cd3a596ef4af0a0087ab0dcba Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Mon, 5 Oct 2026 10:51:36 +0100 Subject: [PATCH 3/4] Actions: ensure CFG callable scopes are unique Prefer the composite-action interpretation when a YAML document is recognized as both a workflow and a composite action, ensuring each CFG node has a unique enclosing callable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../actions/controlflow/internal/Cfg.qll | 16 +++++++++++-- .../library-tests/cfg-callable/action.yml | 12 ++++++++++ .../library-tests/cfg-callable/test.expected | 5 ++++ .../test/library-tests/cfg-callable/test.ql | 23 +++++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 actions/ql/test/library-tests/cfg-callable/action.yml create mode 100644 actions/ql/test/library-tests/cfg-callable/test.expected create mode 100644 actions/ql/test/library-tests/cfg-callable/test.ql diff --git a/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll b/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll index 2fabd484ba22..0adefae2762b 100644 --- a/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll +++ b/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll @@ -93,14 +93,26 @@ module CfgImpl { AstNode getChild(AstNode node, int index) { result = getCfgChild(node, index) } class Callable extends AstNode { - Callable() { this instanceof Workflow or this instanceof CompositeAction } + Callable() { + this instanceof CompositeAction + or + this instanceof Workflow and + not exists(CompositeAction action | action.getLocation() = this.getLocation()) + } } AstNode callableGetBody(Callable callable) { result = callable } + /** + * Gets the unique callable containing `node`. + * + * An Actions AST node may have multiple parent paths, but they converge on + * the same root. + */ Callable getEnclosingCallable(AstNode node) { - result = node.(Callable) + result = node or + not node instanceof Callable and result = getEnclosingCallable(node.getParentNode()) } diff --git a/actions/ql/test/library-tests/cfg-callable/action.yml b/actions/ql/test/library-tests/cfg-callable/action.yml new file mode 100644 index 000000000000..81aa1df95a00 --- /dev/null +++ b/actions/ql/test/library-tests/cfg-callable/action.yml @@ -0,0 +1,12 @@ +name: Ambiguous root + +runs: + using: composite + steps: + - run: echo "${{ github.actor }}" + +jobs: + unexpected: + runs-on: ubuntu-latest + steps: + - run: echo "This must not make action.yml a workflow" diff --git a/actions/ql/test/library-tests/cfg-callable/test.expected b/actions/ql/test/library-tests/cfg-callable/test.expected new file mode 100644 index 000000000000..057e5f24e2c6 --- /dev/null +++ b/actions/ql/test/library-tests/cfg-callable/test.expected @@ -0,0 +1,5 @@ +roots +| 1 | 1 | +cfgScopes +| action.yml:1:1:12:61 | name: Ambiguous root | composite action | +cfgConsistency diff --git a/actions/ql/test/library-tests/cfg-callable/test.ql b/actions/ql/test/library-tests/cfg-callable/test.ql new file mode 100644 index 000000000000..fd60bb368954 --- /dev/null +++ b/actions/ql/test/library-tests/cfg-callable/test.ql @@ -0,0 +1,23 @@ +import codeql.actions.Ast +import codeql.actions.Cfg as Cfg + +query predicate roots(int workflows, int compositeActions) { + workflows = + strictcount(Workflow workflow | workflow.getLocation().getFile().getBaseName() = "action.yml") and + compositeActions = + strictcount(CompositeAction action | action.getLocation().getFile().getBaseName() = "action.yml") +} + +query predicate cfgScopes(Cfg::CfgScope scope, string kind) { + scope.getLocation().getFile().getBaseName() = "action.yml" and + ( + scope instanceof Cfg::WorkflowScope and kind = "workflow" + or + scope instanceof Cfg::CompositeActionScope and kind = "composite action" + ) +} + +query predicate cfgConsistency(string query, int results) { + Cfg::Consistency::consistencyOverview(query, results) and + results != 0 +} From 84ed5d6997b256cf0eebf9c08b8ce78c9e94bad7 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 6 Oct 2026 16:18:46 +0100 Subject: [PATCH 4/4] Address review comments --- .../actions/controlflow/BasicBlocks.qll | 112 ------------------ .../actions/controlflow/internal/Cfg.qll | 85 ++----------- .../dataflow/internal/DataFlowPrivate.qll | 5 +- .../ql/test/library-tests/basic/test.expected | 13 -- actions/ql/test/library-tests/basic/test.ql | 40 +------ .../library-tests/cfg-callable/test.expected | 1 - .../test/library-tests/cfg-callable/test.ql | 5 - .../cfg-environment/test.expected | 2 - .../library-tests/cfg-environment/test.ql | 10 -- 9 files changed, 13 insertions(+), 260 deletions(-) delete mode 100644 actions/ql/lib/codeql/actions/controlflow/BasicBlocks.qll diff --git a/actions/ql/lib/codeql/actions/controlflow/BasicBlocks.qll b/actions/ql/lib/codeql/actions/controlflow/BasicBlocks.qll deleted file mode 100644 index 5ca4f19eff62..000000000000 --- a/actions/ql/lib/codeql/actions/controlflow/BasicBlocks.qll +++ /dev/null @@ -1,112 +0,0 @@ -/** Provides classes representing basic blocks. */ - -private import codeql.actions.Cfg as Cfg - -/** - * A basic block, that is, a maximal straight-line sequence of control flow nodes - * without branches or joins. - */ -class BasicBlock extends Cfg::BasicBlock { - /** Gets an immediate successor of this basic block, if any. */ - BasicBlock getASuccessor() { result = super.getASuccessor() } - - /** Gets an immediate successor of this basic block of a given type, if any. */ - BasicBlock getASuccessor(Cfg::SuccessorType t) { result = super.getASuccessor(t) } - - /** Gets an immediate predecessor of this basic block, if any. */ - BasicBlock getAPredecessor() { result = super.getAPredecessor() } - - /** Gets an immediate predecessor of this basic block of a given type, if any. */ - BasicBlock getAPredecessor(Cfg::SuccessorType t) { result = super.getAPredecessor(t) } - - /** Gets the control flow node at a specific (zero-indexed) position in this basic block. */ - Cfg::Node getNode(int pos) { result = super.getNode(pos) } - - /** Gets a control flow node in this basic block. */ - Cfg::Node getANode() { result = super.getANode() } - - /** Gets the first control flow node in this basic block. */ - Cfg::Node getFirstNode() { result = super.getFirstNode() } - - /** Gets the last control flow node in this basic block. */ - Cfg::Node getLastNode() { result = super.getLastNode() } - - predicate immediatelyDominates(BasicBlock bb) { super.immediatelyDominates(bb) } - - predicate strictlyDominates(BasicBlock bb) { super.strictlyDominates(bb) } - - predicate dominates(BasicBlock bb) { super.dominates(bb) } - - predicate inDominanceFrontier(BasicBlock df) { super.inDominanceFrontier(df) } - - BasicBlock getImmediateDominator() { result = super.getImmediateDominator() } - - predicate strictlyPostDominates(BasicBlock bb) { super.strictlyPostDominates(bb) } - - predicate postDominates(BasicBlock bb) { super.postDominates(bb) } -} - -/** - * An entry basic block, that is, a basic block whose first node is - * an entry node. - */ -class EntryBasicBlock extends BasicBlock, Cfg::EntryBasicBlock { } - -/** - * An annotated exit basic block, that is, a basic block that contains an - * annotated exit node. - */ -class AnnotatedExitBasicBlock extends BasicBlock { - AnnotatedExitBasicBlock() { this.getANode() instanceof Cfg::AnnotatedExitNode } - - /** Holds if this block represents a normal exit. */ - final predicate isNormal() { this.getANode() instanceof Cfg::NormalExitNode } -} - -/** - * An exit basic block, that is, a basic block whose last node is - * an exit node. - */ -class ExitBasicBlock extends BasicBlock { - ExitBasicBlock() { this.getLastNode() instanceof Cfg::ExitNode } -} - -/** A basic block with more than one predecessor. */ -class JoinBlock extends BasicBlock { - JoinBlock() { strictcount(this.getFirstNode().getAPredecessor()) > 1 } - - /** - * Gets the `i`th predecessor of this join block, with respect to some - * arbitrary order. - */ - JoinBlockPredecessor getJoinBlockPredecessor(int i) { none() } -} - -/** A basic block that is an immediate predecessor of a join block. */ -class JoinBlockPredecessor extends BasicBlock { - JoinBlockPredecessor() { this.getASuccessor() instanceof JoinBlock } -} - -/** A basic block that terminates in a condition, splitting the subsequent control flow. */ -class ConditionBlock extends BasicBlock { - ConditionBlock() { - exists(this.getLastNode().getASuccessor(any(Cfg::BooleanSuccessor successor))) - } - - /** - * Holds if basic block `succ` is immediately controlled by this basic - * block with conditional value `s`. - */ - predicate immediatelyControls(BasicBlock succ, Cfg::BooleanSuccessor s) { - succ = this.getASuccessor(s) and - forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != this | succ.dominates(pred)) - } - - /** - * Holds if basic block `controlled` is controlled by this basic block with - * conditional value `s`. - */ - predicate controls(BasicBlock controlled, Cfg::BooleanSuccessor s) { - exists(BasicBlock succ | this.immediatelyControls(succ, s) and succ.dominates(controlled)) - } -} diff --git a/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll b/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll index 0adefae2762b..f01fdbd0e4a1 100644 --- a/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll +++ b/actions/ql/lib/codeql/actions/controlflow/internal/Cfg.qll @@ -77,16 +77,6 @@ module CfgImpl { ) } - private AstNode getLastCfgAstNode(AstNode node) { - not exists(getCfgChild(node, _)) and result = node - or - exists(AstNode child, int index | - child = getCfgChild(node, index) and - not exists(int later | later > index and exists(getCfgChild(node, later))) and - result = getLastCfgAstNode(child) - ) - } - private module CfgAst implements CfgShared::AstSig { class AstNode = ActionsAstNode; @@ -397,54 +387,23 @@ module CfgImpl { /** A CFG scope for a composite action. */ class CompositeActionScope extends CfgScope instanceof CompositeAction { } - /** - * A control flow node. - * - * Only nodes that can be reached from an entry point are included in the CFG. - */ - class Node extends ControlFlowNode { - /** Gets the CFG scope containing this node. */ - CfgScope getScope() { result = this.getEnclosingCallable() } - - Node getASuccessor(SuccessorType type) { result = super.getASuccessor(type) } - - Node getASuccessor() { result = super.getASuccessor() } - - /** Gets an immediate predecessor connected by an edge of type `type`, if any. */ - Node getAPredecessor(SuccessorType type) { result.getASuccessor(type) = this } - - Node getAPredecessor() { result = super.getAPredecessor() } - - /** Holds if this node has a conditional successor. */ - predicate isCondition() { exists(this.getASuccessor(any(ConditionalSuccessor successor))) } - - /** Holds if this node has more than one predecessor. */ - predicate isJoin() { strictcount(this.getAPredecessor()) > 1 } - - /** Holds if this node has more than one successor. */ - predicate isBranch() { strictcount(this.getASuccessor()) > 1 } - } + /** A control flow node. */ + class Node = ControlFlowNode; /** The control flow node at the entry point of a scope. */ - class EntryNode extends Node, ControlFlow::EntryNode { } + class EntryNode = ControlFlow::EntryNode; /** A control flow node indicating normal or exceptional termination of a scope. */ - class AnnotatedExitNode extends Node, ControlFlow::AnnotatedExitNode { - /** Holds if this node represents a normal exit. */ - predicate isNormal() { this instanceof NormalExitNode } - } + class AnnotatedExitNode = ControlFlow::AnnotatedExitNode; /** A control flow node indicating normal termination of a scope. */ - class NormalExitNode extends AnnotatedExitNode, ControlFlow::NormalExitNode { } + class NormalExitNode = ControlFlow::NormalExitNode; /** A control flow node indicating exceptional termination of a scope. */ - class ExceptionalExitNode extends AnnotatedExitNode, ControlFlow::ExceptionalExitNode { } + class ExceptionalExitNode = ControlFlow::ExceptionalExitNode; /** A control flow node indicating the termination of a scope. */ - class ExitNode extends Node, ControlFlow::ExitNode { } - - /** The empty split type retained for compatibility with the legacy Actions CFG. */ - class Split = Void; + class ExitNode = ControlFlow::ExitNode; /** * A node that uniquely represents an AST node. @@ -455,35 +414,5 @@ module CfgImpl { AstCfgNode() { this.injects(_) } AstNode getAstNode() { this.injects(result) } - - /** Gets a comma-separated list of splits in this node, if any. */ - string getSplitsString() { none() } - - /** Gets a split for this control flow node, if any. */ - Split getASplit() { none() } } - - /** - * If needed, call this predicate to force a stage dependency on the cached CFG stage. - */ - cached - predicate forceCachingInSameStage() { CfgCachedStage::ref() } - - /** Gets the first AST node executed within `node`. */ - cached - AstNode getAControlFlowEntryNode(AstNode node) { - result = node and - exists(Node cfgNode | cfgNode.injects(node)) - } - - /** Gets a potential last AST node executed within `node`. */ - cached - AstNode getAControlFlowExitNode(AstNode node) { - exists(Node cfgNode | cfgNode.injects(node)) and - result = getLastCfgAstNode(node) - } - - /** Gets the CFG scope of `node`. */ - cached - CfgScope getNodeCfgScope(Node node) { result = node.getScope() } } diff --git a/actions/ql/lib/codeql/actions/dataflow/internal/DataFlowPrivate.qll b/actions/ql/lib/codeql/actions/dataflow/internal/DataFlowPrivate.qll index 084acb587768..1576377e0804 100644 --- a/actions/ql/lib/codeql/actions/dataflow/internal/DataFlowPrivate.qll +++ b/actions/ql/lib/codeql/actions/dataflow/internal/DataFlowPrivate.qll @@ -3,7 +3,6 @@ private import codeql.dataflow.DataFlow private import codeql.actions.Ast private import codeql.actions.Cfg as Cfg private import codeql.Locations -private import codeql.actions.controlflow.BasicBlocks private import DataFlowPublic private import codeql.actions.dataflow.ExternalFlow private import codeql.actions.dataflow.FlowSteps @@ -50,7 +49,7 @@ predicate isArgumentNode(ArgumentNode arg, DataFlowCall call, ArgumentPosition p } DataFlowCallable nodeGetEnclosingCallable(Node node) { - node = TExprNode(any(DataFlowExpr e | result = e.getScope())) + node = TExprNode(any(DataFlowExpr e | result = e.getEnclosingCallable())) } DataFlowType getNodeType(Node node) { any() } @@ -86,7 +85,7 @@ class DataFlowCall instanceof Cfg::Node { string getName() { result = super.getAstNode().(Uses).getCallee() } - DataFlowCallable getEnclosingCallable() { result = super.getScope() } + DataFlowCallable getEnclosingCallable() { result = super.getEnclosingCallable() } /** Gets a best-effort total ordering. */ int totalorder() { none() } diff --git a/actions/ql/test/library-tests/basic/test.expected b/actions/ql/test/library-tests/basic/test.expected index 947b249f3791..d29f21b8360a 100644 --- a/actions/ql/test/library-tests/basic/test.expected +++ b/actions/ql/test/library-tests/basic/test.expected @@ -1293,7 +1293,6 @@ cfgNodes | .github/workflows/test.yml:40:14:40:52 | echo ${{needs.job1.outputs.job_output}} | | .github/workflows/test.yml:40:20:40:53 | needs.job1.outputs.job_output | cfgCycles -cfgDeadEnds dfNodes | .github/workflows/commands.yml:9:5:31:2 | Job: local_commands | | .github/workflows/commands.yml:15:9:18:6 | Run Step | @@ -1747,16 +1746,6 @@ workflowScopes | .github/workflows/shell.yml:1:1:22:32 | on: push | | .github/workflows/test.yml:1:1:40:53 | on: push | compositeActionScopes -workflowCfgBounds -| .github/workflows/commands.yml:1:1:39:30 | on: push | 1 | 38 | -| .github/workflows/controlcheck.yml:1:1:16:25 | on: | 1 | 16 | -| .github/workflows/expression_nodes.yml:1:1:21:47 | on: issue_comment | 1 | 20 | -| .github/workflows/many_strings.yml:1:1:18:1211 | on: | 1 | 11 | -| .github/workflows/multiline2.yml:1:1:89:35 | on: | 1 | 86 | -| .github/workflows/multiline.yml:1:1:89:29 | on: | 1 | 86 | -| .github/workflows/poisonable_steps.yml:1:1:46:111 | on: push | 1 | 44 | -| .github/workflows/shell.yml:1:1:22:32 | on: push | 1 | 22 | -| .github/workflows/test.yml:1:1:40:53 | on: push | 1 | 40 | workflowCfgNodes | .github/workflows/commands.yml:1:1:39:30 | on: push | | .github/workflows/controlcheck.yml:1:1:16:25 | on: | @@ -1787,8 +1776,6 @@ normalExitNodes | .github/workflows/poisonable_steps.yml:1:1:46:111 | Normal Exit | | .github/workflows/shell.yml:1:1:22:32 | Normal Exit | | .github/workflows/test.yml:1:1:40:53 | Normal Exit | -legacyNodeProperties -legacyCfgSplits sources | AvraamMavridis/files-changed-action | * | output.CHANGED_FILES | filename | manual | | AvraamMavridis/files-changed-action | * | output.CHANGED_FILES_EXTENSIONS | filename | manual | diff --git a/actions/ql/test/library-tests/basic/test.ql b/actions/ql/test/library-tests/basic/test.ql index 5e6749da288b..fe44a0f4dcf5 100644 --- a/actions/ql/test/library-tests/basic/test.ql +++ b/actions/ql/test/library-tests/basic/test.ql @@ -39,11 +39,6 @@ query predicate cfgNodes(Cfg::Node n) { any() } query predicate cfgCycles(Cfg::Node n) { n.getASuccessor+() = n } -query predicate cfgDeadEnds(Cfg::Node n) { - not n instanceof Cfg::ExitNode and - not exists(n.getASuccessor()) -} - query predicate dfNodes(DataFlow::Node e) { any() } query predicate argumentNodes(DataFlow::ArgumentNode e) { any() } @@ -58,43 +53,16 @@ query predicate workflowScopes(Cfg::WorkflowScope c) { any() } query predicate compositeActionScopes(Cfg::CompositeActionScope c) { any() } -query predicate workflowCfgBounds(Workflow workflow, int entryLine, int exitLine) { - exists(AstNode entry, AstNode exit | - entry = Cfg::getAControlFlowEntryNode(workflow) and - exit = Cfg::getAControlFlowExitNode(workflow) and - entryLine = entry.getLocation().getStartLine() and - exitLine = exit.getLocation().getStartLine() - ) -} - query predicate workflowCfgNodes(Cfg::AstCfgNode node) { - Cfg::forceCachingInSameStage() and node.getAstNode() instanceof Workflow and - Cfg::getNodeCfgScope(node) = node.getAstNode() + node.getEnclosingCallable() = node.getAstNode() } -query predicate entryScopes(Cfg::EntryNode entry, Cfg::CfgScope scope) { scope = entry.getScope() } - -query predicate normalExitNodes(Cfg::AnnotatedExitNode exit) { exit.isNormal() } - -query predicate legacyNodeProperties( - Cfg::Node node, Cfg::SuccessorType successorType, string property -) { - node = node.getASuccessor(successorType) and property = "successor" - or - node = node.getAPredecessor(successorType) and property = "predecessor" - or - node.isCondition() and property = "condition" - or - node.isJoin() and property = "join" - or - node.isBranch() and property = "branch" +query predicate entryScopes(Cfg::EntryNode entry, Cfg::CfgScope scope) { + scope = entry.getEnclosingCallable() } -query predicate legacyCfgSplits(Cfg::AstCfgNode node) { - exists(node.getSplitsString()) or - exists(node.getASplit()) -} +query predicate normalExitNodes(Cfg::AnnotatedExitNode exit) { exit instanceof Cfg::NormalExitNode } query predicate sources(string action, string version, string output, string kind, string provenance) { actionsSourceModel(action, version, output, kind, provenance) diff --git a/actions/ql/test/library-tests/cfg-callable/test.expected b/actions/ql/test/library-tests/cfg-callable/test.expected index 057e5f24e2c6..17687b33ef7d 100644 --- a/actions/ql/test/library-tests/cfg-callable/test.expected +++ b/actions/ql/test/library-tests/cfg-callable/test.expected @@ -2,4 +2,3 @@ roots | 1 | 1 | cfgScopes | action.yml:1:1:12:61 | name: Ambiguous root | composite action | -cfgConsistency diff --git a/actions/ql/test/library-tests/cfg-callable/test.ql b/actions/ql/test/library-tests/cfg-callable/test.ql index fd60bb368954..5b66c1c4d314 100644 --- a/actions/ql/test/library-tests/cfg-callable/test.ql +++ b/actions/ql/test/library-tests/cfg-callable/test.ql @@ -16,8 +16,3 @@ query predicate cfgScopes(Cfg::CfgScope scope, string kind) { scope instanceof Cfg::CompositeActionScope and kind = "composite action" ) } - -query predicate cfgConsistency(string query, int results) { - Cfg::Consistency::consistencyOverview(query, results) and - results != 0 -} diff --git a/actions/ql/test/library-tests/cfg-environment/test.expected b/actions/ql/test/library-tests/cfg-environment/test.expected index 4b9b38e9fdb8..21d55030e278 100644 --- a/actions/ql/test/library-tests/cfg-environment/test.expected +++ b/actions/ql/test/library-tests/cfg-environment/test.expected @@ -3,5 +3,3 @@ envCfgNodes | .github/workflows/test.yml:10:19:10:48 | github.event.issue.body | | .github/workflows/test.yml:16:24:16:42 | github.actor | cfgCycles -cfgDeadEnds -cfgConsistency diff --git a/actions/ql/test/library-tests/cfg-environment/test.ql b/actions/ql/test/library-tests/cfg-environment/test.ql index 99f1c23009b3..b4a94fa80bc6 100644 --- a/actions/ql/test/library-tests/cfg-environment/test.ql +++ b/actions/ql/test/library-tests/cfg-environment/test.ql @@ -7,13 +7,3 @@ query predicate envCfgNodes(Expression expression) { } query predicate cfgCycles(Cfg::Node node) { node.getASuccessor+() = node } - -query predicate cfgDeadEnds(Cfg::Node node) { - not node instanceof Cfg::ExitNode and - not exists(node.getASuccessor()) -} - -query predicate cfgConsistency(string query, int results) { - Cfg::Consistency::consistencyOverview(query, results) and - results != 0 -}