Refactor of Bash functions

This commit is contained in:
Alvaro Muñoz
2024-10-11 12:20:26 +02:00
parent d558ff80c3
commit ee25f35653
12 changed files with 697 additions and 346 deletions

View File

@@ -315,6 +315,22 @@ class Run extends Step instanceof RunImpl {
}
predicate getAWriteToGitHubPath(string value) { super.getAWriteToGitHubPath(value) }
predicate getAnEnvReachingGitHubOutputWrite(string var, string output_field) {
super.getAnEnvReachingGitHubOutputWrite(var, output_field)
}
predicate getACmdReachingGitHubOutputWrite(string cmd, string output_field) {
super.getACmdReachingGitHubOutputWrite(cmd, output_field)
}
predicate getAnEnvReachingGitHubEnvWrite(string var, string output_field) {
super.getAnEnvReachingGitHubEnvWrite(var, output_field)
}
predicate getACmdReachingGitHubEnvWrite(string cmd, string output_field) {
super.getACmdReachingGitHubEnvWrite(cmd, output_field)
}
}
abstract class SimpleReferenceExpression extends AstNode instanceof SimpleReferenceExpressionImpl {

View File

@@ -0,0 +1,364 @@
private import codeql.actions.Ast
private import codeql.Locations
import codeql.actions.config.Config
private import codeql.actions.security.ControlChecks
module Bash {
string stmtSeparator() { result = ";" }
string commandSeparator() { result = ["&&", "||"] }
string pipeSeparator() { result = "|" }
string splitSeparators() {
result = stmtSeparator() or result = commandSeparator() or result = pipeSeparator()
}
string redirectionSeparator() { result = [">", ">>", "2>", "2>>", ">&", "2>&", "<", "<<<"] }
string partialFileContentCommand() { result = ["cat", "jq", "yq", "tail", "head"] }
/** Checks if expr is a bash command substitution */
bindingset[expr]
predicate isCmdSubstitution(string expr, string cmd) {
exists(string regexp |
// $(cmd)
regexp = "\\$\\(([^)]+)\\)" and
cmd = expr.regexpCapture(regexp, 1)
or
// `cmd`
regexp = "`([^`]+)`" and
cmd = expr.regexpCapture(regexp, 1)
)
}
/** Checks if expr is a bash command substitution */
bindingset[expr]
predicate containsCmdSubstitution(string expr, string cmd) {
exists(string regexp |
// $(cmd)
regexp = ".*\\$\\(([^)]+)\\).*" and
cmd = expr.regexpCapture(regexp, 1)
or
// `cmd`
regexp = ".*`([^`]+)`.*" and
cmd = expr.regexpCapture(regexp, 1)
)
}
/** Checks if expr is a bash parameter expansion */
bindingset[expr]
predicate isParameterExpansion(string expr, string parameter, string operator, string params) {
exists(string regexp |
// $VAR
regexp = "\\$([a-zA-Z_][a-zA-Z0-9_]+)\\b" and
parameter = expr.regexpCapture(regexp, 1) and
operator = "" and
params = ""
or
// ${VAR}
regexp = "\\$\\{([a-zA-Z_][a-zA-Z0-9_]*)\\}" and
parameter = expr.regexpCapture(regexp, 1) and
operator = "" and
params = ""
or
// ${!VAR}
regexp = "\\$\\{([!#])([a-zA-Z_][a-zA-Z0-9_]*)\\}" and
parameter = expr.regexpCapture(regexp, 2) and
operator = expr.regexpCapture(regexp, 1) and
params = ""
or
// ${VAR<OP><PARAMS>}, ...
regexp = "\\$\\{([a-zA-Z_][a-zA-Z0-9_]*)([#%/:^,\\-+]{1,2})?(.*?)\\}" and
parameter = expr.regexpCapture(regexp, 1) and
operator = expr.regexpCapture(regexp, 2) and
params = expr.regexpCapture(regexp, 3)
)
}
bindingset[expr]
predicate containsParameterExpansion(string expr, string parameter, string operator, string params) {
exists(string regexp |
// $VAR
regexp = ".*\\$([a-zA-Z_][a-zA-Z0-9_]+)\\b.*" and
parameter = expr.regexpCapture(regexp, 1) and
operator = "" and
params = ""
or
// ${VAR}
regexp = ".*\\$\\{([a-zA-Z_][a-zA-Z0-9_]*)\\}.*" and
parameter = expr.regexpCapture(regexp, 1) and
operator = "" and
params = ""
or
// ${!VAR}
regexp = ".*\\$\\{([!#])([a-zA-Z_][a-zA-Z0-9_]*)\\}.*" and
parameter = expr.regexpCapture(regexp, 2) and
operator = expr.regexpCapture(regexp, 1) and
params = ""
or
// ${VAR<OP><PARAMS>}, ...
regexp = ".*\\$\\{([a-zA-Z_][a-zA-Z0-9_]*)([#%/:^,\\-+]{1,2})?(.*?)\\}.*" and
parameter = expr.regexpCapture(regexp, 1) and
operator = expr.regexpCapture(regexp, 2) and
params = expr.regexpCapture(regexp, 3)
)
}
bindingset[raw_content]
predicate extractVariableAndValue(string raw_content, string key, string value) {
exists(string regexp, string content | content = trimQuotes(raw_content) |
regexp = "(?msi).*^([a-zA-Z_][a-zA-Z0-9_]*)\\s*<<\\s*['\"]?(\\S+)['\"]?\\s*\n(.*?)\n\\2\\s*$" and
key = trimQuotes(content.regexpCapture(regexp, 1)) and
value = trimQuotes(content.regexpCapture(regexp, 3))
or
exists(string line |
line = content.splitAt("\n") and
regexp = "(?i)^([a-zA-Z_][a-zA-Z0-9_\\-]*)\\s*=\\s*(.*)$" and
key = trimQuotes(line.regexpCapture(regexp, 1)) and
value = trimQuotes(line.regexpCapture(regexp, 2))
)
)
}
bindingset[script]
predicate singleLineFileWrite(
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
cmd = script.regexpCapture(regexp, 1) and
file = trimQuotes(script.regexpCapture(regexp, 5)) and
filters = "" and
content = script.regexpCapture(regexp, 2)
)
}
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
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
cmd = script.regexpCapture(regexp, 3) and
key = "" and
value = trimQuotes(script.regexpCapture(regexp, 4))
)
}
bindingset[script]
predicate heredocFileWrite(string script, string cmd, string file, string content, string filters) {
exists(string regexp |
regexp =
"(?msi).*^(cat)\\s*(>>|>|\\s*\\|\\s*tee\\s*(-a|--append)?)\\s*(\\S+)\\s*<<\\s*['\"]?(\\S+)['\"]?\\s*\n(.*?)\n\\4\\s*$.*" and
cmd = script.regexpCapture(regexp, 1) and
file = trimQuotes(script.regexpCapture(regexp, 4)) and
content = script.regexpCapture(regexp, 6) and
filters = ""
or
regexp =
"(?msi).*^(cat)\\s*(<<|<)\\s*[-]?['\"]?(\\S+)['\"]?\\s*([^>]*)(>>|>|\\s*\\|\\s*tee\\s*(-a|--append)?)\\s*(\\S+)\\s*\n(.*?)\n\\3\\s*$.*" and
cmd = script.regexpCapture(regexp, 1) and
file = trimQuotes(script.regexpCapture(regexp, 7)) and
filters = script.regexpCapture(regexp, 4) and
content = script.regexpCapture(regexp, 8)
)
}
bindingset[script]
predicate linesFileWrite(string script, string cmd, string file, string content, string filters) {
exists(string regexp, string var_name |
regexp =
"(?msi).*((echo|printf)\\s+['|\"]?(.*?<<(\\S+))['|\"]?\\s*>>\\s*(\\S+)\\s*[\r\n]+)" +
"(((.*?)\\s*>>\\s*\\S+\\s*[\r\n]+)+)" +
"((echo|printf)\\s+['|\"]?(EOF)['|\"]?\\s*>>\\s*\\S+\\s*[\r\n]*).*" and
var_name = trimQuotes(script.regexpCapture(regexp, 3)).regexpReplaceAll("<<\\s*(\\S+)", "") and
content =
var_name + "=$(" +
trimQuotes(script.regexpCapture(regexp, 6))
.regexpReplaceAll(">>.*GITHUB_(ENV|OUTPUT)(})?", "")
.trim() + ")" and
cmd = "echo" and
file = trimQuotes(script.regexpCapture(regexp, 5)) and
filters = ""
)
}
bindingset[script]
predicate blockFileWrite(string script, string cmd, string file, string content, string filters) {
exists(string regexp, string first_line, string var_name |
regexp =
"(?msi).*^\\s*\\{\\s*[\r\n]" +
//
"(.*?)" +
//
"(\\s*\\}\\s*(>>|>|\\s*\\|\\s*tee\\s*(-a|--append)?)\\s*(\\S+))\\s*$.*" and
first_line = script.regexpCapture(regexp, 1).splitAt("\n", 0).trim() and
var_name = first_line.regexpCapture("echo\\s+('|\\\")?(.*)<<.*", 2) and
content = var_name + "=$(" + script.regexpCapture(regexp, 1).splitAt("\n").trim() + ")" and
not content.indexOf("EOF") > 0 and
file = trimQuotes(script.regexpCapture(regexp, 5)) and
cmd = "echo" and
filters = ""
)
}
bindingset[script]
predicate multiLineFileWrite(
string script, string cmd, string file, string content, string filters
) {
heredocFileWrite(script, cmd, file, content, filters)
or
linesFileWrite(script, cmd, file, content, filters)
or
blockFileWrite(script, cmd, file, content, filters)
}
bindingset[script, file_var]
predicate extractFileWrite(string script, string file_var, string content) {
// single line assignment
exists(string file_expr, string raw_content |
isParameterExpansion(file_expr, file_var, _, _) and
singleLineFileWrite(script.splitAt("\n"), _, file_expr, raw_content, _) and
content = trimQuotes(raw_content)
)
or
// workflow command assignment
exists(string key, string value, string cmd |
(
file_var = "GITHUB_ENV" and
cmd = "set-env" and
content = key + "=" + value
or
file_var = "GITHUB_OUTPUT" and
cmd = "set-output" and
content = key + "=" + value
or
file_var = "GITHUB_PATH" and
cmd = "add-path" and
content = value
) and
singleLineWorkflowCmd(script.splitAt("\n"), cmd, key, value)
)
or
// multiline assignment
exists(string file_expr, string raw_content |
multiLineFileWrite(script, _, file_expr, raw_content, _) and
isParameterExpansion(file_expr, file_var, _, _) and
content = trimQuotes(raw_content)
)
}
/** 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) {
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
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) {
exists(string file_write_value |
(
file_var = "GITHUB_ENV" and
run.getAWriteToGitHubEnv(field, file_write_value)
or
file_var = "GITHUB_OUTPUT" and
run.getAWriteToGitHubOutput(field, file_write_value)
or
file_var = "GITHUB_PATH" and
field = "PATH" and
run.getAWriteToGitHubPath(file_write_value)
) and
envReachingRunExpr(run, var, file_write_value)
)
}
/**
* Holds if and environment variable 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 envReachingRunExpr(Run run, 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
containsParameterExpansion(value2, var, _, _) and
containsParameterExpansion(expr, var2, _, _)
)
or
// var reaches the file write directly
// echo "FIELD=${VAR:-default}" >> $GITHUB_ENV (field, file_write_value)
containsParameterExpansion(expr, var, _, _)
}
/**
* 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) {
exists(string file_write_value |
(
file_var = "GITHUB_ENV" and
run.getAWriteToGitHubEnv(field, file_write_value)
or
file_var = "GITHUB_OUTPUT" and
run.getAWriteToGitHubOutput(field, file_write_value)
or
file_var = "GITHUB_PATH" and
field = "PATH" and
run.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)
)
)
}
}

View File

@@ -1,7 +1,8 @@
private import codeql.actions.Ast
private import codeql.Locations
import codeql.actions.config.Config
private import codeql.actions.security.ControlChecks
import codeql.actions.config.Config
import codeql.actions.Bash
bindingset[expr]
string normalizeExpr(string expr) {
@@ -82,239 +83,3 @@ string normalizePath(string path) {
*/
bindingset[subpath, path]
predicate isSubpath(string subpath, string path) { subpath.substring(0, path.length()) = path }
module Bash {
string stmtSeparator() { result = ";" }
string commandSeparator() { result = ["&&", "||"] }
string pipeSeparator() { result = "|" }
string splitSeparators() {
result = stmtSeparator() or result = commandSeparator() or result = pipeSeparator()
}
string redirectionSeparator() { result = [">", ">>", "2>", "2>>", ">&", "2>&", "<", "<<<"] }
string partialFileContentCommand() { result = ["cat", "jq", "yq", "tail", "head"] }
/** Checks if expr is a bash parameter expansion */
bindingset[expr]
predicate isBashParameterExpansion(string expr, string parameter, string operator, string params) {
exists(string regexp |
// $VAR
regexp = "\\$([a-zA-Z_][a-zA-Z0-9_]+)\\b" and
parameter = expr.regexpCapture(regexp, 1) and
operator = "" and
params = ""
or
// ${VAR}
regexp = "\\$\\{([a-zA-Z_][a-zA-Z0-9_]*)\\}" and
parameter = expr.regexpCapture(regexp, 1) and
operator = "" and
params = ""
or
// ${!VAR}
regexp = "\\$\\{([!#])([a-zA-Z_][a-zA-Z0-9_]*)\\}" and
parameter = expr.regexpCapture(regexp, 2) and
operator = expr.regexpCapture(regexp, 1) and
params = ""
or
// ${VAR<OP><PARAMS>}, ...
regexp = "\\$\\{([a-zA-Z_][a-zA-Z0-9_]*)([#%/:^,\\-+]{1,2})?(.*?)\\}" and
parameter = expr.regexpCapture(regexp, 1) and
operator = expr.regexpCapture(regexp, 2) and
params = expr.regexpCapture(regexp, 3)
)
}
bindingset[raw_content]
predicate extractVariableAndValue(string raw_content, string key, string value) {
exists(string regexp, string content | content = trimQuotes(raw_content) |
regexp = "(?msi).*^([a-zA-Z_][a-zA-Z0-9_]*)\\s*<<\\s*['\"]?(\\S+)['\"]?\\s*\n(.*?)\n\\2\\s*$" and
key = trimQuotes(content.regexpCapture(regexp, 1)) and
value = trimQuotes(content.regexpCapture(regexp, 3))
or
exists(string line |
line = content.splitAt("\n") and
regexp = "(?i)^([a-zA-Z_][a-zA-Z0-9_\\-]*)\\s*=\\s*(.*)$" and
key = trimQuotes(line.regexpCapture(regexp, 1)) and
value = trimQuotes(line.regexpCapture(regexp, 2))
)
)
}
bindingset[script]
predicate singleLineFileWrite(
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
cmd = script.regexpCapture(regexp, 1) and
file = trimQuotes(script.regexpCapture(regexp, 5)) and
filters = "" and
content = script.regexpCapture(regexp, 2)
)
}
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
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
cmd = script.regexpCapture(regexp, 3) and
key = "" and
value = trimQuotes(script.regexpCapture(regexp, 4))
)
}
bindingset[script]
predicate heredocFileWrite(string script, string cmd, string file, string content, string filters) {
exists(string regexp |
regexp =
"(?msi).*^(cat)\\s*(>>|>|\\s*\\|\\s*tee\\s*(-a|--append)?)\\s*(\\S+)\\s*<<\\s*['\"]?(\\S+)['\"]?\\s*\n(.*?)\n\\4\\s*$.*" and
cmd = script.regexpCapture(regexp, 1) and
file = trimQuotes(script.regexpCapture(regexp, 4)) and
content = script.regexpCapture(regexp, 6) and
filters = ""
or
regexp =
"(?msi).*^(cat)\\s*(<<|<)\\s*[-]?['\"]?(\\S+)['\"]?\\s*([^>]*)(>>|>|\\s*\\|\\s*tee\\s*(-a|--append)?)\\s*(\\S+)\\s*\n(.*?)\n\\3\\s*$.*" and
cmd = script.regexpCapture(regexp, 1) and
file = trimQuotes(script.regexpCapture(regexp, 7)) and
filters = script.regexpCapture(regexp, 4) and
content = script.regexpCapture(regexp, 8)
)
}
bindingset[script]
predicate linesFileWrite(string script, string cmd, string file, string content, string filters) {
exists(string regexp |
regexp =
"(?msi).*((echo|printf)\\s+['|\"]?(.*?<<(\\S+))['|\"]?\\s*>>\\s*(\\S+)\\s*[\r\n]+)" +
"(((.*?)\\s*>>\\s*\\S+\\s*[\r\n]+)+)" +
"((echo|printf)\\s+['|\"]?(EOF)['|\"]?\\s*>>\\s*\\S+\\s*[\r\n]*).*" and
content =
trimQuotes(script.regexpCapture(regexp, 3)) + "\n" +
trimQuotes(script.regexpCapture(regexp, 6)) + "\n" +
trimQuotes(script.regexpCapture(regexp, 4)) and
cmd = "echo" and
file = trimQuotes(script.regexpCapture(regexp, 5)) and
filters = ""
)
}
bindingset[script]
predicate blockFileWrite(string script, string cmd, string file, string content, string filters) {
exists(string regexp |
regexp =
"(?msi).*^\\s*\\{\\s*[\r\n]" +
//
"(.*?)" +
//
"(\\s*\\}\\s*(>>|>|\\s*\\|\\s*tee\\s*(-a|--append)?)\\s*(\\S+))\\s*$.*" and
content =
script
.regexpCapture(regexp, 1)
.regexpReplaceAll("(?m)^\\s*(echo|printf|write-output)\\s*['\"](.*?)['\"]", "$2")
.regexpReplaceAll("(?m)^\\s*(echo|printf|write-output)\\s*", "") and
file = trimQuotes(script.regexpCapture(regexp, 5)) and
cmd = "echo" and
filters = ""
)
}
bindingset[script]
predicate multiLineFileWrite(
string script, string cmd, string file, string content, string filters
) {
heredocFileWrite(script, cmd, file, content, filters)
or
linesFileWrite(script, cmd, file, content, filters)
or
blockFileWrite(script, cmd, file, content, filters)
}
bindingset[script, file_var]
predicate extractFileWrite(string script, string file_var, string content) {
// single line assignment
exists(string file_expr, string raw_content |
isBashParameterExpansion(file_expr, file_var, _, _) and
singleLineFileWrite(script.splitAt("\n"), _, file_expr, raw_content, _) and
content = trimQuotes(raw_content)
)
or
// workflow command assignment
exists(string key, string value, string cmd |
(
file_var = "GITHUB_ENV" and
cmd = "set-env" and
content = key + "=" + value
or
file_var = "GITHUB_OUTPUT" and
cmd = "set-output" and
content = key + "=" + value
or
file_var = "GITHUB_PATH" and
cmd = "add-path" and
content = value
) and
singleLineWorkflowCmd(script.splitAt("\n"), cmd, key, value)
)
or
// multiline assignment
exists(string file_expr, string raw_content |
multiLineFileWrite(script, _, file_expr, raw_content, _) and
isBashParameterExpansion(file_expr, file_var, _, _) and
content = trimQuotes(raw_content)
)
}
/** Writes the content of the file specified by `path` into a file pointed to by `file_var` */
bindingset[script, file_var]
predicate fileToFileWrite(string script, string file_var, string path) {
exists(string regexp, string line, string file_expr |
isBashParameterExpansion(file_expr, file_var, _, _) and
regexp =
"(?i)(cat)\\s*" + "((?:(?!<<|<<-)[^>\n])+)\\s*" +
"(>>|>|\\s*\\|\\s*tee\\s*(-a|--append)?)\\s*" + "(\\S+)" and
line = script.splitAt("\n") and
path = line.regexpCapture(regexp, 2) and
file_expr = trimQuotes(line.regexpCapture(regexp, 5))
)
}
predicate fileToGitHubEnv(Run run, string path) {
fileToFileWrite(run.getScript(), "GITHUB_ENV", path)
}
predicate fileToGitHubOutput(Run run, string path) {
fileToFileWrite(run.getScript(), "GITHUB_OUTPUT", path)
}
predicate fileToGitHubPath(Run run, string path) {
fileToFileWrite(run.getScript(), "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
// Bash::getACommand(snippet).indexOf(["<", Bash::partialFileContentCommand() + " "]) = 0
exists(int i, string line, string cmd |
run.getStmt(i) = line and
line.matches("%" + snippet + "%") and
run.getCommand(i) = cmd and
cmd.indexOf(["<", Bash::partialFileContentCommand() + " "]) = 0
)
}
}

View File

@@ -1526,6 +1526,22 @@ class RunImpl extends StepImpl {
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)
}
}
/**

View File

@@ -9,6 +9,23 @@ abstract class ArgumentInjectionSink extends DataFlow::Node {
abstract string getCommand();
}
/**
* Holds if an environment variable is used, directly or indirectly, as an argument to a dangerous command
* in a Run step.
* Where the command is a string captured from the Run's script.
*/
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
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
exists(run.getInScopeEnvVarExpr(var))
)
}
/**
* Holds if a Run step declares an environment variable, uses it as the argument to a command vulnerable to argument injection.
* e.g.
@@ -21,10 +38,10 @@ class ArgumentInjectionFromEnvVarSink extends ArgumentInjectionSink {
string command;
ArgumentInjectionFromEnvVarSink() {
exists(Run run, string var_name |
envToArgInjSink(var_name, run, command) and
exists(Run run, string var |
envToArgInjSink(var, run, command) and
run.getScriptScalar() = this.asExpr() and
exists(run.getInScopeEnvVarExpr(var_name))
exists(run.getInScopeEnvVarExpr(var))
)
or
exists(
@@ -42,6 +59,33 @@ class ArgumentInjectionFromEnvVarSink extends ArgumentInjectionSink {
override string getCommand() { result = command }
}
/**
* Holds if a Run step executes a command that returns untrusted data which flows to an unsafe argument
* e.g.
* run: |
* BODY=$(git log --format=%s)
* sed "s/FOO/$BODY/g" > /tmp/foo
*/
class ArgumentInjectionFromCommandSink extends ArgumentInjectionSink {
string command;
ArgumentInjectionFromCommandSink() {
exists(
CommandSource source, Run run, string cmd, string argument, string regexp, int argument_group,
int command_group
|
run = source.getEnclosingRun() and
this.asExpr() = run.getScriptScalar() and
cmd = run.getACommand() and
argumentInjectionSinksDataModel(regexp, command_group, argument_group) and
argument = cmd.regexpCapture(regexp, argument_group) and
command = cmd.regexpCapture(regexp, command_group)
)
}
override string getCommand() { result = command }
}
/**
* Holds if a Run step declares an environment variable, uses it as the argument to a command vulnerable to argument injection.
*/
@@ -71,6 +115,14 @@ private module ArgumentInjectionConfig implements DataFlow::ConfigSig {
}
predicate isSink(DataFlow::Node sink) { sink instanceof ArgumentInjectionSink }
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(Run run, string var |
run.getInScopeEnvVarExpr(var) = pred.asExpr() and
succ.asExpr() = run.getScriptScalar() and
envToArgInjSink(var, run, _)
)
}
}
/** Tracks flow of unsafe user input that is used to construct and evaluate a code script. */

View File

@@ -274,6 +274,24 @@ private module ArtifactPoisoningConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ArtifactSource }
predicate isSink(DataFlow::Node sink) { sink instanceof ArtifactPoisoningSink }
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(PoisonableStep step |
pred instanceof ArtifactSource and
pred.asExpr().(Step).getAFollowingStep() = step and
(
succ.asExpr() = step.(Run).getScriptScalar() or
succ.asExpr() = step.(UsesStep)
)
)
or
exists(Run run |
pred instanceof ArtifactSource and
pred.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScriptScalar() and
Bash::outputsPartialFileContent(run, run.getACommand())
)
}
}
/** Tracks flow of unsafe artifacts that is used in an insecure way. */

View File

@@ -19,6 +19,22 @@ private module CodeInjectionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) { sink instanceof CodeInjectionSink }
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(Uses step |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = step and
succ.asExpr() = step and
madSink(succ, "code-injection")
)
or
exists(Run run |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScriptScalar() and
Bash::outputsPartialFileContent(run, run.getACommand())
)
}
}
/** Tracks flow of unsafe user input that is used to construct and evaluate a code script. */

View File

@@ -14,6 +14,9 @@ abstract class EnvPathInjectionSink extends DataFlow::Node { }
* e.g.
* run: |
* cat foo.txt >> $GITHUB_PATH
* echo "$(cat foo.txt)" >> $GITHUB_PATH
* FOO=$(cat foo.txt)
* echo "$FOO" >> $GITHUB_PATH
*/
class EnvPathInjectionFromFileReadSink extends EnvPathInjectionSink {
EnvPathInjectionFromFileReadSink() {
@@ -25,35 +28,34 @@ class EnvPathInjectionFromFileReadSink extends EnvPathInjectionSink {
this.asExpr() = run.getScriptScalar() and
step.getAFollowingStep() = run and
(
// e.g.
// cat test-results/.env >> $GITHUB_PATH
Bash::fileToGitHubPath(run, _)
or
exists(string value |
run.getAWriteToGitHubPath(value) and
(
Bash::outputsPartialFileContent(run, value)
or
// e.g.
// FOO=$(cat test-results/sha-number)
// echo "FOO=$FOO" >> $GITHUB_PATH
exists(string var_name, string var_value |
run.getAnAssignment(var_name, var_value) and
Bash::outputsPartialFileContent(run, var_value) and
(
value.matches("%$" + ["", "{", "ENV{"] + var_name + "%")
or
value.regexpMatch("\\$\\((echo|printf|write-output)\\s+.*") and
value.indexOf(var_name) > 0
)
)
)
exists(string cmd |
Bash::cmdReachingGitHubFileWrite(run, cmd, "GITHUB_PATH", _) and
Bash::outputsPartialFileContent(run, cmd)
)
or
Bash::fileToGitHubPath(run, _)
)
)
}
}
/**
* Holds if a Run step executes a command that returns untrusted data which flows to GITHUB_ENV
* e.g.
* run: |
* COMMIT_MESSAGE=$(git log --format=%s)
* echo "${COMMIT_MESSAGE}" >> $GITHUB_PATH
*/
class EnvPathInjectionFromCommandSink extends EnvPathInjectionSink {
EnvPathInjectionFromCommandSink() {
exists(CommandSource source |
this.asExpr() = source.getEnclosingRun().getScriptScalar() and
Bash::cmdReachingGitHubFileWrite(source.getEnclosingRun(), source.getCommand(), "GITHUB_PATH",
_)
)
}
}
/**
* Holds if a Run step declares an environment variable, uses it to declare a PATH env var.
* e.g.
@@ -65,7 +67,7 @@ class EnvPathInjectionFromFileReadSink extends EnvPathInjectionSink {
class EnvPathInjectionFromEnvVarSink extends EnvPathInjectionSink {
EnvPathInjectionFromEnvVarSink() {
exists(Run run, string var_name |
envToSpecialFile("GITHUB_PATH", var_name, run, _) and
Bash::envReachingGitHubFileWrite(run, var_name, "GITHUB_PATH", _) and
exists(run.getInScopeEnvVarExpr(var_name)) and
run.getScriptScalar() = this.asExpr()
)
@@ -84,6 +86,28 @@ private module EnvPathInjectionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) { sink instanceof EnvPathInjectionSink }
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"], _)
)
or
exists(Uses step |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = step and
succ.asExpr() = step and
madSink(succ, "envpath-injection")
)
or
exists(Run run |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScriptScalar() and
Bash::outputsPartialFileContent(run, run.getACommand())
)
}
}
/** Tracks flow of unsafe user input that is used to construct and evaluate the PATH environment variable. */

View File

@@ -14,8 +14,12 @@ abstract class EnvVarInjectionSink extends DataFlow::Node { }
* e.g.
* run: |
* cat test-results/.env >> $GITHUB_ENV
*
* echo "sha=$(cat test-results/sha-number)" >> $GITHUB_ENV
* echo "sha=$(<test-results/sha-number)" >> $GITHUB_ENV
*
* FOO=$(cat test-results/sha-number)
* echo "FOO=$FOO" >> $GITHUB_ENV
*/
class EnvVarInjectionFromFileReadSink extends EnvVarInjectionSink {
EnvVarInjectionFromFileReadSink() {
@@ -27,37 +31,34 @@ class EnvVarInjectionFromFileReadSink extends EnvVarInjectionSink {
this.asExpr() = run.getScriptScalar() and
step.getAFollowingStep() = run and
(
// e.g.
// cat test-results/.env >> $GITHUB_ENV
Bash::fileToGitHubEnv(run, _)
or
exists(string value |
run.getAWriteToGitHubEnv(_, value) and
(
// e.g.
// echo "FOO=$(cat test-results/sha-number)" >> $GITHUB_ENV
Bash::outputsPartialFileContent(run, value)
or
// e.g.
// FOO=$(cat test-results/sha-number)
// echo "FOO=$FOO" >> $GITHUB_ENV
exists(string var_name, string var_value |
run.getAnAssignment(var_name, var_value) and
Bash::outputsPartialFileContent(run, var_value) and
(
value.matches("%$" + ["", "{", "ENV{"] + var_name + "%")
or
value.regexpMatch("\\$\\((echo|printf|write-output)\\s+.*") and
value.indexOf(var_name) > 0
)
)
)
exists(string cmd |
Bash::cmdReachingGitHubFileWrite(run, cmd, "GITHUB_ENV", _) and
Bash::outputsPartialFileContent(run, cmd)
)
or
Bash::fileToGitHubEnv(run, _)
)
)
}
}
/**
* Holds if a Run step executes a command that returns untrusted data which flows to GITHUB_ENV
* e.g.
* run: |
* COMMIT_MESSAGE=$(git log --format=%s)
* echo "COMMIT_MESSAGE=${COMMIT_MESSAGE}" >> $GITHUB_ENV
*/
class EnvVarInjectionFromCommandSink extends EnvVarInjectionSink {
EnvVarInjectionFromCommandSink() {
exists(CommandSource source |
this.asExpr() = source.getEnclosingRun().getScriptScalar() and
Bash::cmdReachingGitHubFileWrite(source.getEnclosingRun(), source.getCommand(), "GITHUB_ENV",
_)
)
}
}
/**
* Holds if a Run step declares an environment variable, uses it to declare env var.
* e.g.
@@ -69,9 +70,9 @@ class EnvVarInjectionFromFileReadSink extends EnvVarInjectionSink {
class EnvVarInjectionFromEnvVarSink extends EnvVarInjectionSink {
EnvVarInjectionFromEnvVarSink() {
exists(Run run, string var_name |
envToSpecialFile("GITHUB_ENV", var_name, run, _) and
exists(run.getInScopeEnvVarExpr(var_name)) and
run.getScriptScalar() = this.asExpr()
run.getScriptScalar() = this.asExpr() and
Bash::envReachingGitHubFileWrite(run, var_name, "GITHUB_ENV", _)
)
}
}
@@ -104,6 +105,28 @@ private module EnvVarInjectionConfig implements DataFlow::ConfigSig {
}
predicate isSink(DataFlow::Node sink) { sink instanceof EnvVarInjectionSink }
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"], _)
)
or
exists(Uses step |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = step and
succ.asExpr() = step and
madSink(succ, "envvar-injection")
)
or
exists(Run run |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScriptScalar() and
Bash::outputsPartialFileContent(run, run.getACommand())
)
}
}
/** Tracks flow of unsafe user input that is used to construct and evaluate an environment variable. */

View File

@@ -10,7 +10,7 @@ import codeql.actions.dataflow.FlowSources
abstract class OutputClobberingSink extends DataFlow::Node { }
/**
* Holds if a Run step declares an environment variable with contents from a local file.
* Holds if a Run step declares a step output variable with contents from a local file.
* e.g.
* run: |
* cat test-results/.vars >> $GITHUB_OUTPUT
@@ -21,58 +21,43 @@ class OutputClobberingFromFileReadSink extends OutputClobberingSink {
OutputClobberingFromFileReadSink() {
exists(Run run, Step step |
(
step instanceof UntrustedArtifactDownloadStep or
step instanceof UntrustedArtifactDownloadStep
or
// This shoould be:
// artifact instanceof PRHeadCheckoutStep
// 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
step.(Uses).getCallee() = "actions/checkout" or
step instanceof GitMutableRefCheckout or
step instanceof GitSHACheckout or
step instanceof GhMutableRefCheckout or
exists(Uses uses |
step = uses and
uses.getCallee() = "actions/checkout" and
exists(uses.getArgument("ref"))
)
or
step instanceof GitMutableRefCheckout
or
step instanceof GitSHACheckout
or
step instanceof GhMutableRefCheckout
or
step instanceof GhSHACheckout
) and
this.asExpr() = run.getScriptScalar() and
step.getAFollowingStep() = run and
this.asExpr() = run.getScriptScalar() and
(
// e.g.
// cat test-results/.vars >> $GITHUB_OUTPUT
Bash::fileToGitHubOutput(run, _)
or
exists(string key, string value |
run.getAWriteToGitHubOutput(key, value) and
// there is a different output variable in the same script
// TODO: key2/value2 should be declared before key/value
exists(string key2 |
run.getAWriteToGitHubOutput(key2, _) and
not key2 = key
) and
(
Bash::outputsPartialFileContent(run, value)
or
// e.g.
// FOO=$(cat test-results/sha-number)
// echo "FOO=$FOO" >> $GITHUB_OUTPUT
exists(string var_name, string var_value |
run.getAnAssignment(var_name, var_value) and
Bash::outputsPartialFileContent(run, var_value) and
(
value.matches("%$" + ["", "{", "ENV{"] + var_name + "%")
or
value.regexpMatch("\\$\\((echo|printf|write-output)\\s+.*") and
value.indexOf(var_name) > 0
)
)
)
exists(string cmd |
Bash::cmdReachingGitHubFileWrite(run, cmd, "GITHUB_OUTPUT", _) and
Bash::outputsPartialFileContent(run, cmd)
)
or
Bash::fileToGitHubOutput(run, _)
)
)
}
}
/**
* Holds if a Run step declares an environment variable, uses it to declare env var.
* Holds if a Run step declares an environment variable, uses it in a step variable output.
* e.g.
* env:
* BODY: ${{ github.event.comment.body }}
@@ -81,15 +66,15 @@ class OutputClobberingFromFileReadSink extends OutputClobberingSink {
*/
class OutputClobberingFromEnvVarSink extends OutputClobberingSink {
OutputClobberingFromEnvVarSink() {
exists(Run run, string var_name, string key |
envToSpecialFile("GITHUB_OUTPUT", var_name, run, key) and
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 key2 |
run.getAWriteToGitHubOutput(key2, _) and
not key2 = key
exists(string field2 |
run.getAWriteToGitHubOutput(field2, _) and
not field2 = field
) and
exists(run.getInScopeEnvVarExpr(var_name)) and
exists(run.getInScopeEnvVarExpr(var)) and
run.getScriptScalar() = this.asExpr()
)
}
@@ -113,10 +98,9 @@ class OutputClobberingFromEnvVarSink extends OutputClobberingSink {
*/
class WorkflowCommandClobberingFromEnvVarSink extends OutputClobberingSink {
WorkflowCommandClobberingFromEnvVarSink() {
exists(Run run, string output_line, string clobbering_line, string var_name |
run.getScript().splitAt("\n") = output_line and
Bash::singleLineWorkflowCmd(output_line, "set-output", _, _) and
run.getScript().splitAt("\n") = clobbering_line and
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()
@@ -124,13 +108,36 @@ class WorkflowCommandClobberingFromEnvVarSink extends OutputClobberingSink {
}
}
/**
* - id: clob1
* run: |
* # VULNERABLE
* PR="$(<pr-number)"
* echo "$PR"
* echo "::set-output name=OUTPUT::SAFE"
* - id: clob2
* run: |
* # VULNERABLE
* cat pr-number
* echo "::set-output name=OUTPUT::SAFE"
* - id: clob3
* run: |
* # VULNERABLE
* echo "::set-output name=OUTPUT::SAFE"
* ls *.txt
* - id: clob4
* run: |
* # VULNERABLE
* CURRENT_VERSION=$(cat gradle.properties | sed -n '/^version=/ { s/^version=//;p }')
* echo "$CURRENT_VERSION"
* echo "::set-output name=OUTPUT::SAFE"
*/
class WorkflowCommandClobberingFromFileReadSink extends OutputClobberingSink {
WorkflowCommandClobberingFromFileReadSink() {
exists(Run run, string output_line, string clobbering_line |
exists(Run run, string clobbering_line |
run.getScriptScalar() = this.asExpr() and
run.getScript().splitAt("\n") = output_line and
Bash::singleLineWorkflowCmd(output_line, "set-output", _, _) and
run.getScript().splitAt("\n") = clobbering_line and
Bash::singleLineWorkflowCmd(run.getACommand(), "set-output", _, _) and
run.getACommand() = clobbering_line and
(
// A file is read and its content is assigned to an env var that gets printed to stdout
// - run: |
@@ -170,6 +177,31 @@ private module OutputClobberingConfig implements DataFlow::ConfigSig {
}
predicate isSink(DataFlow::Node sink) { sink instanceof OutputClobberingSink }
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(_, _)
)
or
exists(Uses step |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = step and
succ.asExpr() = step and
madSink(succ, "output-clobbering")
)
or
exists(Run run |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScriptScalar() and
(
Bash::outputsPartialFileContent(run, run.getACommand()) or
Bash::singleLineWorkflowCmd(run.getACommand(), "set-output", _, _)
)
)
}
}
/** Tracks flow of unsafe user input that is used to construct and evaluate an environment variable. */

View File

@@ -23,8 +23,8 @@ class PoisonableCommandStep extends PoisonableStep, Run {
}
}
class JavascriptImportnUsesStep extends PoisonableStep, UsesStep {
JavascriptImportnUsesStep() {
class JavascriptImportUsesStep extends PoisonableStep, UsesStep {
JavascriptImportUsesStep() {
exists(string script, string line, string import_stmt |
this.getCallee() = "actions/github-script" and
script = this.getArgument("script") and
@@ -35,6 +35,13 @@ class JavascriptImportnUsesStep extends PoisonableStep, UsesStep {
}
}
class SetupNodeUsesStep extends PoisonableStep, UsesStep {
SetupNodeUsesStep() {
this.getCallee() = "actions/setup-node" and
this.getArgument("cache") = "yarn"
}
}
class LocalScriptExecutionRunStep extends PoisonableStep, Run {
string path;

View File

@@ -56,6 +56,15 @@ private module ActionsMutableRefCheckoutConfig implements DataFlow::ConfigSig {
uses.getArgumentExpr("ref") = sink.asExpr()
)
}
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(Run run |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScriptScalar() and
Bash::outputsPartialFileContent(run, run.getACommand())
)
}
}
module ActionsMutableRefCheckoutFlow = TaintTracking::Global<ActionsMutableRefCheckoutConfig>;
@@ -93,6 +102,15 @@ private module ActionsSHACheckoutConfig implements DataFlow::ConfigSig {
uses.getArgumentExpr("ref") = sink.asExpr()
)
}
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(Run run |
pred instanceof FileSource and
pred.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScriptScalar() and
Bash::outputsPartialFileContent(run, run.getACommand())
)
}
}
module ActionsSHACheckoutFlow = TaintTracking::Global<ActionsSHACheckoutConfig>;
@@ -139,7 +157,7 @@ predicate containsHeadSHA(string s) {
"\\bgithub\\.event\\.merge_group\\.head_sha\\b",
"\\bgithub\\.event\\.merge_group\\.head_commit\\.id\\b",
// heuristics
"\\bhead\\.sha\\b", "\\bhead_sha\\b", "\\bpr_head_sha\\b"
"\\bhead\\.sha\\b", "\\bhead_sha\\b", "\\bmerge_sha\\b", "\\bpr_head_sha\\b"
], _, _)
)
}
@@ -156,7 +174,7 @@ predicate containsHeadRef(string s) {
"\\bgithub\\.event\\.check_run\\.pull_requests\\[\\d+\\]\\.head\\.ref\\b",
"\\bgithub\\.event\\.merge_group\\.head_ref\\b",
// heuristics
"\\bhead\\.ref\\b", "\\bhead_ref\\b", "\\bpr_head_ref\\b",
"\\bhead\\.ref\\b", "\\bhead_ref\\b", "\\bmerge_ref\\b", "\\bpr_head_ref\\b",
// env vars
"GITHUB_HEAD_REF",
], _, _)