Refactor Script support

This commit is contained in:
Alvaro Muñoz
2024-10-14 12:04:20 +02:00
parent a09acb5462
commit be87eccbe7
35 changed files with 1001 additions and 519 deletions

View File

@@ -22,6 +22,10 @@ class AstNode instanceof AstNodeImpl {
CompositeAction getEnclosingCompositeAction() { result = super.getEnclosingCompositeAction() }
Expression getInScopeEnvVarExpr(string name) { result = super.getInScopeEnvVarExpr(name) }
ScalarValue getInScopeDefaultValue(string name, string prop) {
result = super.getInScopeDefaultValue(name, prop)
}
}
class ScalarValue extends AstNode instanceof ScalarValueImpl {
@@ -121,6 +125,10 @@ class ReusableWorkflow extends Workflow instanceof ReusableWorkflowImpl {
class Input extends AstNode instanceof InputImpl { }
class Default extends AstNode instanceof DefaultsImpl {
ScalarValue getValue(string name, string prop) { result = super.getValue(name, prop) }
}
class Outputs extends AstNode instanceof OutputsImpl {
Expression getAnOutputExpr() { result = super.getAnOutputExpr() }
@@ -286,14 +294,18 @@ class ExternalJob extends Job, Uses instanceof ExternalJobImpl { }
* See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun.
*/
class Run extends Step instanceof RunImpl {
string getScript() { result = super.getScript() }
ScalarValue getScriptScalar() { result = super.getScriptScalar() }
ShellScript getScript() { result = super.getScript() }
Expression getAnScriptExpr() { result = super.getAnScriptExpr() }
string getWorkingDirectory() { result = super.getWorkingDirectory() }
string getShell() { result = super.getShell() }
}
class ShellScript extends ScalarValueImpl instanceof ShellScriptImpl {
string getRawScript() { result = super.getRawScript() }
string getStmt(int i) { result = super.getStmt(i) }
string getAStmt() { result = super.getAStmt() }
@@ -302,19 +314,23 @@ class Run extends Step instanceof RunImpl {
string getACommand() { result = super.getACommand() }
predicate getAssignment(int i, string name, string value) { super.getAssignment(i, name, value) }
string getFileReadCommand(int i) { result = super.getFileReadCommand(i) }
predicate getAnAssignment(string name, string value) { super.getAnAssignment(name, value) }
string getAFileReadCommand() { result = super.getAFileReadCommand() }
predicate getAWriteToGitHubEnv(string name, string value) {
super.getAWriteToGitHubEnv(name, value)
predicate getAssignment(int i, string name, string data) { super.getAssignment(i, name, data) }
predicate getAnAssignment(string name, string data) { super.getAnAssignment(name, data) }
predicate getAWriteToGitHubEnv(string name, string data) {
super.getAWriteToGitHubEnv(name, data)
}
predicate getAWriteToGitHubOutput(string name, string value) {
super.getAWriteToGitHubOutput(name, value)
predicate getAWriteToGitHubOutput(string name, string data) {
super.getAWriteToGitHubOutput(name, data)
}
predicate getAWriteToGitHubPath(string value) { super.getAWriteToGitHubPath(value) }
predicate getAWriteToGitHubPath(string data) { super.getAWriteToGitHubPath(data) }
predicate getAnEnvReachingGitHubOutputWrite(string var, string output_field) {
super.getAnEnvReachingGitHubOutputWrite(var, output_field)
@@ -331,6 +347,18 @@ class Run extends Step instanceof RunImpl {
predicate getACmdReachingGitHubEnvWrite(string cmd, string output_field) {
super.getACmdReachingGitHubEnvWrite(cmd, output_field)
}
predicate getAnEnvReachingGitHubPathWrite(string var) {
super.getAnEnvReachingGitHubPathWrite(var)
}
predicate getACmdReachingGitHubPathWrite(string cmd) { super.getACmdReachingGitHubPathWrite(cmd) }
predicate fileToGitHubEnv(string path) { super.fileToGitHubEnv(path) }
predicate fileToGitHubOutput(string path) { super.fileToGitHubOutput(path) }
predicate fileToGitHubPath(string path) { super.fileToGitHubPath(path) }
}
abstract class SimpleReferenceExpression extends AstNode instanceof SimpleReferenceExpressionImpl {

View File

@@ -1,7 +1,303 @@
private import codeql.actions.Ast
private import codeql.Locations
import codeql.actions.config.Config
private import codeql.actions.security.ControlChecks
class BashShellScript extends ShellScript {
BashShellScript() {
exists(Run run |
this = run.getScript() and
run.getShell().matches("bash%")
)
}
private string lineProducer(int i) {
result = this.getRawScript().regexpReplaceAll("\\\\\\s*\n", "").splitAt("\n", i)
}
private predicate cmdSubstitutionReplacement(string cmdSubs, string id, int k) {
exists(string line | line = this.lineProducer(k) |
exists(int i, int j |
cmdSubs =
// $() cmd substitution
line.regexpFind("\\$\\((?:[^()]+|\\((?:[^()]+|\\([^()]*\\))*\\))*\\)", i, j)
.regexpReplaceAll("^\\$\\(", "")
.regexpReplaceAll("\\)$", "") and
id = "cmdsubs:" + k + ":" + i + ":" + j
)
or
exists(int i, int j |
// `...` cmd substitution
cmdSubs =
line.regexpFind("\\`[^\\`]+\\`", i, j)
.regexpReplaceAll("^\\`", "")
.regexpReplaceAll("\\`$", "") and
id = "cmd:" + k + ":" + i + ":" + j
)
)
}
private predicate rankedCmdSubstitutionReplacements(int i, string old, string new) {
old = rank[i](string old2 | this.cmdSubstitutionReplacement(old2, _, _) | old2) and
this.cmdSubstitutionReplacement(old, new, _)
}
private predicate doReplaceCmdSubstitutions(int line, int round, string old, string new) {
round = 0 and
old = this.lineProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doReplaceCmdSubstitutions(line, round - 1, old, middle) and
this.rankedCmdSubstitutionReplacements(round, target, replacement) and
new = middle.replaceAll(target, replacement)
)
}
private string cmdSubstitutedLineProducer(int i) {
// script lines where any command substitution has been replaced with a unique placeholder
result =
max(int round, string new |
this.doReplaceCmdSubstitutions(i, round, _, new)
|
new order by round
)
or
this.cmdSubstitutionReplacement(result, _, i)
}
private predicate quotedStringReplacement(string quotedStr, string id) {
exists(string line, int k | line = this.cmdSubstitutedLineProducer(k) |
exists(int i, int j |
// double quoted string
quotedStr = line.regexpFind("\"((?:[^\"\\\\]|\\\\.)*)\"", i, j) and
id =
"qstr:" + k + ":" + i + ":" + j + ":" + quotedStr.length() + ":" +
quotedStr.regexpReplaceAll("[^a-zA-Z0-9]", "")
)
or
exists(int i, int j |
// single quoted string
quotedStr = line.regexpFind("'((?:\\\\.|[^'\\\\])*)'", i, j) and
id =
"qstr:" + k + ":" + i + ":" + j + ":" + quotedStr.length() + ":" +
quotedStr.regexpReplaceAll("[^a-zA-Z0-9]", "")
)
)
}
private predicate rankedQuotedStringReplacements(int i, string old, string new) {
old = rank[i](string old2 | this.quotedStringReplacement(old2, _) | old2) and
this.quotedStringReplacement(old, new)
}
private predicate doReplaceQuotedStrings(int line, int round, string old, string new) {
round = 0 and
old = this.cmdSubstitutedLineProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doReplaceQuotedStrings(line, round - 1, old, middle) and
this.rankedQuotedStringReplacements(round, target, replacement) and
new = middle.replaceAll(target, replacement)
)
}
private string quotedStringLineProducer(int i) {
result =
max(int round, string new | this.doReplaceQuotedStrings(i, round, _, new) | new order by round)
}
private string stmtProducer(int i) {
result = this.quotedStringLineProducer(i).splitAt(Bash::splitSeparator()).trim() and
// when splitting the line with a separator that is not present, the result is the original line which may contain other separators
// we only one the split parts that do not contain any of the separators
not result.indexOf(Bash::splitSeparator()) > -1
}
private predicate doStmtRestoreQuotedStrings(int line, int round, string old, string new) {
round = 0 and
old = this.stmtProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doStmtRestoreQuotedStrings(line, round - 1, old, middle) and
this.rankedQuotedStringReplacements(round, target, replacement) and
new = middle.replaceAll(replacement, target)
)
}
private string restoredStmtQuotedStringLineProducer(int i) {
result =
max(int round, string new |
this.doStmtRestoreQuotedStrings(i, round, _, new)
|
new order by round
)
}
private predicate doStmtRestoreCmdSubstitutions(int line, int round, string old, string new) {
round = 0 and
old = this.restoredStmtQuotedStringLineProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doStmtRestoreCmdSubstitutions(line, round - 1, old, middle) and
this.rankedCmdSubstitutionReplacements(round, target, replacement) and
new = middle.replaceAll(replacement, target)
)
}
override string getStmt(int i) {
result =
max(int round, string new |
this.doStmtRestoreCmdSubstitutions(i, round, _, new)
|
new order by round
)
}
override string getAStmt() { result = this.getStmt(_) }
private string cmdProducer(int i) {
result = this.quotedStringLineProducer(i).splitAt(Bash::separator()).trim() and
// when splitting the line with a separator that is not present, the result is the original line which may contain other separators
// we only one the split parts that do not contain any of the separators
not result.indexOf(Bash::separator()) > -1
}
private predicate doCmdRestoreQuotedStrings(int line, int round, string old, string new) {
round = 0 and
old = this.cmdProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doCmdRestoreQuotedStrings(line, round - 1, old, middle) and
this.rankedQuotedStringReplacements(round, target, replacement) and
new = middle.replaceAll(replacement, target)
)
}
private string restoredCmdQuotedStringLineProducer(int i) {
result =
max(int round, string new |
this.doCmdRestoreQuotedStrings(i, round, _, new)
|
new order by round
)
}
private predicate doCmdRestoreCmdSubstitutions(int line, int round, string old, string new) {
round = 0 and
old = this.restoredCmdQuotedStringLineProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doCmdRestoreCmdSubstitutions(line, round - 1, old, middle) and
this.rankedCmdSubstitutionReplacements(round, target, replacement) and
new = middle.replaceAll(replacement, target)
)
}
string getCmd(int i) {
result =
max(int round, string new |
this.doCmdRestoreCmdSubstitutions(i, round, _, new)
|
new order by round
)
}
string getACmd() { result = this.getCmd(_) }
override string getCommand(int i) {
result = this.getCmd(i) and
// exclude variable declarations
not result.regexpMatch("^[a-zA-Z0-9\\-_]+=") and
// exclude the following keywords
not result =
[
"", "for", "in", "do", "done", "if", "then", "else", "elif", "fi", "while", "until", "case",
"esac", "{", "}"
]
}
override string getACommand() { result = this.getCommand(_) }
override string getFileReadCommand(int i) {
result = this.getStmt(i) and
result.matches(Bash::fileReadCommand() + "%")
}
override string getAFileReadCommand() { result = this.getFileReadCommand(_) }
override predicate getAssignment(int i, string name, string data) {
exists(string stmt |
stmt = this.getStmt(i) and
name = stmt.regexpCapture("^([a-zA-Z0-9\\-_]+)=.*", 1) and
data = stmt.regexpCapture("^[a-zA-Z0-9\\-_]+=(.*)", 1)
)
}
override predicate getAnAssignment(string name, string data) { this.getAssignment(_, name, data) }
override predicate getAWriteToGitHubEnv(string name, string data) {
exists(string raw |
Bash::extractFileWrite(this.getRawScript(), "GITHUB_ENV", raw) and
Bash::extractVariableAndValue(raw, name, data)
)
}
override predicate getAWriteToGitHubOutput(string name, string data) {
exists(string raw |
Bash::extractFileWrite(this.getRawScript(), "GITHUB_OUTPUT", raw) and
Bash::extractVariableAndValue(raw, name, data)
)
}
override predicate getAWriteToGitHubPath(string data) {
Bash::extractFileWrite(this.getRawScript(), "GITHUB_PATH", data)
}
override predicate getAnEnvReachingGitHubOutputWrite(string var, string output_field) {
Bash::envReachingGitHubFileWrite(this, var, "GITHUB_OUTPUT", output_field)
}
override predicate getACmdReachingGitHubOutputWrite(string cmd, string output_field) {
Bash::cmdReachingGitHubFileWrite(this, cmd, "GITHUB_OUTPUT", output_field)
}
override predicate getAnEnvReachingGitHubEnvWrite(string var, string output_field) {
Bash::envReachingGitHubFileWrite(this, var, "GITHUB_ENV", output_field)
}
override predicate getACmdReachingGitHubEnvWrite(string cmd, string output_field) {
Bash::cmdReachingGitHubFileWrite(this, cmd, "GITHUB_ENV", output_field)
}
override predicate getAnEnvReachingGitHubPathWrite(string var) {
Bash::envReachingGitHubFileWrite(this, var, "GITHUB_PATH", _)
}
override predicate getACmdReachingGitHubPathWrite(string cmd) {
Bash::cmdReachingGitHubFileWrite(this, cmd, "GITHUB_PATH", _)
}
override predicate fileToGitHubEnv(string path) {
Bash::fileToFileWrite(this, "GITHUB_ENV", path)
}
override predicate fileToGitHubOutput(string path) {
Bash::fileToFileWrite(this, "GITHUB_OUTPUT", path)
}
override predicate fileToGitHubPath(string path) {
Bash::fileToFileWrite(this, "GITHUB_PATH", path)
}
}
module Bash {
string stmtSeparator() { result = ";" }
@@ -23,7 +319,7 @@ module Bash {
result = pipeSeparator()
}
string partialFileContentCommand() { result = ["cat", "jq", "yq", "tail", "head"] }
string fileReadCommand() { result = ["<", "cat", "jq", "yq", "tail", "head"] }
/** Checks if expr is a bash command substitution */
bindingset[expr]
@@ -133,8 +429,7 @@ module Bash {
string script, string cmd, string file, string content, string filters
) {
exists(string regexp |
regexp =
"(?i)(echo|printf|write-output)\\s*(.*?)\\s*(>>|>|\\s*\\|\\s*tee\\s*(-a|--append)?)\\s*(\\S+)" and
regexp = "(?i)(echo|printf)\\s*(.*?)\\s*(>>|>|\\s*\\|\\s*tee\\s*(-a|--append)?)\\s*(\\S+)" and
cmd = script.regexpCapture(regexp, 1) and
file = trimQuotes(script.regexpCapture(regexp, 5)) and
filters = "" and
@@ -145,13 +440,12 @@ module Bash {
bindingset[script]
predicate singleLineWorkflowCmd(string script, string cmd, string key, string value) {
exists(string regexp |
regexp =
"(?i)(echo|printf|write-output)\\s*(['|\"])?::(set-[a-z]+)\\s*name\\s*=\\s*(.*?)::(.*)" and
regexp = "(?i)(echo|printf)\\s*(['|\"])?::(set-[a-z]+)\\s*name\\s*=\\s*(.*?)::(.*)" and
cmd = script.regexpCapture(regexp, 3) and
key = script.regexpCapture(regexp, 4) and
value = trimQuotes(script.regexpCapture(regexp, 5))
or
regexp = "(?i)(echo|printf|write-output)\\s*(['|\"])?::(add-[a-z]+)\\s*::(.*)" and
regexp = "(?i)(echo|printf)\\s*(['|\"])?::(add-[a-z]+)\\s*::(.*)" and
cmd = script.regexpCapture(regexp, 3) and
key = "" and
value = trimQuotes(script.regexpCapture(regexp, 4))
@@ -262,57 +556,38 @@ module Bash {
}
/** Writes the content of the file specified by `path` into a file pointed to by `file_var` */
predicate fileToFileWrite(Run run, string file_var, string path) {
predicate fileToFileWrite(BashShellScript script, string file_var, string path) {
exists(string regexp, string stmt, string file_expr |
regexp =
"(?i)(cat)\\s*" + "((?:(?!<<|<<-)[^>\n])+)\\s*" +
"(>>|>|\\s*\\|\\s*tee\\s*(-a|--append)?)\\s*" + "(\\S+)" and
stmt = run.getAStmt() and
stmt = script.getAStmt() and
file_expr = trimQuotes(stmt.regexpCapture(regexp, 5)) and
path = stmt.regexpCapture(regexp, 2) and
containsParameterExpansion(file_expr, file_var, _, _)
)
}
predicate fileToGitHubEnv(Run run, string path) { fileToFileWrite(run, "GITHUB_ENV", path) }
predicate fileToGitHubOutput(Run run, string path) { fileToFileWrite(run, "GITHUB_OUTPUT", path) }
predicate fileToGitHubPath(Run run, string path) { fileToFileWrite(run, "GITHUB_PATH", path) }
bindingset[snippet]
predicate outputsPartialFileContent(Run run, string snippet) {
// e.g.
// echo FOO=`yq '.foo' foo.yml` >> $GITHUB_ENV
// echo "FOO=$(<foo.txt)" >> $GITHUB_ENV
// yq '.foo' foo.yml >> $GITHUB_PATH
// cat foo.txt >> $GITHUB_PATH
exists(int i, string line, string cmd |
run.getStmt(i) = line and
line.indexOf(snippet.regexpReplaceAll("^\\$\\(", "").regexpReplaceAll("\\)$", "")) > -1 and
run.getCommand(i) = cmd and
cmd.indexOf(["<", Bash::partialFileContentCommand() + " "]) = 0
)
}
/**
* Holds if the Run scripts contains an access to an environment variable called `var`
* which value may get appended to the GITHUB_XXX special file
*/
predicate envReachingGitHubFileWrite(Run run, string var, string file_var, string field) {
predicate envReachingGitHubFileWrite(
BashShellScript script, string var, string file_var, string field
) {
exists(string file_write_value |
(
file_var = "GITHUB_ENV" and
run.getAWriteToGitHubEnv(field, file_write_value)
script.getAWriteToGitHubEnv(field, file_write_value)
or
file_var = "GITHUB_OUTPUT" and
run.getAWriteToGitHubOutput(field, file_write_value)
script.getAWriteToGitHubOutput(field, file_write_value)
or
file_var = "GITHUB_PATH" and
field = "PATH" and
run.getAWriteToGitHubPath(file_write_value)
script.getAWriteToGitHubPath(file_write_value)
) and
envReachingRunExpr(run, var, file_write_value)
envReachingRunExpr(script, var, file_write_value)
)
}
@@ -321,11 +596,11 @@ module Bash {
* Where the expression is a string captured from the Run's script.
*/
bindingset[expr]
predicate envReachingRunExpr(Run run, string var, string expr) {
predicate envReachingRunExpr(BashShellScript script, string var, string expr) {
exists(string var2, string value2 |
// VAR2=${VAR:-default} (var2=value2)
// echo "FIELD=${VAR2:-default}" >> $GITHUB_ENV (field, file_write_value)
run.getAnAssignment(var2, value2) and
script.getAnAssignment(var2, value2) and
containsParameterExpansion(value2, var, _, _) and
containsParameterExpansion(expr, var2, _, _)
)
@@ -339,33 +614,42 @@ module Bash {
* Holds if the Run scripts contains a command substitution (`cmd`)
* which output may get appended to the GITHUB_XXX special file
*/
predicate cmdReachingGitHubFileWrite(Run run, string cmd, string file_var, string field) {
predicate cmdReachingGitHubFileWrite(
BashShellScript script, string cmd, string file_var, string field
) {
exists(string file_write_value |
(
file_var = "GITHUB_ENV" and
run.getAWriteToGitHubEnv(field, file_write_value)
script.getAWriteToGitHubEnv(field, file_write_value)
or
file_var = "GITHUB_OUTPUT" and
run.getAWriteToGitHubOutput(field, file_write_value)
script.getAWriteToGitHubOutput(field, file_write_value)
or
file_var = "GITHUB_PATH" and
field = "PATH" and
run.getAWriteToGitHubPath(file_write_value)
script.getAWriteToGitHubPath(file_write_value)
) and
(
// cmd output is assigned to a second variable (var2) and var2 reaches the file write
exists(string var2, string value2 |
// VAR2=$(cmd)
// echo "FIELD=${VAR2:-default}" >> $GITHUB_ENV (field, file_write_value)
run.getAnAssignment(var2, value2) and
containsCmdSubstitution(value2, cmd) and
containsParameterExpansion(file_write_value, var2, _, _)
)
or
// var reaches the file write directly
// echo "FIELD=$(cmd)" >> $GITHUB_ENV (field, file_write_value)
containsCmdSubstitution(file_write_value, cmd)
)
cmdReachingRunExpr(script, cmd, file_write_value)
)
}
/**
* Holds if a command output is used, directly or indirectly, in a Run's step expression.
* Where the expression is a string captured from the Run's script.
*/
bindingset[expr]
predicate cmdReachingRunExpr(BashShellScript script, string cmd, string expr) {
// cmd output is assigned to a second variable (var2) and var2 reaches the file write
exists(string var2, string value2 |
// VAR2=$(cmd)
// echo "FIELD=${VAR2:-default}" >> $GITHUB_ENV (field, file_write_value)
script.getAnAssignment(var2, value2) and
containsCmdSubstitution(value2, cmd) and
containsParameterExpansion(expr, var2, _, _)
)
or
// var reaches the file write directly
// echo "FIELD=$(cmd)" >> $GITHUB_ENV (field, file_write_value)
containsCmdSubstitution(expr, cmd)
}
}

View File

@@ -3,6 +3,7 @@ private import codeql.Locations
private import codeql.actions.security.ControlChecks
import codeql.actions.config.Config
import codeql.actions.Bash
import codeql.actions.PowerShell
bindingset[expr]
string normalizeExpr(string expr) {

View File

@@ -0,0 +1,50 @@
private import codeql.actions.Ast
class PowerShellScript extends ShellScript {
PowerShellScript() {
exists(Run run |
this = run.getScript() and
run.getShell().matches("pwsh%")
)
}
override string getStmt(int i) { none() }
override string getAStmt() { none() }
override string getCommand(int i) { none() }
override string getACommand() { none() }
override string getFileReadCommand(int i) { none() }
override string getAFileReadCommand() { none() }
override predicate getAssignment(int i, string name, string data) { none() }
override predicate getAnAssignment(string name, string data) { none() }
override predicate getAWriteToGitHubEnv(string name, string data) { none() }
override predicate getAWriteToGitHubOutput(string name, string data) { none() }
override predicate getAWriteToGitHubPath(string data) { none() }
override predicate getAnEnvReachingGitHubOutputWrite(string var, string output_field) { none() }
override predicate getACmdReachingGitHubOutputWrite(string cmd, string output_field) { none() }
override predicate getAnEnvReachingGitHubEnvWrite(string var, string output_field) { none() }
override predicate getACmdReachingGitHubEnvWrite(string cmd, string output_field) { none() }
override predicate getAnEnvReachingGitHubPathWrite(string var) { none() }
override predicate getACmdReachingGitHubPathWrite(string cmd) { none() }
override predicate fileToGitHubEnv(string path) { none() }
override predicate fileToGitHubOutput(string path) { none() }
override predicate fileToGitHubPath(string path) { none() }
}

View File

@@ -62,6 +62,7 @@ private newtype TAstNode =
n.lookup("jobs") instanceof YamlMapping
} or
TRunsNode(YamlMapping n) { exists(CompositeActionImpl a | a.getNode().lookup("runs") = n) } or
TDefaultsNode(YamlMapping n) { exists(YamlMapping m | m.lookup("defaults") = n) } or
TInputsNode(YamlMapping n) { exists(YamlMapping m | m.lookup("inputs") = n) } or
TInputNode(YamlValue n) { exists(YamlMapping m | m.lookup("inputs").(YamlMapping).maps(n, _)) } or
TOutputsNode(YamlMapping n) { exists(YamlMapping m | m.lookup("outputs") = n) } or
@@ -141,6 +142,19 @@ abstract class AstNodeImpl extends TAstNode {
env.getParentNode().getAChildNode*() = this
)
}
ScalarValueImpl getInScopeDefaultValue(string name, string prop) {
exists(DefaultsImpl dft |
this.getEnclosingJob().getNode().(YamlMapping).maps(_, dft.getNode()) and
result = dft.getValue(name, prop)
)
or
not exists(DefaultsImpl dft | this.getEnclosingJob() = dft.getParentNode()) and
exists(DefaultsImpl dft |
this.getEnclosingWorkflow().getNode().(YamlMapping).maps(_, dft.getNode()) and
result = dft.getValue(name, prop)
)
}
}
class ScalarValueImpl extends AstNodeImpl, TScalarValueNode {
@@ -165,6 +179,61 @@ class ScalarValueImpl extends AstNodeImpl, TScalarValueNode {
string getValue() { result = value.getValue() }
}
class ShellScriptImpl extends ScalarValueImpl {
ShellScriptImpl() { exists(YamlMapping run | run.lookup("run").(YamlScalar) = this.getNode()) }
string getRawScript() { result = this.getValue().regexpReplaceAll("\\\\\\s*\n", "") }
RunImpl getEnclosingRun() { result.getNode().lookup("run") = this.getNode() }
abstract string getStmt(int i);
abstract string getAStmt();
abstract string getCommand(int i);
string getACommand() {
if this.getEnclosingRun().getShell().matches("bash%")
then result = this.(BashShellScript).getACommand()
else
if this.getEnclosingRun().getShell().matches("pwsh%")
then result = this.(PowerShellScript).getACommand()
else result = "NOT IMPLEMENTED"
}
abstract string getFileReadCommand(int i);
abstract string getAFileReadCommand();
abstract predicate getAssignment(int i, string name, string data);
abstract predicate getAnAssignment(string name, string data);
abstract predicate getAWriteToGitHubEnv(string name, string data);
abstract predicate getAWriteToGitHubOutput(string name, string data);
abstract predicate getAWriteToGitHubPath(string data);
abstract predicate getAnEnvReachingGitHubOutputWrite(string var, string output_field);
abstract predicate getACmdReachingGitHubOutputWrite(string cmd, string output_field);
abstract predicate getAnEnvReachingGitHubEnvWrite(string var, string output_field);
abstract predicate getACmdReachingGitHubEnvWrite(string cmd, string output_field);
abstract predicate getAnEnvReachingGitHubPathWrite(string var);
abstract predicate getACmdReachingGitHubPathWrite(string cmd);
abstract predicate fileToGitHubEnv(string path);
abstract predicate fileToGitHubOutput(string path);
abstract predicate fileToGitHubPath(string path);
}
class ExpressionImpl extends AstNodeImpl, TExpressionNode {
YamlNode key;
YamlString value;
@@ -493,6 +562,28 @@ class InputsImpl extends AstNodeImpl, TInputsNode {
}
}
class DefaultsImpl extends AstNodeImpl, TDefaultsNode {
YamlMapping n;
DefaultsImpl() { this = TDefaultsNode(n) }
override string toString() { result = n.toString() }
override AstNodeImpl getAChildNode() { result.getNode() = n.getAChildNode*() }
override AstNodeImpl getParentNode() { result.getAChildNode() = this }
override string getAPrimaryQlClass() { result = "DefaultsImpl" }
override Location getLocation() { result = n.getLocation() }
override YamlMapping getNode() { result = n }
ScalarValueImpl getValue(string name, string prop) {
n.lookup(name).(YamlMapping).lookup(prop) = result.getNode()
}
}
class InputImpl extends AstNodeImpl, TInputNode {
YamlValue n;
@@ -1314,20 +1405,18 @@ class ExternalJobImpl extends JobImpl, UsesImpl {
class RunImpl extends StepImpl {
YamlScalar script;
ScalarValueImpl scriptScalar;
RunImpl() { this.getNode().lookup("run") = script }
string getScript() { result = script.getValue().regexpReplaceAll("\\\\\\s*\n", "") }
ScalarValueImpl getScriptScalar() { result = TScalarValueNode(script) }
ExpressionImpl getAnScriptExpr() { result.getParentNode().getNode() = script }
RunImpl() {
this.getNode().lookup("run") = script and
scriptScalar = TScalarValueNode(script)
}
override string toString() {
if exists(this.getId()) then result = "Run Step: " + this.getId() else result = "Run Step"
}
/** Gets the working directory for this `runs` mapping. */
/** Gets the working directory for this `run` mapping. */
string getWorkingDirectory() {
if exists(n.lookup("working-directory").(YamlString).getValue())
then
@@ -1339,268 +1428,19 @@ class RunImpl extends StepImpl {
else result = "GITHUB_WORKSPACE/"
}
private string lineProducer(int i) {
result = script.getValue().regexpReplaceAll("\\\\\\s*\n", "").splitAt("\n", i)
/** Gets the shell for this `run` mapping. */
string getShell() {
if exists(n.lookup("shell").(YamlString).getValue())
then result = n.lookup("shell").(YamlString).getValue()
else
if exists(this.getInScopeDefaultValue("run", "shell"))
then result = this.getInScopeDefaultValue("run", "shell").getValue()
else result = "bash"
}
private predicate cmdSubstitutionReplacement(string cmdSubs, string id, int k) {
exists(string line | line = this.lineProducer(k) |
exists(int i, int j |
cmdSubs =
// $() cmd substitution
line.regexpFind("\\$\\((?:[^()]+|\\((?:[^()]+|\\([^()]*\\))*\\))*\\)", i, j)
.regexpReplaceAll("^\\$\\(", "")
.regexpReplaceAll("\\)$", "") and
id = "cmdsubs:" + k + ":" + i + ":" + j
)
or
exists(int i, int j |
// `...` cmd substitution
cmdSubs =
line.regexpFind("\\`[^\\`]+\\`", i, j)
.regexpReplaceAll("^\\`", "")
.regexpReplaceAll("\\`$", "") and
id = "cmd:" + k + ":" + i + ":" + j
)
)
}
ShellScriptImpl getScript() { result = scriptScalar }
private predicate rankedCmdSubstitutionReplacements(int i, string old, string new) {
old = rank[i](string old2 | this.cmdSubstitutionReplacement(old2, _, _) | old2) and
this.cmdSubstitutionReplacement(old, new, _)
}
private predicate doReplaceCmdSubstitutions(int line, int round, string old, string new) {
round = 0 and
old = this.lineProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doReplaceCmdSubstitutions(line, round - 1, old, middle) and
this.rankedCmdSubstitutionReplacements(round, target, replacement) and
new = middle.replaceAll(target, replacement)
)
}
private string cmdSubstitutedLineProducer(int i) {
// script lines where any command substitution has been replaced with a unique placeholder
result =
max(int round, string new |
this.doReplaceCmdSubstitutions(i, round, _, new)
|
new order by round
)
or
this.cmdSubstitutionReplacement(result, _, i)
}
private predicate quotedStringReplacement(string quotedStr, string id) {
exists(string line, int k | line = this.cmdSubstitutedLineProducer(k) |
exists(int i, int j |
// double quoted string
quotedStr = line.regexpFind("\"((?:[^\"\\\\]|\\\\.)*)\"", i, j) and
id =
"qstr:" + k + ":" + i + ":" + j + ":" + quotedStr.length() + ":" +
quotedStr.regexpReplaceAll("[^a-zA-Z0-9]", "")
)
or
exists(int i, int j |
// single quoted string
quotedStr = line.regexpFind("'((?:\\\\.|[^'\\\\])*)'", i, j) and
id =
"qstr:" + k + ":" + i + ":" + j + ":" + quotedStr.length() + ":" +
quotedStr.regexpReplaceAll("[^a-zA-Z0-9]", "")
)
)
}
private predicate rankedQuotedStringReplacements(int i, string old, string new) {
old = rank[i](string old2 | this.quotedStringReplacement(old2, _) | old2) and
this.quotedStringReplacement(old, new)
}
private predicate doReplaceQuotedStrings(int line, int round, string old, string new) {
round = 0 and
old = this.cmdSubstitutedLineProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doReplaceQuotedStrings(line, round - 1, old, middle) and
this.rankedQuotedStringReplacements(round, target, replacement) and
new = middle.replaceAll(target, replacement)
)
}
private string quotedStringLineProducer(int i) {
result =
max(int round, string new | this.doReplaceQuotedStrings(i, round, _, new) | new order by round)
}
private string stmtProducer(int i) {
result = this.quotedStringLineProducer(i).splitAt(Bash::splitSeparator()).trim() and
// when splitting the line with a separator that is not present, the result is the original line which may contain other separators
// we only one the split parts that do not contain any of the separators
not result.indexOf(Bash::splitSeparator()) > -1
}
private predicate doStmtRestoreQuotedStrings(int line, int round, string old, string new) {
round = 0 and
old = this.stmtProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doStmtRestoreQuotedStrings(line, round - 1, old, middle) and
this.rankedQuotedStringReplacements(round, target, replacement) and
new = middle.replaceAll(replacement, target)
)
}
private string restoredStmtQuotedStringLineProducer(int i) {
result =
max(int round, string new |
this.doStmtRestoreQuotedStrings(i, round, _, new)
|
new order by round
)
}
private predicate doStmtRestoreCmdSubstitutions(int line, int round, string old, string new) {
round = 0 and
old = this.restoredStmtQuotedStringLineProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doStmtRestoreCmdSubstitutions(line, round - 1, old, middle) and
this.rankedCmdSubstitutionReplacements(round, target, replacement) and
new = middle.replaceAll(replacement, target)
)
}
string getStmt(int i) {
result =
max(int round, string new |
this.doStmtRestoreCmdSubstitutions(i, round, _, new)
|
new order by round
)
}
string getAStmt() { result = this.getStmt(_) }
private string cmdProducer(int i) {
result = this.quotedStringLineProducer(i).splitAt(Bash::separator()).trim() and
// when splitting the line with a separator that is not present, the result is the original line which may contain other separators
// we only one the split parts that do not contain any of the separators
not result.indexOf(Bash::separator()) > -1
}
private predicate doCmdRestoreQuotedStrings(int line, int round, string old, string new) {
round = 0 and
old = this.cmdProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doCmdRestoreQuotedStrings(line, round - 1, old, middle) and
this.rankedQuotedStringReplacements(round, target, replacement) and
new = middle.replaceAll(replacement, target)
)
}
private string restoredCmdQuotedStringLineProducer(int i) {
result =
max(int round, string new |
this.doCmdRestoreQuotedStrings(i, round, _, new)
|
new order by round
)
}
private predicate doCmdRestoreCmdSubstitutions(int line, int round, string old, string new) {
round = 0 and
old = this.restoredCmdQuotedStringLineProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doCmdRestoreCmdSubstitutions(line, round - 1, old, middle) and
this.rankedCmdSubstitutionReplacements(round, target, replacement) and
new = middle.replaceAll(replacement, target)
)
}
string getCmd(int i) {
result =
max(int round, string new |
this.doCmdRestoreCmdSubstitutions(i, round, _, new)
|
new order by round
)
}
string getACmd() { result = this.getCmd(_) }
string getCommand(int i) {
result = this.getCmd(i) and
// exclude variable declarations
not result.regexpMatch("^[a-zA-Z0-9\\-_]+=") and
// exclude the following keywords
not result =
[
"", "for", "in", "do", "done", "if", "then", "else", "elif", "fi", "while", "until", "case",
"esac", "{", "}"
]
}
string getACommand() { result = this.getCommand(_) }
predicate getAssignment(int i, string name, string value) {
exists(string stmt |
stmt = this.getStmt(i) and
name = stmt.regexpCapture("^([a-zA-Z0-9\\-_]+)=.*", 1) and
value = stmt.regexpCapture("^[a-zA-Z0-9\\-_]+=(.*)", 1)
)
}
predicate getAnAssignment(string name, string value) { this.getAssignment(_, name, value) }
predicate getAWriteToGitHubEnv(string name, string value) {
exists(string raw |
Bash::extractFileWrite(this.getScript(), "GITHUB_ENV", raw) and
Bash::extractVariableAndValue(raw, name, value)
)
}
predicate getAWriteToGitHubOutput(string name, string value) {
exists(string raw |
Bash::extractFileWrite(this.getScript(), "GITHUB_OUTPUT", raw) and
Bash::extractVariableAndValue(raw, name, value)
)
}
predicate getAWriteToGitHubPath(string value) {
Bash::extractFileWrite(this.getScript(), "GITHUB_PATH", value)
}
predicate getAnEnvReachingGitHubOutputWrite(string var, string output_field) {
Bash::envReachingGitHubFileWrite(this, var, "GITHUB_OUTPUT", output_field)
}
predicate getACmdReachingGitHubOutputWrite(string cmd, string output_field) {
Bash::cmdReachingGitHubFileWrite(this, cmd, "GITHUB_OUTPUT", output_field)
}
predicate getAnEnvReachingGitHubEnvWrite(string var, string output_field) {
Bash::envReachingGitHubFileWrite(this, var, "GITHUB_ENV", output_field)
}
predicate getACmdReachingGitHubEnvWrite(string cmd, string output_field) {
Bash::cmdReachingGitHubFileWrite(this, cmd, "GITHUB_ENV", output_field)
}
ExpressionImpl getAnScriptExpr() { result.getParentNode().getNode() = script }
}
/**

View File

@@ -282,7 +282,7 @@ private class RunTree extends StandardPreOrderTree instanceof Run {
(
child = super.getInScopeEnvVarExpr(_) or
child = super.getAnScriptExpr() or
child = super.getScriptScalar()
child = super.getScript()
) and
l = child.getLocation()
|

View File

@@ -86,7 +86,8 @@ class GitCommandSource extends RemoteFlowSource, CommandSource {
exists(Uses uses |
checkout = uses and
uses.getCallee() = "actions/checkout" and
exists(uses.getArgument("ref"))
exists(uses.getArgument("ref")) and
not uses.getArgument("ref").matches("%base%")
)
or
checkout instanceof GitMutableRefCheckout
@@ -97,9 +98,9 @@ class GitCommandSource extends RemoteFlowSource, CommandSource {
or
checkout instanceof GhSHACheckout
) and
this.asExpr() = run.getScriptScalar() and
this.asExpr() = run.getScript() and
checkout.getAFollowingStep() = run and
run.getACommand() = cmd and
run.getScript().getACommand() = cmd and
cmd.indexOf("git") = 0 and
untrustedGitCommandsDataModel(cmd_regex, flag) and
cmd.regexpMatch(cmd_regex)
@@ -127,8 +128,8 @@ class GitHubEventPathSource extends RemoteFlowSource, CommandSource {
// PR_TITLE=$(jq --raw-output .pull_request.title ${GITHUB_EVENT_PATH})
// BODY=$(jq -r '.issue.body' "$GITHUB_EVENT_PATH" | sed -n '3p')
GitHubEventPathSource() {
this.asExpr() = run.getScriptScalar() and
run.getACommand() = cmd and
this.asExpr() = run.getScript() and
run.getScript().getACommand() = cmd and
cmd.matches("jq%") and
cmd.matches("%GITHUB_EVENT_PATH%") and
exists(string regexp |
@@ -207,10 +208,11 @@ private class CheckoutSource extends RemoteFlowSource, FileSource {
// but PRHeadCheckoutStep uses Taint Tracking anc causes a non-Monolitic Recursion error
// so we list all the subclasses of PRHeadCheckoutStep here and use actions/checkout as a workaround
// instead of using ActionsMutableRefCheckout and ActionsSHACheckout
exists(Uses u |
this.asExpr() = u and
u.getCallee() = "actions/checkout" and
exists(u.getArgument("ref"))
exists(Uses uses |
this.asExpr() = uses and
uses.getCallee() = "actions/checkout" and
exists(uses.getArgument("ref")) and
not uses.getArgument("ref").matches("%base%")
)
or
this.asExpr() instanceof GitMutableRefCheckout

View File

@@ -23,7 +23,7 @@ predicate envToOutputStoreStep(DataFlow::Node pred, DataFlow::Node succ, DataFlo
exists(Run run, string var, string field |
run.getInScopeEnvVarExpr(var) = pred.asExpr() and
succ.asExpr() = run and
Bash::envReachingGitHubFileWrite(run, var, "GITHUB_OUTPUT", field) and
run.getScript().getAnEnvReachingGitHubOutputWrite(var, field) and
c = any(DataFlow::FieldContent ct | ct.getName() = field)
)
}
@@ -35,8 +35,8 @@ predicate envToEnvStoreStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::
run.getInScopeEnvVarExpr(var) = pred.asExpr() and
// we store the taint on the enclosing job since the may not exist an implicit env attribute
succ.asExpr() = run.getEnclosingJob() and
Bash::envReachingGitHubFileWrite(run, var, "GITHUB_ENV", field) and
c = any(DataFlow::FieldContent ct | ct.getName() = field) //and
run.getScript().getAnEnvReachingGitHubEnvWrite(var, field) and
c = any(DataFlow::FieldContent ct | ct.getName() = field)
)
}
@@ -55,12 +55,12 @@ predicate commandToOutputStoreStep(DataFlow::Node pred, DataFlow::Node succ, Dat
or
exists(FileSource source |
source.asExpr().(Step).getAFollowingStep() = run and
Bash::outputsPartialFileContent(run, cmd)
run.getScript().getAFileReadCommand() = cmd
)
) and
Bash::cmdReachingGitHubFileWrite(run, cmd, "GITHUB_OUTPUT", key) and
run.getScript().getACmdReachingGitHubOutputWrite(cmd, key) and
c = any(DataFlow::FieldContent ct | ct.getName() = key) and
pred.asExpr() = run.getScriptScalar() and
pred.asExpr() = run.getScript() and
succ.asExpr() = run
)
}
@@ -80,12 +80,12 @@ predicate commandToEnvStoreStep(DataFlow::Node pred, DataFlow::Node succ, DataFl
or
exists(FileSource source |
source.asExpr().(Step).getAFollowingStep() = run and
Bash::outputsPartialFileContent(run, cmd)
run.getScript().getAFileReadCommand() = cmd
)
) and
Bash::cmdReachingGitHubFileWrite(run, cmd, "GITHUB_ENV", key) and
run.getScript().getACmdReachingGitHubEnvWrite(cmd, key) and
c = any(DataFlow::FieldContent ct | ct.getName() = key) and
pred.asExpr() = run.getScriptScalar() and
pred.asExpr() = run.getScript() and
// we store the taint on the enclosing job since there may not be an implicit env attribute
succ.asExpr() = run.getEnclosingJob()
)

View File

@@ -22,14 +22,14 @@ class AdditionalTaintStep extends Unit {
}
/**
* A download artifact step followed by a step that may use downloaded artifacts.
* A file source step followed by a Run step may read the file.
*/
predicate fileDownloadToRunStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(FileSource source, Run run |
pred = source and
source.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScriptScalar() and
Bash::outputsPartialFileContent(run, run.getACommand())
succ.asExpr() = run.getScript() and
exists(run.getScript().getAFileReadCommand())
)
}

View File

@@ -17,11 +17,11 @@ abstract class ArgumentInjectionSink extends DataFlow::Node {
bindingset[var]
predicate envToArgInjSink(string var, Run run, string command) {
exists(string argument, string cmd, string regexp, int command_group, int argument_group |
run.getACommand() = cmd and
run.getScript().getACommand() = cmd and
argumentInjectionSinksDataModel(regexp, command_group, argument_group) and
command = cmd.regexpCapture(regexp, command_group) and
argument = cmd.regexpCapture(regexp, argument_group) and
Bash::envReachingRunExpr(run, var, argument) and
Bash::envReachingRunExpr(run.getScript(), var, argument) and
exists(run.getInScopeEnvVarExpr(var))
)
}
@@ -40,15 +40,15 @@ class ArgumentInjectionFromEnvVarSink extends ArgumentInjectionSink {
ArgumentInjectionFromEnvVarSink() {
exists(Run run, string var |
envToArgInjSink(var, run, command) and
run.getScriptScalar() = this.asExpr() and
run.getScript() = this.asExpr() and
exists(run.getInScopeEnvVarExpr(var))
)
or
exists(
Run run, string cmd, string argument, string regexp, int argument_group, int command_group
|
run.getACommand() = cmd and
run.getScriptScalar() = this.asExpr() and
run.getScript().getACommand() = cmd and
run.getScript() = this.asExpr() and
argumentInjectionSinksDataModel(regexp, command_group, argument_group) and
argument = cmd.regexpCapture(regexp, argument_group) and
command = cmd.regexpCapture(regexp, command_group) and
@@ -75,8 +75,8 @@ class ArgumentInjectionFromCommandSink extends ArgumentInjectionSink {
int command_group
|
run = source.getEnclosingRun() and
this.asExpr() = run.getScriptScalar() and
cmd = run.getACommand() and
this.asExpr() = run.getScript() and
cmd = run.getScript().getACommand() and
argumentInjectionSinksDataModel(regexp, command_group, argument_group) and
argument = cmd.regexpCapture(regexp, argument_group) and
command = cmd.regexpCapture(regexp, command_group)
@@ -106,8 +106,8 @@ private module ArgumentInjectionConfig implements DataFlow::ConfigSig {
exists(
Run run, string argument, string cmd, string regexp, int command_group, int argument_group
|
run.getScriptScalar() = source.asExpr() and
run.getACommand() = cmd and
run.getScript() = source.asExpr() and
run.getScript().getACommand() = cmd and
argumentInjectionSinksDataModel(regexp, command_group, argument_group) and
argument = cmd.regexpCapture(regexp, argument_group) and
argument.regexpMatch(".*\\$(\\{)?(GITHUB_HEAD_REF).*")
@@ -119,7 +119,7 @@ private module ArgumentInjectionConfig implements DataFlow::ConfigSig {
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(Run run, string var |
run.getInScopeEnvVarExpr(var) = pred.asExpr() and
succ.asExpr() = run.getScriptScalar() and
succ.asExpr() = run.getScript() and
envToArgInjSink(var, run, _)
)
}

View File

@@ -155,15 +155,21 @@ class ActionsGitHubScriptDownloadStep extends UntrustedArtifactDownloadStep, Use
}
override string getPath() {
if this.getAFollowingStep().(Run).getACommand().regexpMatch(unzipRegexp() + unzipDirArgRegexp())
if
this.getAFollowingStep()
.(Run)
.getScript()
.getACommand()
.regexpMatch(unzipRegexp() + unzipDirArgRegexp())
then
result =
normalizePath(trimQuotes(this.getAFollowingStep()
.(Run)
.getScript()
.getACommand()
.regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 2)))
else
if this.getAFollowingStep().(Run).getACommand().regexpMatch(unzipRegexp())
if this.getAFollowingStep().(Run).getScript().getACommand().regexpMatch(unzipRegexp())
then result = "GITHUB_WORKSPACE/"
else none()
}
@@ -172,31 +178,37 @@ class ActionsGitHubScriptDownloadStep extends UntrustedArtifactDownloadStep, Use
class GHRunArtifactDownloadStep extends UntrustedArtifactDownloadStep, Run {
GHRunArtifactDownloadStep() {
// eg: - run: gh run download ${{ github.event.workflow_run.id }} --repo "${GITHUB_REPOSITORY}" --name "artifact_name"
this.getACommand().regexpMatch(".*gh\\s+run\\s+download.*") and
this.getACommand().matches("%github.event.workflow_run.id%") and
this.getScript().getACommand().regexpMatch(".*gh\\s+run\\s+download.*") and
this.getScript().getACommand().matches("%github.event.workflow_run.id%") and
(
this.getACommand().regexpMatch(unzipRegexp()) or
this.getAFollowingStep().(Run).getACommand().regexpMatch(unzipRegexp())
this.getScript().getACommand().regexpMatch(unzipRegexp()) or
this.getAFollowingStep().(Run).getScript().getACommand().regexpMatch(unzipRegexp())
)
}
override string getPath() {
if
this.getAFollowingStep().(Run).getACommand().regexpMatch(unzipRegexp() + unzipDirArgRegexp()) or
this.getACommand().regexpMatch(unzipRegexp() + unzipDirArgRegexp())
this.getAFollowingStep()
.(Run)
.getScript()
.getACommand()
.regexpMatch(unzipRegexp() + unzipDirArgRegexp()) or
this.getScript().getACommand().regexpMatch(unzipRegexp() + unzipDirArgRegexp())
then
result =
normalizePath(trimQuotes(this.getACommand()
normalizePath(trimQuotes(this.getScript()
.getACommand()
.regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 2))) or
result =
normalizePath(trimQuotes(this.getAFollowingStep()
.(Run)
.getScript()
.getACommand()
.regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 2)))
else
if
this.getAFollowingStep().(Run).getACommand().regexpMatch(unzipRegexp()) or
this.getACommand().regexpMatch(unzipRegexp())
this.getAFollowingStep().(Run).getScript().getACommand().regexpMatch(unzipRegexp()) or
this.getScript().getACommand().regexpMatch(unzipRegexp())
then result = "GITHUB_WORKSPACE/"
else none()
}
@@ -213,24 +225,30 @@ class DirectArtifactDownloadStep extends UntrustedArtifactDownloadStep, Run {
// gh api $url > "$name.zip"
// unzip -d "$name" "$name.zip"
// done
this.getACommand().matches("%github.event.workflow_run.artifacts_url%") and
this.getScript().getACommand().matches("%github.event.workflow_run.artifacts_url%") and
(
this.getACommand().regexpMatch(unzipRegexp()) or
this.getAFollowingStep().(Run).getACommand().regexpMatch(unzipRegexp())
this.getScript().getACommand().regexpMatch(unzipRegexp()) or
this.getAFollowingStep().(Run).getScript().getACommand().regexpMatch(unzipRegexp())
)
}
override string getPath() {
if
this.getACommand().regexpMatch(unzipRegexp() + unzipDirArgRegexp()) or
this.getAFollowingStep().(Run).getACommand().regexpMatch(unzipRegexp() + unzipDirArgRegexp())
this.getScript().getACommand().regexpMatch(unzipRegexp() + unzipDirArgRegexp()) or
this.getAFollowingStep()
.(Run)
.getScript()
.getACommand()
.regexpMatch(unzipRegexp() + unzipDirArgRegexp())
then
result =
normalizePath(trimQuotes(this.getACommand()
normalizePath(trimQuotes(this.getScript()
.getACommand()
.regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 2))) or
result =
normalizePath(trimQuotes(this.getAFollowingStep()
.(Run)
.getScript()
.getACommand()
.regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 2)))
else result = "GITHUB_WORKSPACE/"
@@ -246,7 +264,7 @@ class ArtifactPoisoningSink extends DataFlow::Node {
// excluding artifacts downloaded to /tmp
not download.getPath().regexpMatch("^/tmp.*") and
(
poisonable.(Run).getScriptScalar() = this.asExpr() and
poisonable.(Run).getScript() = this.asExpr() and
(
// Check if the poisonable step is a local script execution step
// and the path of the command or script matches the path of the downloaded artifact
@@ -280,7 +298,7 @@ private module ArtifactPoisoningConfig implements DataFlow::ConfigSig {
pred instanceof ArtifactSource and
pred.asExpr().(Step).getAFollowingStep() = step and
(
succ.asExpr() = step.(Run).getScriptScalar() or
succ.asExpr() = step.(Run).getScript() or
succ.asExpr() = step.(UsesStep)
)
)
@@ -288,8 +306,8 @@ private module ArtifactPoisoningConfig implements DataFlow::ConfigSig {
exists(Run run |
pred instanceof ArtifactSource and
pred.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScriptScalar() and
Bash::outputsPartialFileContent(run, run.getACommand())
succ.asExpr() = run.getScript() and
exists(run.getScript().getAFileReadCommand())
)
}
}

View File

@@ -31,8 +31,8 @@ private module CodeInjectionConfig implements DataFlow::ConfigSig {
exists(Run run |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScriptScalar() and
Bash::outputsPartialFileContent(run, run.getACommand())
succ.asExpr() = run.getScript() and
exists(run.getScript().getAFileReadCommand())
)
}
}

View File

@@ -283,8 +283,8 @@ class BashCommentVsHeadDateCheck extends CommentVsHeadDateCheck, Run {
BashCommentVsHeadDateCheck() {
// eg: if [[ $(date -d "$pushed_at" +%s) -gt $(date -d "$COMMENT_AT" +%s) ]]; then
exists(string cmd1, string cmd2 |
cmd1 = this.getACommand() and
cmd2 = this.getACommand() and
cmd1 = this.getScript().getACommand() and
cmd2 = this.getScript().getACommand() and
not cmd1 = cmd2 and
cmd1.toLowerCase().regexpMatch("date\\s+-d.*(commit|pushed|comment|commented)_at.*") and
cmd2.toLowerCase().regexpMatch("date\\s+-d.*(commit|pushed|comment|commented)_at.*")

View File

@@ -25,15 +25,15 @@ class EnvPathInjectionFromFileReadSink extends EnvPathInjectionSink {
step instanceof UntrustedArtifactDownloadStep or
step instanceof PRHeadCheckoutStep
) and
this.asExpr() = run.getScriptScalar() and
this.asExpr() = run.getScript() and
step.getAFollowingStep() = run and
(
exists(string cmd |
Bash::cmdReachingGitHubFileWrite(run, cmd, "GITHUB_PATH", _) and
Bash::outputsPartialFileContent(run, cmd)
run.getScript().getACmdReachingGitHubPathWrite(cmd) and
run.getScript().getAFileReadCommand() = cmd
)
or
Bash::fileToGitHubPath(run, _)
run.getScript().fileToGitHubPath(_)
)
)
}
@@ -49,9 +49,8 @@ class EnvPathInjectionFromFileReadSink extends EnvPathInjectionSink {
class EnvPathInjectionFromCommandSink extends EnvPathInjectionSink {
EnvPathInjectionFromCommandSink() {
exists(CommandSource source |
this.asExpr() = source.getEnclosingRun().getScriptScalar() and
Bash::cmdReachingGitHubFileWrite(source.getEnclosingRun(), source.getCommand(), "GITHUB_PATH",
_)
this.asExpr() = source.getEnclosingRun().getScript() and
source.getEnclosingRun().getScript().getACmdReachingGitHubPathWrite(source.getCommand())
)
}
}
@@ -67,9 +66,9 @@ class EnvPathInjectionFromCommandSink extends EnvPathInjectionSink {
class EnvPathInjectionFromEnvVarSink extends EnvPathInjectionSink {
EnvPathInjectionFromEnvVarSink() {
exists(Run run, string var_name |
Bash::envReachingGitHubFileWrite(run, var_name, "GITHUB_PATH", _) and
run.getScript().getAnEnvReachingGitHubPathWrite(var_name) and
exists(run.getInScopeEnvVarExpr(var_name)) and
run.getScriptScalar() = this.asExpr()
run.getScript() = this.asExpr()
)
}
}
@@ -90,8 +89,12 @@ private module EnvPathInjectionConfig implements DataFlow::ConfigSig {
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(Run run, string var |
run.getInScopeEnvVarExpr(var) = pred.asExpr() and
succ.asExpr() = run.getScriptScalar() and
Bash::envReachingGitHubFileWrite(run, var, ["GITHUB_ENV", "GITHUB_OUTPUT", "GITHUB_PATH"], _)
succ.asExpr() = run.getScript() and
(
run.getScript().getAnEnvReachingGitHubOutputWrite(var, _) or
run.getScript().getAnEnvReachingGitHubEnvWrite(var, _) or
run.getScript().getAnEnvReachingGitHubPathWrite(var)
)
)
or
exists(Uses step |
@@ -104,8 +107,8 @@ private module EnvPathInjectionConfig implements DataFlow::ConfigSig {
exists(Run run |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScriptScalar() and
Bash::outputsPartialFileContent(run, run.getACommand())
succ.asExpr() = run.getScript() and
exists(run.getScript().getAFileReadCommand())
)
}
}

View File

@@ -28,15 +28,15 @@ class EnvVarInjectionFromFileReadSink extends EnvVarInjectionSink {
step instanceof UntrustedArtifactDownloadStep or
step instanceof PRHeadCheckoutStep
) and
this.asExpr() = run.getScriptScalar() and
this.asExpr() = run.getScript() and
step.getAFollowingStep() = run and
(
exists(string cmd |
Bash::cmdReachingGitHubFileWrite(run, cmd, "GITHUB_ENV", _) and
Bash::outputsPartialFileContent(run, cmd)
run.getScript().getACmdReachingGitHubEnvWrite(cmd, _) and
run.getScript().getAFileReadCommand() = cmd
)
or
Bash::fileToGitHubEnv(run, _)
run.getScript().fileToGitHubEnv(_)
)
)
}
@@ -52,9 +52,8 @@ class EnvVarInjectionFromFileReadSink extends EnvVarInjectionSink {
class EnvVarInjectionFromCommandSink extends EnvVarInjectionSink {
EnvVarInjectionFromCommandSink() {
exists(CommandSource source |
this.asExpr() = source.getEnclosingRun().getScriptScalar() and
Bash::cmdReachingGitHubFileWrite(source.getEnclosingRun(), source.getCommand(), "GITHUB_ENV",
_)
this.asExpr() = source.getEnclosingRun().getScript() and
source.getEnclosingRun().getScript().getACmdReachingGitHubEnvWrite(source.getCommand(), _)
)
}
}
@@ -71,8 +70,8 @@ class EnvVarInjectionFromEnvVarSink extends EnvVarInjectionSink {
EnvVarInjectionFromEnvVarSink() {
exists(Run run, string var_name |
exists(run.getInScopeEnvVarExpr(var_name)) and
run.getScriptScalar() = this.asExpr() and
Bash::envReachingGitHubFileWrite(run, var_name, "GITHUB_ENV", _)
run.getScript() = this.asExpr() and
run.getScript().getAnEnvReachingGitHubEnvWrite(var_name, _)
)
}
}
@@ -109,8 +108,12 @@ private module EnvVarInjectionConfig implements DataFlow::ConfigSig {
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(Run run, string var |
run.getInScopeEnvVarExpr(var) = pred.asExpr() and
succ.asExpr() = run.getScriptScalar() and
Bash::envReachingGitHubFileWrite(run, var, ["GITHUB_ENV", "GITHUB_OUTPUT", "GITHUB_PATH"], _)
succ.asExpr() = run.getScript() and
(
run.getScript().getAnEnvReachingGitHubEnvWrite(var, _)
or
run.getScript().getAnEnvReachingGitHubOutputWrite(var, _)
)
)
or
exists(Uses step |
@@ -123,8 +126,8 @@ private module EnvVarInjectionConfig implements DataFlow::ConfigSig {
exists(Run run |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScriptScalar() and
Bash::outputsPartialFileContent(run, run.getACommand())
succ.asExpr() = run.getScript() and
exists(run.getScript().getAFileReadCommand())
)
}
}

View File

@@ -19,7 +19,7 @@ abstract class OutputClobberingSink extends DataFlow::Node { }
*/
class OutputClobberingFromFileReadSink extends OutputClobberingSink {
OutputClobberingFromFileReadSink() {
exists(Run run, Step step |
exists(Run run, Step step, string field1, string field2 |
(
step instanceof UntrustedArtifactDownloadStep
or
@@ -31,7 +31,8 @@ class OutputClobberingFromFileReadSink extends OutputClobberingSink {
exists(Uses uses |
step = uses and
uses.getCallee() = "actions/checkout" and
exists(uses.getArgument("ref"))
exists(uses.getArgument("ref")) and
not uses.getArgument("ref").matches("%base%")
)
or
step instanceof GitMutableRefCheckout
@@ -43,14 +44,28 @@ class OutputClobberingFromFileReadSink extends OutputClobberingSink {
step instanceof GhSHACheckout
) and
step.getAFollowingStep() = run and
this.asExpr() = run.getScriptScalar() and
this.asExpr() = run.getScript() and
// A write to GITHUB_OUTPUT that is not attacker-controlled
exists(string str |
// The output of a command that is not a file read command
run.getScript().getACmdReachingGitHubOutputWrite(str, field1) and
not str = run.getScript().getAFileReadCommand()
or
// A hard-coded string
run.getScript().getAWriteToGitHubOutput(field1, str) and
str.regexpMatch("[\"'0-9a-zA-Z_\\-]+")
) and
// A write to GITHUB_OUTPUT that is attacker-controlled
(
// echo "sha=$(<test-results/sha-number)" >> $GITHUB_OUTPUT
exists(string cmd |
Bash::cmdReachingGitHubFileWrite(run, cmd, "GITHUB_OUTPUT", _) and
Bash::outputsPartialFileContent(run, cmd)
run.getScript().getACmdReachingGitHubOutputWrite(cmd, field2) and
run.getScript().getAFileReadCommand() = cmd
)
or
Bash::fileToGitHubOutput(run, _)
// cat test-results/.vars >> $GITHUB_OUTPUT
run.getScript().fileToGitHubOutput(_) and
field2 = "UNKNOWN"
)
)
}
@@ -66,16 +81,24 @@ class OutputClobberingFromFileReadSink extends OutputClobberingSink {
*/
class OutputClobberingFromEnvVarSink extends OutputClobberingSink {
OutputClobberingFromEnvVarSink() {
exists(Run run, string var, string field |
Bash::envReachingGitHubFileWrite(run, var, "GITHUB_OUTPUT", field) and
// there is a different output variable in the same script
// TODO: key2/value2 should be declared before key/value
exists(string field2 |
run.getAWriteToGitHubOutput(field2, _) and
not field2 = field
exists(Run run, string field1, string field2 |
// A write to GITHUB_OUTPUT that is attacker-controlled
exists(string var |
run.getScript().getAnEnvReachingGitHubOutputWrite(var, field1) and
exists(run.getInScopeEnvVarExpr(var)) and
run.getScript() = this.asExpr()
) and
exists(run.getInScopeEnvVarExpr(var)) and
run.getScriptScalar() = this.asExpr()
// A write to GITHUB_OUTPUT that is not attacker-controlled
exists(string str |
// The output of a command that is not a file read command
run.getScript().getACmdReachingGitHubOutputWrite(str, field2) and
not str = run.getScript().getAFileReadCommand()
or
// A hard-coded string
run.getScript().getAWriteToGitHubOutput(field2, str) and
str.regexpMatch("[\"'0-9a-zA-Z_\\-]+")
) and
not field2 = field1
)
}
}
@@ -97,13 +120,18 @@ class OutputClobberingFromEnvVarSink extends OutputClobberingSink {
* echo $BODY
*/
class WorkflowCommandClobberingFromEnvVarSink extends OutputClobberingSink {
string clobbering_var;
string clobbered_value;
WorkflowCommandClobberingFromEnvVarSink() {
exists(Run run, string clobbering_line, string var_name |
Bash::singleLineWorkflowCmd(run.getACommand(), "set-output", _, _) and
run.getACommand() = clobbering_line and
clobbering_line.regexpMatch(".*echo\\s+(-e\\s+)?(\"|')?\\$(\\{)?" + var_name + ".*") and
exists(run.getInScopeEnvVarExpr(var_name)) and
run.getScriptScalar() = this.asExpr()
exists(Run run, string workflow_cmd_stmt, string clobbering_stmt |
run.getScript() = this.asExpr() and
run.getScript().getAStmt() = clobbering_stmt and
clobbering_stmt.regexpMatch("echo\\s+(-e\\s+)?(\"|')?\\$(\\{)?" + clobbering_var + ".*") and
exists(run.getInScopeEnvVarExpr(clobbering_var)) and
run.getScript().getAStmt() = workflow_cmd_stmt and
clobbered_value =
trimQuotes(workflow_cmd_stmt.regexpCapture(".*::set-output\\s+name=.*::(.*)", 1))
)
}
}
@@ -133,30 +161,35 @@ class WorkflowCommandClobberingFromEnvVarSink extends OutputClobberingSink {
* echo "::set-output name=OUTPUT::SAFE"
*/
class WorkflowCommandClobberingFromFileReadSink extends OutputClobberingSink {
string clobbering_cmd;
WorkflowCommandClobberingFromFileReadSink() {
exists(Run run, string clobbering_line |
run.getScriptScalar() = this.asExpr() and
Bash::singleLineWorkflowCmd(run.getACommand(), "set-output", _, _) and
run.getACommand() = clobbering_line and
exists(Run run, string clobbering_stmt |
run.getScript() = this.asExpr() and
run.getScript().getAStmt() = clobbering_stmt and
(
// A file is read and its content is assigned to an env var that gets printed to stdout
// A file's content is assigned to an env var that gets printed to stdout
// - run: |
// foo=$(<pr-id.txt)"
// echo "${foo}"
exists(string var_name, string value |
run.getAnAssignment(var_name, value) and
Bash::outputsPartialFileContent(run, trimQuotes(value)) and
clobbering_line.regexpMatch(".*echo\\s+(-e\\s+)?(\"|')?\\$(\\{)?" + var_name + ".*")
exists(string var, string value |
run.getScript().getAnAssignment(var, value) and
clobbering_cmd = run.getScript().getAFileReadCommand() and
trimQuotes(value) = ["$(" + clobbering_cmd + ")", "`" + clobbering_cmd + "`"] and
clobbering_stmt.regexpMatch("echo.*\\$(\\{)?" + var + ".*")
)
or
// A file is read and its content is printed to stdout
// - run: echo "foo=$(<pr-id.txt)"
clobbering_line.regexpMatch(".*echo\\s+(-e)?\\s*(\"|')?") and
clobbering_line.regexpMatch(["ls", Bash::partialFileContentCommand()] + "\\s.*")
or
// A file content is printed to stdout
// - run: cat pr-id.txt
clobbering_line.regexpMatch(["ls", Bash::partialFileContentCommand()] + "\\s.*")
clobbering_cmd = run.getScript().getACommand() and
clobbering_cmd.regexpMatch(["ls", Bash::fileReadCommand()] + "\\s.*") and
(
// - run: echo "foo=$(<pr-id.txt)"
clobbering_stmt.regexpMatch("echo.*" + clobbering_cmd + ".*")
or
// A file content is printed to stdout
// - run: cat pr-id.txt
clobbering_stmt.indexOf(clobbering_cmd) = 0
)
)
)
}
@@ -181,8 +214,8 @@ private module OutputClobberingConfig implements DataFlow::ConfigSig {
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(Run run, string var |
run.getInScopeEnvVarExpr(var) = pred.asExpr() and
succ.asExpr() = run.getScriptScalar() and
run.getAWriteToGitHubOutput(_, _)
succ.asExpr() = run.getScript() and
run.getScript().getAWriteToGitHubOutput(_, _)
)
or
exists(Uses step |
@@ -195,10 +228,10 @@ private module OutputClobberingConfig implements DataFlow::ConfigSig {
exists(Run run |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScriptScalar() and
succ.asExpr() = run.getScript() and
(
Bash::outputsPartialFileContent(run, run.getACommand()) or
Bash::singleLineWorkflowCmd(run.getACommand(), "set-output", _, _)
exists(run.getScript().getAFileReadCommand()) or
run.getScript().getAStmt().matches("%::set-output %")
)
)
}

View File

@@ -18,7 +18,7 @@ class PoisonableCommandStep extends PoisonableStep, Run {
PoisonableCommandStep() {
exists(string regexp |
poisonableCommandsDataModel(regexp) and
this.getACommand().regexpMatch("^" + regexp + ".*")
this.getScript().getACommand().regexpMatch("^" + regexp + ".*")
)
}
}
@@ -46,7 +46,7 @@ class LocalScriptExecutionRunStep extends PoisonableStep, Run {
string path;
LocalScriptExecutionRunStep() {
exists(string cmd, string regexp, int path_group | cmd = this.getACommand() |
exists(string cmd, string regexp, int path_group | cmd = this.getScript().getACommand() |
poisonableLocalScriptsDataModel(regexp, path_group) and
path = cmd.regexpCapture(regexp, path_group)
)
@@ -58,12 +58,3 @@ class LocalScriptExecutionRunStep extends PoisonableStep, Run {
class LocalActionUsesStep extends PoisonableStep, UsesStep {
LocalActionUsesStep() { this.getCallee().matches("./%") }
}
class EnvVarInjectionFromFileReadRunStep extends PoisonableStep, Run {
string value;
EnvVarInjectionFromFileReadRunStep() {
this.getAWriteToGitHubEnv(_, value) and
Bash::outputsPartialFileContent(this, value)
}
}

View File

@@ -61,8 +61,8 @@ private module ActionsMutableRefCheckoutConfig implements DataFlow::ConfigSig {
exists(Run run |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScriptScalar() and
Bash::outputsPartialFileContent(run, run.getACommand())
succ.asExpr() = run.getScript() and
exists(run.getScript().getAFileReadCommand())
)
}
}
@@ -107,8 +107,8 @@ private module ActionsSHACheckoutConfig implements DataFlow::ConfigSig {
exists(Run run |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScriptScalar() and
Bash::outputsPartialFileContent(run, run.getACommand())
succ.asExpr() = run.getScript() and
exists(run.getScript().getAFileReadCommand())
)
}
}
@@ -283,7 +283,7 @@ class ActionsSHACheckout extends SHACheckoutStep instanceof UsesStep {
/** Checkout of a Pull Request HEAD ref using git within a Run step */
class GitMutableRefCheckout extends MutableRefCheckoutStep instanceof Run {
GitMutableRefCheckout() {
exists(string cmd | this.getACommand() = cmd |
exists(string cmd | this.getScript().getACommand() = cmd |
cmd.regexpMatch("git\\s+(fetch|pull).*") and
(
(containsHeadRef(cmd) or containsPullRequestNumber(cmd))
@@ -306,7 +306,7 @@ class GitMutableRefCheckout extends MutableRefCheckoutStep instanceof Run {
/** Checkout of a Pull Request HEAD ref using git within a Run step */
class GitSHACheckout extends SHACheckoutStep instanceof Run {
GitSHACheckout() {
exists(string cmd | this.getACommand() = cmd |
exists(string cmd | this.getScript().getACommand() = cmd |
cmd.regexpMatch("git\\s+(fetch|pull).*") and
(
containsHeadSHA(cmd)
@@ -326,7 +326,7 @@ class GitSHACheckout extends SHACheckoutStep instanceof Run {
/** Checkout of a Pull Request HEAD ref using gh within a Run step */
class GhMutableRefCheckout extends MutableRefCheckoutStep instanceof Run {
GhMutableRefCheckout() {
exists(string cmd | this.getACommand() = cmd |
exists(string cmd | this.getScript().getACommand() = cmd |
cmd.regexpMatch(".*(gh|hub)\\s+pr\\s+checkout.*") and
(
(containsHeadRef(cmd) or containsPullRequestNumber(cmd))
@@ -348,7 +348,7 @@ class GhMutableRefCheckout extends MutableRefCheckoutStep instanceof Run {
/** Checkout of a Pull Request HEAD ref using gh within a Run step */
class GhSHACheckout extends SHACheckoutStep instanceof Run {
GhSHACheckout() {
exists(string cmd | this.getACommand() = cmd |
exists(string cmd | this.getScript().getACommand() = cmd |
cmd.regexpMatch("gh\\s+pr\\s+checkout.*") and
(
containsHeadSHA(cmd)

View File

@@ -1,10 +1,19 @@
on: push
defaults:
run:
shell: bash -wkf
jobs:
local_commands:
runs-on: ubuntu-latest
defaults:
run:
shell: bash -job
steps:
- run: |
- shell: bash -step
run: |
command1 ; command2
- run: |
command3 | command4
@@ -19,3 +28,12 @@ jobs:
- run: |
command13 "`command14` $(date | wc -l)"
local_commands2:
runs-on: ubuntu-latest
steps:
- shell: bash -step
run: |
command1 ; command2
- shell: pwsh
run: |
command3 | command4

View File

@@ -1,19 +1,21 @@
| .github/workflows/commands.yml:7:9:9:6 | Run Step | command1 |
| .github/workflows/commands.yml:7:9:9:6 | Run Step | command2 |
| .github/workflows/commands.yml:9:9:11:6 | Run Step | command3 |
| .github/workflows/commands.yml:9:9:11:6 | Run Step | command4 |
| .github/workflows/commands.yml:11:9:13:6 | Run Step | command5 "$(command6)" |
| .github/workflows/commands.yml:11:9:13:6 | Run Step | command6 |
| .github/workflows/commands.yml:13:9:15:6 | Run Step | command7 |
| .github/workflows/commands.yml:13:9:15:6 | Run Step | command8 |
| .github/workflows/commands.yml:15:9:17:6 | Run Step | command9 |
| .github/workflows/commands.yml:15:9:17:6 | Run Step | command10 |
| .github/workflows/commands.yml:17:9:19:6 | Run Step | command11 "`command12`" |
| .github/workflows/commands.yml:17:9:19:6 | Run Step | command12 |
| .github/workflows/commands.yml:19:9:20:50 | Run Step | command13 "`command14` $(date \| wc -l)" |
| .github/workflows/commands.yml:19:9:20:50 | Run Step | command14 |
| .github/workflows/commands.yml:19:9:20:50 | Run Step | date |
| .github/workflows/commands.yml:19:9:20:50 | Run Step | wc -l |
| .github/workflows/commands.yml:15:9:18:6 | Run Step | command1 |
| .github/workflows/commands.yml:15:9:18:6 | Run Step | command2 |
| .github/workflows/commands.yml:18:9:20:6 | Run Step | command3 |
| .github/workflows/commands.yml:18:9:20:6 | Run Step | command4 |
| .github/workflows/commands.yml:20:9:22:6 | Run Step | command5 "$(command6)" |
| .github/workflows/commands.yml:20:9:22:6 | Run Step | command6 |
| .github/workflows/commands.yml:22:9:24:6 | Run Step | command7 |
| .github/workflows/commands.yml:22:9:24:6 | Run Step | command8 |
| .github/workflows/commands.yml:24:9:26:6 | Run Step | command9 |
| .github/workflows/commands.yml:24:9:26:6 | Run Step | command10 |
| .github/workflows/commands.yml:26:9:28:6 | Run Step | command11 "`command12`" |
| .github/workflows/commands.yml:26:9:28:6 | Run Step | command12 |
| .github/workflows/commands.yml:28:9:31:2 | Run Step | command13 "`command14` $(date \| wc -l)" |
| .github/workflows/commands.yml:28:9:31:2 | Run Step | command14 |
| .github/workflows/commands.yml:28:9:31:2 | Run Step | date |
| .github/workflows/commands.yml:28:9:31:2 | Run Step | wc -l |
| .github/workflows/commands.yml:34:9:37:6 | Run Step | command1 |
| .github/workflows/commands.yml:34:9:37:6 | Run Step | command2 |
| .github/workflows/expression_nodes.yml:7:9:8:6 | Run Step | LINE 1echo '${{ github.event.comment.body }}' |
| .github/workflows/expression_nodes.yml:8:9:10:6 | Run Step | LINE 1 echo '${{ github.event.comment.body }}' |
| .github/workflows/expression_nodes.yml:10:9:13:6 | Run Step | LINE 1 echo '${{ github.event.comment.body }}' |

View File

@@ -1,4 +1,4 @@
import actions
from Run run
select run, run.getACommand()
select run, run.getScript().getACommand()

View File

@@ -1,5 +1,3 @@
| .github/workflows/multiline2.yml:63:9:66:6 | Run Step |
| .github/workflows/multiline.yml:63:9:66:6 | Run Step |
| .github/workflows/poisonable_steps.yml:8:9:13:6 | Uses Step |
| .github/workflows/poisonable_steps.yml:13:9:14:6 | Run Step |
| .github/workflows/poisonable_steps.yml:14:9:15:6 | Run Step |

View File

@@ -1 +1,2 @@
ERROR: Ast::ShellScript is incompatible with string (test.ql:24,66-67)
ERROR: getCallee() cannot be resolved for type DataFlowPublic::CallNode (test.ql:62,79-88)

View File

@@ -34,5 +34,6 @@ jobs:
name: pr_number
- id: clob1
run: |
# VULNERABLE
echo "OUTPUT_1=HARDCODED" >> $GITHUB_OUTPUT
echo "OUTPUT_2=$(<pr-number)" >> $GITHUB_OUTPUT

View File

@@ -0,0 +1,32 @@
name: DownloadArtifacts
description: 'Downloads and unarchives artifacts for a workflow that runs on workflow_run so that it can use its data'
runs:
using: "composite"
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "artifacts"
})[0];
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync(`/tmp/artifacts.zip`, Buffer.from(download.data));
- run: |
mkdir -p /tmp/artifacts
unzip /tmp/artifacts.zip
shell: bash
- run: |
echo "Downloaded artifacts:"
ls -ablh
shell: bash

View File

@@ -0,0 +1,32 @@
name: DownloadArtifacts
description: 'Downloads and unarchives artifacts for a workflow that runs on workflow_run so that it can use its data'
runs:
using: "composite"
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "artifacts"
})[0];
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync(`/tmp/artifacts.zip`, Buffer.from(download.data));
- run: |
mkdir -p /tmp/artifacts
unzip /tmp/artifacts.zip -d /tmp/artifacts
shell: bash
- run: |
echo "Downloaded artifacts:"
ls -ablh /tmp/artifacts
shell: bash

View File

@@ -0,0 +1,20 @@
name: Pull Request Open
on:
workflow_run:
workflows: ["Prev"]
types:
- completed
jobs:
Download:
runs-on: ubuntu-latest
steps:
- run: |
gh run download "${{github.event.workflow_run.id}}" --repo "${GITHUB_REPOSITORY}" --name "artifact_name"
- name: Unzip
run: |
unzip artifact_name.zip -d foo
- name: Env Var Injection
run: |
echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV

View File

@@ -0,0 +1,26 @@
name: Pull Request Open
on:
workflow_run:
workflows: ["Prev"]
types:
- completed
jobs:
Download:
runs-on: ubuntu-latest
steps:
- run: |
gh run download "${{github.event.workflow_run.id}}" --repo "${GITHUB_REPOSITORY}" --name "artifact_name"
- name: Unzip
run: |
unzip artifact_name.zip -d foo
- name: Env Var Injection
run: |
echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"
cat foo >> "$GITHUB_ENV"
echo "EOF" >> "${GITHUB_ENV}"

View File

@@ -0,0 +1,27 @@
name: Pull Request Open
on:
workflow_run:
workflows: ["Prev"]
types:
- completed
jobs:
Download:
runs-on: ubuntu-latest
steps:
- run: |
gh run download "${{github.event.workflow_run.id}}" --repo "${GITHUB_REPOSITORY}" --name "artifact_name"
- name: Unzip
run: |
unzip artifact_name.zip -d foo
- run: |
{
echo 'JSON_RESPONSE<<EOF'
cat foo
echo EOF
} >> "$GITHUB_ENV"

View File

@@ -0,0 +1,29 @@
name: SnapshotPR
on:
workflow_run:
workflows:
- ApprovalComment
types:
- completed
jobs:
snapshot:
permissions:
id-token: write
pull-requests: write
statuses: write
if: github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
- uses: ./.github/actions/download-artifact
- id: metadata
run: |
pr_number="$(head -n 2 /tmp/artifacts/metadata.txt | tail -n 1)"
pr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"
echo PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"
echo PR_NUMBER="$pr_number" >> "$GITHUB_ENV"
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
with:
ref: ${{ env.PR_COMMIT }}
- uses: ./.github/actions/install-deps
- run: make snapshot

View File

@@ -0,0 +1,29 @@
name: SnapshotPR
on:
workflow_run:
workflows:
- ApprovalComment
types:
- completed
jobs:
snapshot:
permissions:
id-token: write
pull-requests: write
statuses: write
if: github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
- uses: ./.github/actions/download-artifact-2
- id: metadata
run: |
pr_number="$(head -n 2 /tmp/artifacts/metadata.txt | tail -n 1)"
pr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"
echo PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"
echo PR_NUMBER="$pr_number" >> "$GITHUB_ENV"
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
with:
ref: ${{ env.PR_COMMIT }}
- uses: ./.github/actions/install-deps
- run: make snapshot

View File

@@ -1,4 +1,9 @@
edges
| .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:20:14:24:55 | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n | provenance | Config |
| .github/actions/download-artifact/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning91.yml:20:14:24:55 | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n | provenance | Config |
| .github/workflows/artifactpoisoning51.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning51.yml:19:14:20:57 | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n | provenance | Config |
| .github/workflows/artifactpoisoning52.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning52.yml:19:14:22:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n | provenance | Config |
| .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n | provenance | Config |
| .github/workflows/test2.yml:12:9:41:6 | Uses Step | .github/workflows/test2.yml:41:14:43:52 | unzip pr.zip\necho "pr_number=$(cat NR)" >> $GITHUB_ENV\n | provenance | Config |
| .github/workflows/test3.yml:13:7:20:4 | Uses Step | .github/workflows/test3.yml:20:12:23:77 | echo "PR_NUMBER=$(cat pr_number.txt \| jq -r .)" >> $GITHUB_ENV\necho "PR_HEAD_REPO=$(cat pr_head_repo.txt \| jq -Rr .)" >> $GITHUB_ENV\necho "PR_HEAD_REF=$(cat pr_head_ref.txt \| jq -Rr .)" >> $GITHUB_ENV\n | provenance | Config |
| .github/workflows/test4.yml:11:19:11:56 | github.event.pull_request.title | .github/workflows/test4.yml:12:14:13:48 | echo "PR_TITLE=$TITLE" >> $GITHUB_ENV\n | provenance | Config |
@@ -25,6 +30,16 @@ edges
| .github/workflows/test12.yml:38:9:46:6 | Uses Step | .github/workflows/test12.yml:63:14:68:29 | {\n echo 'PRERELEASE_REPORT<<EOF'\n cat prerelease-report.md\n echo EOF\n} >> "$GITHUB_ENV"\n | provenance | Config |
| .github/workflows/test12.yml:55:9:61:6 | Uses Step | .github/workflows/test12.yml:63:14:68:29 | {\n echo 'PRERELEASE_REPORT<<EOF'\n cat prerelease-report.md\n echo EOF\n} >> "$GITHUB_ENV"\n | provenance | Config |
nodes
| .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | semmle.label | Uses Step |
| .github/actions/download-artifact/action.yaml:6:7:25:4 | Uses Step | semmle.label | Uses Step |
| .github/workflows/artifactpoisoning51.yml:13:9:15:6 | Run Step | semmle.label | Run Step |
| .github/workflows/artifactpoisoning51.yml:19:14:20:57 | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n | semmle.label | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n |
| .github/workflows/artifactpoisoning52.yml:13:9:15:6 | Run Step | semmle.label | Run Step |
| .github/workflows/artifactpoisoning52.yml:19:14:22:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n | semmle.label | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n |
| .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | semmle.label | Run Step |
| .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n | semmle.label | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n |
| .github/workflows/artifactpoisoning91.yml:20:14:24:55 | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n | semmle.label | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n |
| .github/workflows/artifactpoisoning92.yml:20:14:24:55 | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n | semmle.label | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n |
| .github/workflows/test2.yml:12:9:41:6 | Uses Step | semmle.label | Uses Step |
| .github/workflows/test2.yml:41:14:43:52 | unzip pr.zip\necho "pr_number=$(cat NR)" >> $GITHUB_ENV\n | semmle.label | unzip pr.zip\necho "pr_number=$(cat NR)" >> $GITHUB_ENV\n |
| .github/workflows/test3.yml:13:7:20:4 | Uses Step | semmle.label | Uses Step |
@@ -79,6 +94,11 @@ nodes
| .github/workflows/test15.yml:18:14:20:48 | PR_BODY=$(jq --raw-output .pull_request.body ${GITHUB_EVENT_PATH})\necho "BODY=$PR_BODY" >> "$GITHUB_ENV"\n | semmle.label | PR_BODY=$(jq --raw-output .pull_request.body ${GITHUB_EVENT_PATH})\necho "BODY=$PR_BODY" >> "$GITHUB_ENV"\n |
subpaths
#select
| .github/workflows/artifactpoisoning51.yml:19:14:20:57 | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n | .github/workflows/artifactpoisoning51.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning51.yml:19:14:20:57 | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n | Potential environment variable injection in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning51.yml:19:14:20:57 | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n |
| .github/workflows/artifactpoisoning52.yml:19:14:22:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n | .github/workflows/artifactpoisoning52.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning52.yml:19:14:22:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n | Potential environment variable injection in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning52.yml:19:14:22:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n |
| .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n | .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n | Potential environment variable injection in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n |
| .github/workflows/artifactpoisoning91.yml:20:14:24:55 | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n | .github/actions/download-artifact/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning91.yml:20:14:24:55 | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n | Potential environment variable injection in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning91.yml:20:14:24:55 | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n |
| .github/workflows/artifactpoisoning92.yml:20:14:24:55 | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n | .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:20:14:24:55 | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n | Potential environment variable injection in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning92.yml:20:14:24:55 | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n |
| .github/workflows/test2.yml:41:14:43:52 | unzip pr.zip\necho "pr_number=$(cat NR)" >> $GITHUB_ENV\n | .github/workflows/test2.yml:12:9:41:6 | Uses Step | .github/workflows/test2.yml:41:14:43:52 | unzip pr.zip\necho "pr_number=$(cat NR)" >> $GITHUB_ENV\n | Potential environment variable injection in $@, which may be controlled by an external user. | .github/workflows/test2.yml:41:14:43:52 | unzip pr.zip\necho "pr_number=$(cat NR)" >> $GITHUB_ENV\n | unzip pr.zip\necho "pr_number=$(cat NR)" >> $GITHUB_ENV\n |
| .github/workflows/test3.yml:20:12:23:77 | echo "PR_NUMBER=$(cat pr_number.txt \| jq -r .)" >> $GITHUB_ENV\necho "PR_HEAD_REPO=$(cat pr_head_repo.txt \| jq -Rr .)" >> $GITHUB_ENV\necho "PR_HEAD_REF=$(cat pr_head_ref.txt \| jq -Rr .)" >> $GITHUB_ENV\n | .github/workflows/test3.yml:13:7:20:4 | Uses Step | .github/workflows/test3.yml:20:12:23:77 | echo "PR_NUMBER=$(cat pr_number.txt \| jq -r .)" >> $GITHUB_ENV\necho "PR_HEAD_REPO=$(cat pr_head_repo.txt \| jq -Rr .)" >> $GITHUB_ENV\necho "PR_HEAD_REF=$(cat pr_head_ref.txt \| jq -Rr .)" >> $GITHUB_ENV\n | Potential environment variable injection in $@, which may be controlled by an external user. | .github/workflows/test3.yml:20:12:23:77 | echo "PR_NUMBER=$(cat pr_number.txt \| jq -r .)" >> $GITHUB_ENV\necho "PR_HEAD_REPO=$(cat pr_head_repo.txt \| jq -Rr .)" >> $GITHUB_ENV\necho "PR_HEAD_REF=$(cat pr_head_ref.txt \| jq -Rr .)" >> $GITHUB_ENV\n | echo "PR_NUMBER=$(cat pr_number.txt \| jq -r .)" >> $GITHUB_ENV\necho "PR_HEAD_REPO=$(cat pr_head_repo.txt \| jq -Rr .)" >> $GITHUB_ENV\necho "PR_HEAD_REF=$(cat pr_head_ref.txt \| jq -Rr .)" >> $GITHUB_ENV\n |
| .github/workflows/test4.yml:12:14:13:48 | echo "PR_TITLE=$TITLE" >> $GITHUB_ENV\n | .github/workflows/test4.yml:11:19:11:56 | github.event.pull_request.title | .github/workflows/test4.yml:12:14:13:48 | echo "PR_TITLE=$TITLE" >> $GITHUB_ENV\n | Potential environment variable injection in $@, which may be controlled by an external user. | .github/workflows/test4.yml:12:14:13:48 | echo "PR_TITLE=$TITLE" >> $GITHUB_ENV\n | echo "PR_TITLE=$TITLE" >> $GITHUB_ENV\n |

View File

@@ -1,4 +1,9 @@
edges
| .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:20:14:24:55 | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n | provenance | Config |
| .github/actions/download-artifact/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning91.yml:20:14:24:55 | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n | provenance | Config |
| .github/workflows/artifactpoisoning51.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning51.yml:19:14:20:57 | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n | provenance | Config |
| .github/workflows/artifactpoisoning52.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning52.yml:19:14:22:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n | provenance | Config |
| .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n | provenance | Config |
| .github/workflows/test2.yml:12:9:41:6 | Uses Step | .github/workflows/test2.yml:41:14:43:52 | unzip pr.zip\necho "pr_number=$(cat NR)" >> $GITHUB_ENV\n | provenance | Config |
| .github/workflows/test3.yml:13:7:20:4 | Uses Step | .github/workflows/test3.yml:20:12:23:77 | echo "PR_NUMBER=$(cat pr_number.txt \| jq -r .)" >> $GITHUB_ENV\necho "PR_HEAD_REPO=$(cat pr_head_repo.txt \| jq -Rr .)" >> $GITHUB_ENV\necho "PR_HEAD_REF=$(cat pr_head_ref.txt \| jq -Rr .)" >> $GITHUB_ENV\n | provenance | Config |
| .github/workflows/test4.yml:11:19:11:56 | github.event.pull_request.title | .github/workflows/test4.yml:12:14:13:48 | echo "PR_TITLE=$TITLE" >> $GITHUB_ENV\n | provenance | Config |
@@ -25,6 +30,16 @@ edges
| .github/workflows/test12.yml:38:9:46:6 | Uses Step | .github/workflows/test12.yml:63:14:68:29 | {\n echo 'PRERELEASE_REPORT<<EOF'\n cat prerelease-report.md\n echo EOF\n} >> "$GITHUB_ENV"\n | provenance | Config |
| .github/workflows/test12.yml:55:9:61:6 | Uses Step | .github/workflows/test12.yml:63:14:68:29 | {\n echo 'PRERELEASE_REPORT<<EOF'\n cat prerelease-report.md\n echo EOF\n} >> "$GITHUB_ENV"\n | provenance | Config |
nodes
| .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | semmle.label | Uses Step |
| .github/actions/download-artifact/action.yaml:6:7:25:4 | Uses Step | semmle.label | Uses Step |
| .github/workflows/artifactpoisoning51.yml:13:9:15:6 | Run Step | semmle.label | Run Step |
| .github/workflows/artifactpoisoning51.yml:19:14:20:57 | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n | semmle.label | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n |
| .github/workflows/artifactpoisoning52.yml:13:9:15:6 | Run Step | semmle.label | Run Step |
| .github/workflows/artifactpoisoning52.yml:19:14:22:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n | semmle.label | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n |
| .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | semmle.label | Run Step |
| .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n | semmle.label | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n |
| .github/workflows/artifactpoisoning91.yml:20:14:24:55 | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n | semmle.label | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n |
| .github/workflows/artifactpoisoning92.yml:20:14:24:55 | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n | semmle.label | pr_number="$(head -n 2 /tmp/artifacts/metadata.txt \| tail -n 1)"\npr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"\necho PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"\necho PR_NUMBER="$pr_number" >> "$GITHUB_ENV"\n |
| .github/workflows/test2.yml:12:9:41:6 | Uses Step | semmle.label | Uses Step |
| .github/workflows/test2.yml:41:14:43:52 | unzip pr.zip\necho "pr_number=$(cat NR)" >> $GITHUB_ENV\n | semmle.label | unzip pr.zip\necho "pr_number=$(cat NR)" >> $GITHUB_ENV\n |
| .github/workflows/test3.yml:13:7:20:4 | Uses Step | semmle.label | Uses Step |

View File

@@ -11,9 +11,6 @@ edges
| .github/workflows/artifactpoisoning34.yml:13:9:16:6 | Run Step | .github/workflows/artifactpoisoning34.yml:20:14:22:23 | npm install\nnpm run lint\n | provenance | Config |
| .github/workflows/artifactpoisoning41.yml:13:9:21:6 | Run Step | .github/workflows/artifactpoisoning41.yml:22:14:22:22 | ./foo/cmd | provenance | Config |
| .github/workflows/artifactpoisoning42.yml:13:9:21:6 | Run Step | .github/workflows/artifactpoisoning42.yml:22:14:22:18 | ./cmd | provenance | Config |
| .github/workflows/artifactpoisoning51.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning51.yml:19:14:20:57 | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n | provenance | Config |
| .github/workflows/artifactpoisoning52.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning52.yml:19:14:22:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n | provenance | Config |
| .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n | provenance | Config |
| .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | provenance | Config |
| .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | provenance | Config |
nodes
@@ -38,12 +35,6 @@ nodes
| .github/workflows/artifactpoisoning41.yml:22:14:22:22 | ./foo/cmd | semmle.label | ./foo/cmd |
| .github/workflows/artifactpoisoning42.yml:13:9:21:6 | Run Step | semmle.label | Run Step |
| .github/workflows/artifactpoisoning42.yml:22:14:22:18 | ./cmd | semmle.label | ./cmd |
| .github/workflows/artifactpoisoning51.yml:13:9:15:6 | Run Step | semmle.label | Run Step |
| .github/workflows/artifactpoisoning51.yml:19:14:20:57 | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n | semmle.label | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n |
| .github/workflows/artifactpoisoning52.yml:13:9:15:6 | Run Step | semmle.label | Run Step |
| .github/workflows/artifactpoisoning52.yml:19:14:22:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n | semmle.label | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n |
| .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | semmle.label | Run Step |
| .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n | semmle.label | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n |
| .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | semmle.label | Uses Step |
| .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | semmle.label | sed -f config foo.md > bar.md\n |
| .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | semmle.label | Uses Step |
@@ -62,9 +53,6 @@ subpaths
| .github/workflows/artifactpoisoning34.yml:20:14:22:23 | npm install\nnpm run lint\n | .github/workflows/artifactpoisoning34.yml:13:9:16:6 | Run Step | .github/workflows/artifactpoisoning34.yml:20:14:22:23 | npm install\nnpm run lint\n | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning34.yml:20:14:22:23 | npm install\nnpm run lint\n | npm install\nnpm run lint\n |
| .github/workflows/artifactpoisoning41.yml:22:14:22:22 | ./foo/cmd | .github/workflows/artifactpoisoning41.yml:13:9:21:6 | Run Step | .github/workflows/artifactpoisoning41.yml:22:14:22:22 | ./foo/cmd | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning41.yml:22:14:22:22 | ./foo/cmd | ./foo/cmd |
| .github/workflows/artifactpoisoning42.yml:22:14:22:18 | ./cmd | .github/workflows/artifactpoisoning42.yml:13:9:21:6 | Run Step | .github/workflows/artifactpoisoning42.yml:22:14:22:18 | ./cmd | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning42.yml:22:14:22:18 | ./cmd | ./cmd |
| .github/workflows/artifactpoisoning51.yml:19:14:20:57 | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n | .github/workflows/artifactpoisoning51.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning51.yml:19:14:20:57 | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning51.yml:19:14:20:57 | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n |
| .github/workflows/artifactpoisoning52.yml:19:14:22:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n | .github/workflows/artifactpoisoning52.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning52.yml:19:14:22:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning52.yml:19:14:22:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n |
| .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n | .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n |
| .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | sed -f config foo.md > bar.md\n |
| .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | python test.py |
| .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | Uses Step |

View File

@@ -11,9 +11,6 @@ edges
| .github/workflows/artifactpoisoning34.yml:13:9:16:6 | Run Step | .github/workflows/artifactpoisoning34.yml:20:14:22:23 | npm install\nnpm run lint\n | provenance | Config |
| .github/workflows/artifactpoisoning41.yml:13:9:21:6 | Run Step | .github/workflows/artifactpoisoning41.yml:22:14:22:22 | ./foo/cmd | provenance | Config |
| .github/workflows/artifactpoisoning42.yml:13:9:21:6 | Run Step | .github/workflows/artifactpoisoning42.yml:22:14:22:18 | ./cmd | provenance | Config |
| .github/workflows/artifactpoisoning51.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning51.yml:19:14:20:57 | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n | provenance | Config |
| .github/workflows/artifactpoisoning52.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning52.yml:19:14:22:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n | provenance | Config |
| .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n | provenance | Config |
| .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | provenance | Config |
| .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | provenance | Config |
nodes
@@ -38,12 +35,6 @@ nodes
| .github/workflows/artifactpoisoning41.yml:22:14:22:22 | ./foo/cmd | semmle.label | ./foo/cmd |
| .github/workflows/artifactpoisoning42.yml:13:9:21:6 | Run Step | semmle.label | Run Step |
| .github/workflows/artifactpoisoning42.yml:22:14:22:18 | ./cmd | semmle.label | ./cmd |
| .github/workflows/artifactpoisoning51.yml:13:9:15:6 | Run Step | semmle.label | Run Step |
| .github/workflows/artifactpoisoning51.yml:19:14:20:57 | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n | semmle.label | echo "pr_number=$(cat foo/bar)" >> $GITHUB_ENV\n |
| .github/workflows/artifactpoisoning52.yml:13:9:15:6 | Run Step | semmle.label | Run Step |
| .github/workflows/artifactpoisoning52.yml:19:14:22:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n | semmle.label | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\ncat foo >> "$GITHUB_ENV"\necho "EOF" >> "${GITHUB_ENV}"\n |
| .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | semmle.label | Run Step |
| .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n | semmle.label | {\n echo 'JSON_RESPONSE<<EOF'\n cat foo\n echo EOF\n} >> "$GITHUB_ENV"\n |
| .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | semmle.label | Uses Step |
| .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | semmle.label | sed -f config foo.md > bar.md\n |
| .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | semmle.label | Uses Step |