mirror of
https://github.com/github/codeql.git
synced 2026-05-05 21:55:19 +02:00
Merge pull request #99 from github/bash_parser
Improve Bash script parser
This commit is contained in:
@@ -294,9 +294,27 @@ class Run extends Step instanceof RunImpl {
|
||||
|
||||
string getWorkingDirectory() { result = super.getWorkingDirectory() }
|
||||
|
||||
string getStmt(int i) { result = super.getStmt(i) }
|
||||
|
||||
string getAStmt() { result = super.getAStmt() }
|
||||
|
||||
string getCommand(int i) { result = super.getCommand(i) }
|
||||
|
||||
string getACommand() { result = super.getACommand() }
|
||||
|
||||
predicate getAssignment(int i, string name, string value) { super.getAssignment(i, name, value) }
|
||||
|
||||
predicate getAnAssignment(string name, string value) { super.getAnAssignment(name, value) }
|
||||
|
||||
predicate getAWriteToGitHubEnv(string name, string value) {
|
||||
super.getAWriteToGitHubEnv(name, value)
|
||||
}
|
||||
|
||||
predicate getAWriteToGitHubOutput(string name, string value) {
|
||||
super.getAWriteToGitHubOutput(name, value)
|
||||
}
|
||||
|
||||
predicate getAWriteToGitHubPath(string value) { super.getAWriteToGitHubPath(value) }
|
||||
}
|
||||
|
||||
abstract class SimpleReferenceExpression extends AstNode instanceof SimpleReferenceExpressionImpl {
|
||||
|
||||
@@ -24,219 +24,6 @@ string trimQuotes(string str) {
|
||||
result = str.trim().regexpReplaceAll("^(\"|')", "").regexpReplaceAll("(\"|')$", "")
|
||||
}
|
||||
|
||||
/** 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)) +
|
||||
// TODO: there are some >> $GITHUB_ENV, >> $GITHUB_OUTPUT, >> "$GITHUB_ENV" lefotvers in content
|
||||
//.regexpReplaceAll("\\s*(>|>>)\\s*\\$[{]*" + file + "(.*?)[}]*", "")
|
||||
")\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)
|
||||
)
|
||||
}
|
||||
|
||||
predicate writeToGitHubEnv(Run run, string content) {
|
||||
extractFileWrite(run.getScript(), "GITHUB_ENV", content)
|
||||
}
|
||||
|
||||
predicate writeToGitHubOutput(Run run, string content) {
|
||||
extractFileWrite(run.getScript(), "GITHUB_OUTPUT", content)
|
||||
}
|
||||
|
||||
predicate writeToGitHubPath(Run run, string content) {
|
||||
extractFileWrite(run.getScript(), "GITHUB_PATH", 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)
|
||||
}
|
||||
|
||||
predicate inPrivilegedContext(AstNode node, Event event) {
|
||||
node.getEnclosingJob().isPrivilegedExternallyTriggerable(event)
|
||||
}
|
||||
@@ -245,16 +32,6 @@ predicate inNonPrivilegedContext(AstNode node) {
|
||||
not node.getEnclosingJob().isPrivilegedExternallyTriggerable(_)
|
||||
}
|
||||
|
||||
bindingset[snippet]
|
||||
predicate outputsPartialFileContent(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
|
||||
}
|
||||
|
||||
string defaultBranchNames() {
|
||||
repositoryDataModel(_, result)
|
||||
or
|
||||
@@ -321,80 +98,225 @@ module Bash {
|
||||
|
||||
string partialFileContentCommand() { result = ["cat", "jq", "yq", "tail", "head"] }
|
||||
|
||||
bindingset[script]
|
||||
string getACommand(string script) {
|
||||
exists(string stmt_, string stmt, string subline2, string cmd |
|
||||
stmt_ = script.regexpReplaceAll("\\\\\\s*\n", "").splitAt("\n") and
|
||||
stmt =
|
||||
[
|
||||
// $() command substitution
|
||||
stmt_
|
||||
.regexpFind("\\$\\((?:[^()]+|\\((?:[^()]+|\\([^()]*\\))*\\))*\\)", _, _)
|
||||
.regexpReplaceAll("^\\$\\(", "")
|
||||
.regexpReplaceAll("\\)$", ""),
|
||||
// `...` command substitution
|
||||
stmt_
|
||||
.regexpFind("\\`[^\\`]+\\`", _, _)
|
||||
.regexpReplaceAll("^\\`", "")
|
||||
.regexpReplaceAll("\\`$", ""),
|
||||
// original line with no substitutions
|
||||
stmt_
|
||||
.regexpReplaceAll("\\`[^\\`]+\\`", "SUBCOMMAND")
|
||||
.regexpReplaceAll("\\$\\((?:[^()]+|\\((?:[^()]+|\\([^()]*\\))*\\))*\\)", "SUBCOMMAND")
|
||||
] and
|
||||
// We shoulg replace quoted arguments with a placeholder to avoid splitting them
|
||||
// eg: ls | grep -E "*.(tar.gz|zip)$"
|
||||
//subline2 = subline.regexpReplaceAll("\"([^\"]+)\"", "$0").regexpReplaceAll("'([^']+)'", "$0") and
|
||||
(
|
||||
stmt.regexpMatch(".*\"([^\"]+)\".*") and
|
||||
exists(int i |
|
||||
subline2 =
|
||||
stmt.replaceAll(stmt.regexpFind("\"([^\"]+)\"", _, i),
|
||||
stmt.regexpFind("\"([^\"]+)\"", _, i)
|
||||
.replaceAll("|", "::PIPE::")
|
||||
.replaceAll(";", "::SEMICOLON::")
|
||||
.replaceAll("&&", "::AND::")
|
||||
.replaceAll("||", "::OR::"))
|
||||
)
|
||||
or
|
||||
stmt.regexpMatch(".*'([^']+)'.*") and
|
||||
exists(int i |
|
||||
subline2 =
|
||||
stmt.replaceAll(stmt.regexpFind("'([^']+)'", _, i),
|
||||
stmt.regexpFind("'([^']+)'", _, i)
|
||||
.replaceAll("|", "::PIPE::")
|
||||
.replaceAll(";", "::SEMICOLON::")
|
||||
.replaceAll("&&", "::AND::")
|
||||
.replaceAll("||", "::OR::"))
|
||||
)
|
||||
or
|
||||
not stmt.regexpMatch(".*'([^']+)'.*") and
|
||||
not stmt.regexpMatch(".*\"([^\"]+)\".*") and
|
||||
subline2 = stmt
|
||||
) and
|
||||
cmd = subline2.splitAt(splitSeparators()).trim() and
|
||||
// when splitting the line with a separator that is not found, 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 cmd.indexOf(splitSeparators()) > -1 and
|
||||
not cmd =
|
||||
[
|
||||
"", "for", "in", "do", "done", "if", "then", "else", "elif", "fi", "while", "until",
|
||||
"case", "esac", "{", "}"
|
||||
] and
|
||||
result =
|
||||
cmd.replaceAll("::PIPE::", "|")
|
||||
.replaceAll("::SEMICOLON::", ";")
|
||||
.replaceAll("::AND::", "&&")
|
||||
.replaceAll("::OR::", "||")
|
||||
/** 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 getAnAssignment(string script, string name, string value) {
|
||||
exists(string stmt |
|
||||
stmt = script.regexpReplaceAll("\\\\\\s*\n", "").splitAt("\n").trim() and
|
||||
name = stmt.regexpCapture("^([a-zA-Z0-9\\-_]+)=.*", 1) and
|
||||
value = stmt.regexpCapture("^[a-zA-Z0-9\\-_]+=(.*)", 1)
|
||||
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" +
|
||||
"\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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1319,12 +1319,6 @@ class RunImpl extends StepImpl {
|
||||
|
||||
string getScript() { result = script.getValue().regexpReplaceAll("\\\\\\s*\n", "") }
|
||||
|
||||
string getACommand() { result = Bash::getACommand(this.getScript()) }
|
||||
|
||||
predicate getAnAssignment(string name, string value) {
|
||||
Bash::getAnAssignment(this.getScript(), name, value)
|
||||
}
|
||||
|
||||
ScalarValueImpl getScriptScalar() { result = TScalarValueNode(script) }
|
||||
|
||||
ExpressionImpl getAnScriptExpr() { result.getParentNode().getNode() = script }
|
||||
@@ -1344,6 +1338,194 @@ class RunImpl extends StepImpl {
|
||||
.regexpReplaceAll("^\\./", "GITHUB_WORKSPACE/")
|
||||
else result = "GITHUB_WORKSPACE/"
|
||||
}
|
||||
|
||||
private string lineProducer(int i) {
|
||||
result = script.getValue().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 cmdProducer(int i) {
|
||||
result = this.quotedStringLineProducer(i).splitAt(Bash::splitSeparators()).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::splitSeparators()) > -1
|
||||
}
|
||||
|
||||
private predicate doRestoreQuotedStrings(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.doRestoreQuotedStrings(line, round - 1, old, middle) and
|
||||
this.rankedQuotedStringReplacements(round, target, replacement) and
|
||||
new = middle.replaceAll(replacement, target)
|
||||
)
|
||||
}
|
||||
|
||||
private string restoredQuotedStringLineProducer(int i) {
|
||||
result =
|
||||
max(int round, string new | this.doRestoreQuotedStrings(i, round, _, new) | new order by round)
|
||||
}
|
||||
|
||||
private predicate doRestoreCmdSubstitutions(int line, int round, string old, string new) {
|
||||
round = 0 and
|
||||
old = this.restoredQuotedStringLineProducer(line) and
|
||||
new = old
|
||||
or
|
||||
round > 0 and
|
||||
exists(string middle, string target, string replacement |
|
||||
this.doRestoreCmdSubstitutions(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.doRestoreCmdSubstitutions(i, round, _, new)
|
||||
|
|
||||
new order by round
|
||||
)
|
||||
}
|
||||
|
||||
string getAStmt() { result = this.getStmt(_) }
|
||||
|
||||
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) }
|
||||
|
||||
string getCommand(int i) {
|
||||
result = this.getStmt(i) 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 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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -82,20 +82,17 @@ predicate envToArgInjSink(string var_name, Run run, string command) {
|
||||
*/
|
||||
bindingset[var_name]
|
||||
predicate envToSpecialFile(string file, string var_name, Run run, string key) {
|
||||
exists(string content, string value |
|
||||
exists(string value |
|
||||
(
|
||||
file = "GITHUB_ENV" and
|
||||
writeToGitHubEnv(run, content) and
|
||||
extractVariableAndValue(content, key, value)
|
||||
run.getAWriteToGitHubEnv(key, value)
|
||||
or
|
||||
file = "GITHUB_OUTPUT" and
|
||||
writeToGitHubOutput(run, content) and
|
||||
extractVariableAndValue(content, key, value)
|
||||
run.getAWriteToGitHubOutput(key, value)
|
||||
or
|
||||
file = "GITHUB_PATH" and
|
||||
writeToGitHubPath(run, content) and
|
||||
key = "path" and
|
||||
value = content
|
||||
run.getAWriteToGitHubPath(value) and
|
||||
key = "path"
|
||||
) and
|
||||
envToRunExpr(var_name, run, value)
|
||||
)
|
||||
@@ -144,14 +141,13 @@ predicate envToOutputStoreStep(DataFlow::Node pred, DataFlow::Node succ, DataFlo
|
||||
}
|
||||
|
||||
predicate envToEnvStoreStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::ContentSet c) {
|
||||
exists(Run run, string var_name, string content, string key, string value |
|
||||
writeToGitHubEnv(run, content) and
|
||||
extractVariableAndValue(content, key, value) and
|
||||
exists(Run run, string var_name, string key, string value |
|
||||
run.getAWriteToGitHubEnv(key, value) and
|
||||
c = any(DataFlow::FieldContent ct | ct.getName() = key) and
|
||||
pred.asExpr() = run.getInScopeEnvVarExpr(var_name) and
|
||||
// we store the taint on the enclosing job since the may not exist an implicit env attribute
|
||||
succ.asExpr() = run.getEnclosingJob() and
|
||||
isBashParameterExpansion(value, var_name, _, _)
|
||||
Bash::isBashParameterExpansion(value, var_name, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -178,29 +174,24 @@ predicate controlledCWD(Step artifact) {
|
||||
* echo "::set-output name=id::$foo
|
||||
*/
|
||||
predicate artifactToOutputStoreStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::ContentSet c) {
|
||||
exists(Run run, Step artifact, string content, string key, string value |
|
||||
exists(Run run, Step artifact, string key, string value |
|
||||
controlledCWD(artifact) and
|
||||
(
|
||||
// A file is read and its content is assigned to an env var
|
||||
// - run: |
|
||||
// foo=$(<pr-id.txt)"
|
||||
// echo "::set-output name=id::$foo
|
||||
exists(string var_name, string line, string assignment_regexp, string file_read |
|
||||
run.getScript().splitAt("\n") = line and
|
||||
assignment_regexp = "([a-zA-Z0-9\\-_]+)=(.*)" and
|
||||
var_name = line.regexpCapture(assignment_regexp, 1) and
|
||||
file_read = line.regexpCapture(assignment_regexp, 2) and
|
||||
outputsPartialFileContent(file_read) and
|
||||
exists(string var_name, string file_read |
|
||||
run.getAnAssignment(var_name, file_read) and
|
||||
Bash::outputsPartialFileContent(run, file_read) and
|
||||
envToRunExpr(var_name, run, value) and
|
||||
writeToGitHubOutput(run, content) and
|
||||
extractVariableAndValue(content, key, value)
|
||||
run.getAWriteToGitHubOutput(key, value)
|
||||
)
|
||||
or
|
||||
// A file is read and its content is assigned to an output
|
||||
// - run: echo "::set-output name=id::$(<pr-id.txt)"
|
||||
writeToGitHubOutput(run, content) and
|
||||
extractVariableAndValue(content, key, value) and
|
||||
outputsPartialFileContent(value)
|
||||
run.getAWriteToGitHubOutput(key, value) and
|
||||
Bash::outputsPartialFileContent(run, value)
|
||||
) and
|
||||
c = any(DataFlow::FieldContent ct | ct.getName() = key) and
|
||||
artifact.getAFollowingStep() = run and
|
||||
@@ -217,29 +208,24 @@ predicate artifactToOutputStoreStep(DataFlow::Node pred, DataFlow::Node succ, Da
|
||||
* echo "bar=${foo}" >> "$GITHUB_ENV"
|
||||
*/
|
||||
predicate artifactToEnvStoreStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::ContentSet c) {
|
||||
exists(Run run, string content, string key, string value, Step artifact |
|
||||
exists(Run run, string key, string value, Step artifact |
|
||||
controlledCWD(artifact) and
|
||||
(
|
||||
// A file is read and its content is assigned to an env var
|
||||
// - run: |
|
||||
// foo=$(<pr-id.txt)"
|
||||
// echo "bar=${foo}" >> "$GITHUB_ENV"
|
||||
exists(string var_name, string line, string assignment_regexp, string file_read |
|
||||
run.getScript().splitAt("\n") = line and
|
||||
assignment_regexp = "([a-zA-Z0-9\\-_]+)=(.*)" and
|
||||
var_name = line.regexpCapture(assignment_regexp, 1) and
|
||||
file_read = line.regexpCapture(assignment_regexp, 2) and
|
||||
outputsPartialFileContent(file_read) and
|
||||
exists(string var_name, string file_read |
|
||||
run.getAnAssignment(var_name, file_read) and
|
||||
Bash::outputsPartialFileContent(run, file_read) and
|
||||
envToRunExpr(var_name, run, value) and
|
||||
writeToGitHubEnv(run, content) and
|
||||
extractVariableAndValue(content, key, value)
|
||||
run.getAWriteToGitHubEnv(key, value)
|
||||
)
|
||||
or
|
||||
// A file is read and its content is assigned to an output
|
||||
// - run: echo "foo=$(<pr-id.txt)" >> "$GITHUB_ENV"
|
||||
writeToGitHubEnv(run, content) and
|
||||
extractVariableAndValue(content, key, value) and
|
||||
outputsPartialFileContent(value)
|
||||
run.getAWriteToGitHubEnv(key, value) and
|
||||
Bash::outputsPartialFileContent(run, value)
|
||||
) and
|
||||
c = any(DataFlow::FieldContent ct | ct.getName() = key) and
|
||||
artifact.getAFollowingStep() = run and
|
||||
|
||||
@@ -27,19 +27,19 @@ class EnvPathInjectionFromFileReadSink extends EnvPathInjectionSink {
|
||||
(
|
||||
// e.g.
|
||||
// cat test-results/.env >> $GITHUB_PATH
|
||||
fileToGitHubPath(run, _)
|
||||
Bash::fileToGitHubPath(run, _)
|
||||
or
|
||||
exists(string value |
|
||||
writeToGitHubPath(run, value) and
|
||||
run.getAWriteToGitHubPath(value) and
|
||||
(
|
||||
outputsPartialFileContent(value)
|
||||
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
|
||||
outputsPartialFileContent(var_value) and
|
||||
Bash::outputsPartialFileContent(run, var_value) and
|
||||
(
|
||||
value.matches("%$" + ["", "{", "ENV{"] + var_name + "%")
|
||||
or
|
||||
|
||||
@@ -29,22 +29,21 @@ class EnvVarInjectionFromFileReadSink extends EnvVarInjectionSink {
|
||||
(
|
||||
// e.g.
|
||||
// cat test-results/.env >> $GITHUB_ENV
|
||||
fileToGitHubEnv(run, _)
|
||||
Bash::fileToGitHubEnv(run, _)
|
||||
or
|
||||
exists(string content, string value |
|
||||
writeToGitHubEnv(run, content) and
|
||||
extractVariableAndValue(content, _, value) and
|
||||
exists(string value |
|
||||
run.getAWriteToGitHubEnv(_, value) and
|
||||
(
|
||||
// e.g.
|
||||
// echo "FOO=$(cat test-results/sha-number)" >> $GITHUB_ENV
|
||||
outputsPartialFileContent(value)
|
||||
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
|
||||
outputsPartialFileContent(var_value) and
|
||||
Bash::outputsPartialFileContent(run, var_value) and
|
||||
(
|
||||
value.matches("%$" + ["", "{", "ENV{"] + var_name + "%")
|
||||
or
|
||||
|
||||
@@ -38,27 +38,25 @@ class OutputClobberingFromFileReadSink extends OutputClobberingSink {
|
||||
(
|
||||
// e.g.
|
||||
// cat test-results/.vars >> $GITHUB_OUTPUT
|
||||
fileToGitHubOutput(run, _)
|
||||
Bash::fileToGitHubOutput(run, _)
|
||||
or
|
||||
exists(string content, string key, string value |
|
||||
writeToGitHubOutput(run, content) and
|
||||
extractVariableAndValue(content, key, value) and
|
||||
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 content2, string key2 |
|
||||
writeToGitHubOutput(run, content2) and
|
||||
extractVariableAndValue(content2, key2, _) and
|
||||
exists(string key2 |
|
||||
run.getAWriteToGitHubOutput(key2, _) and
|
||||
not key2 = key
|
||||
) and
|
||||
(
|
||||
outputsPartialFileContent(value)
|
||||
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
|
||||
outputsPartialFileContent(var_value) and
|
||||
Bash::outputsPartialFileContent(run, var_value) and
|
||||
(
|
||||
value.matches("%$" + ["", "{", "ENV{"] + var_name + "%")
|
||||
or
|
||||
@@ -87,9 +85,8 @@ class OutputClobberingFromEnvVarSink extends OutputClobberingSink {
|
||||
envToSpecialFile("GITHUB_OUTPUT", var_name, run, key) and
|
||||
// there is a different output variable in the same script
|
||||
// TODO: key2/value2 should be declared before key/value
|
||||
exists(string content2, string key2 |
|
||||
writeToGitHubOutput(run, content2) and
|
||||
extractVariableAndValue(content2, key2, _) and
|
||||
exists(string key2 |
|
||||
run.getAWriteToGitHubOutput(key2, _) and
|
||||
not key2 = key
|
||||
) and
|
||||
exists(run.getInScopeEnvVarExpr(var_name)) and
|
||||
@@ -118,7 +115,7 @@ class WorkflowCommandClobberingFromEnvVarSink extends OutputClobberingSink {
|
||||
WorkflowCommandClobberingFromEnvVarSink() {
|
||||
exists(Run run, string output_line, string clobbering_line, string var_name |
|
||||
run.getScript().splitAt("\n") = output_line and
|
||||
singleLineWorkflowCmd(output_line, "set-output", _, _) and
|
||||
Bash::singleLineWorkflowCmd(output_line, "set-output", _, _) and
|
||||
run.getScript().splitAt("\n") = clobbering_line and
|
||||
clobbering_line.regexpMatch(".*echo\\s+(-e\\s+)?(\"|')?\\$(\\{)?" + var_name + ".*") and
|
||||
exists(run.getInScopeEnvVarExpr(var_name)) and
|
||||
@@ -132,19 +129,16 @@ class WorkflowCommandClobberingFromFileReadSink extends OutputClobberingSink {
|
||||
exists(Run run, string output_line, string clobbering_line |
|
||||
run.getScriptScalar() = this.asExpr() and
|
||||
run.getScript().splitAt("\n") = output_line and
|
||||
singleLineWorkflowCmd(output_line, "set-output", _, _) and
|
||||
Bash::singleLineWorkflowCmd(output_line, "set-output", _, _) and
|
||||
run.getScript().splitAt("\n") = clobbering_line and
|
||||
(
|
||||
// A file is read and its 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, string assign_line, string assignment_regexp |
|
||||
run.getScript().splitAt("\n") = assign_line and
|
||||
assignment_regexp = "([a-zA-Z0-9\\-_]+)=(.*)" and
|
||||
var_name = assign_line.regexpCapture(assignment_regexp, 1) and
|
||||
value = assign_line.regexpCapture(assignment_regexp, 2) and
|
||||
outputsPartialFileContent(trimQuotes(value)) and
|
||||
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 + ".*")
|
||||
)
|
||||
or
|
||||
|
||||
@@ -53,11 +53,10 @@ class LocalActionUsesStep extends PoisonableStep, UsesStep {
|
||||
}
|
||||
|
||||
class EnvVarInjectionFromFileReadRunStep extends PoisonableStep, Run {
|
||||
string value;
|
||||
|
||||
EnvVarInjectionFromFileReadRunStep() {
|
||||
exists(string content, string value |
|
||||
writeToGitHubEnv(this, content) and
|
||||
extractVariableAndValue(content, _, value) and
|
||||
outputsPartialFileContent(value)
|
||||
)
|
||||
this.getAWriteToGitHubEnv(_, value) and
|
||||
Bash::outputsPartialFileContent(this, value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
| .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 "SUBCOMMAND" |
|
||||
| .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 "SUBCOMMAND" |
|
||||
| .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 "SUBCOMMAND SUBCOMMAND" |
|
||||
| .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 |
|
||||
@@ -20,19 +20,23 @@
|
||||
| .github/workflows/expression_nodes.yml:10:9:13:6 | Run Step | LINE 2 echo '${{github.event.issue.body}}' |
|
||||
| .github/workflows/expression_nodes.yml:13:9:16:6 | Run Step | LINE 1 echo '${{ github.event.comment.body }}' echo '${{github.event.issue.body}}' |
|
||||
| .github/workflows/expression_nodes.yml:16:9:20:6 | Run Step | LINE 1 echo '${{ github.event.comment.body }}' |
|
||||
| .github/workflows/expression_nodes.yml:16:9:20:6 | Run Step | LINE 1 echo qstr:0:0:12:34:githubeventcommentbody |
|
||||
| .github/workflows/expression_nodes.yml:16:9:20:6 | Run Step | LINE 1 echo qstr:2:0:12:34:githubeventcommentbody |
|
||||
| .github/workflows/expression_nodes.yml:16:9:20:6 | Run Step | LINE 2 echo '${{github.event.issue.body}}' |
|
||||
| .github/workflows/expression_nodes.yml:16:9:20:6 | Run Step | LINE 3 echo '${{ github.event.comment.body }}' |
|
||||
| .github/workflows/expression_nodes.yml:16:9:20:6 | Run Step | LINE 3 echo qstr:0:0:12:34:githubeventcommentbody |
|
||||
| .github/workflows/expression_nodes.yml:16:9:20:6 | Run Step | LINE 3 echo qstr:2:0:12:34:githubeventcommentbody |
|
||||
| .github/workflows/expression_nodes.yml:20:9:21:47 | Run Step | LINE 1 echo '${{ github.event.comment.body }}' echo '${{github.event.issue.body}}' |
|
||||
| .github/workflows/multiline2.yml:11:9:15:6 | Run Step | echo "CHANGELOGEOF" |
|
||||
| .github/workflows/multiline2.yml:11:9:15:6 | Run Step | echo "changelog<<CHANGELOGEOF" |
|
||||
| .github/workflows/multiline2.yml:11:9:15:6 | Run Step | echo -e "$FILTERED_CHANGELOG" |
|
||||
| .github/workflows/multiline2.yml:11:9:15:6 | Run Step | tee -a $GITHUB_OUTPUT |
|
||||
| .github/workflows/multiline2.yml:15:9:20:6 | Run Step | EOF=SUBCOMMAND |
|
||||
| .github/workflows/multiline2.yml:15:9:20:6 | Run Step | EOF=$(dd if=/dev/urandom bs=15 count=1 status=none \| base64) |
|
||||
| .github/workflows/multiline2.yml:15:9:20:6 | Run Step | base64 |
|
||||
| .github/workflows/multiline2.yml:15:9:20:6 | Run Step | cat status.output.json |
|
||||
| .github/workflows/multiline2.yml:15:9:20:6 | Run Step | dd if=/dev/urandom bs=15 count=1 status=none |
|
||||
| .github/workflows/multiline2.yml:15:9:20:6 | Run Step | echo "$(cat status.output.json)" |
|
||||
| .github/workflows/multiline2.yml:15:9:20:6 | Run Step | echo "$EOF" |
|
||||
| .github/workflows/multiline2.yml:15:9:20:6 | Run Step | echo "SUBCOMMAND" |
|
||||
| .github/workflows/multiline2.yml:15:9:20:6 | Run Step | echo "status<<$EOF" |
|
||||
| .github/workflows/multiline2.yml:15:9:20:6 | Run Step | tee -a $GITHUB_OUTPUT |
|
||||
| .github/workflows/multiline2.yml:20:9:24:6 | Run Step | echo "$EOF" |
|
||||
@@ -71,7 +75,7 @@
|
||||
| .github/workflows/multiline2.yml:58:9:63:6 | Run Step | echo "FOO=$TITLE" |
|
||||
| .github/workflows/multiline2.yml:58:9:63:6 | Run Step | tee -a "$GITHUB_ENV" |
|
||||
| .github/workflows/multiline2.yml:63:9:66:6 | Run Step | cat issue.txt |
|
||||
| .github/workflows/multiline2.yml:63:9:66:6 | Run Step | echo REPO_NAME=SUBCOMMAND |
|
||||
| .github/workflows/multiline2.yml:63:9:66:6 | Run Step | echo REPO_NAME=$(cat issue.txt \| sed 's/\\\\r/\\\\n/g' \| grep -ioE '\\\\s*[a-z0-9_-]+/[a-z0-9_-]+\\\\s*$' \| tr -d ' ') |
|
||||
| .github/workflows/multiline2.yml:63:9:66:6 | Run Step | grep -ioE '\\\\s*[a-z0-9_-]+/[a-z0-9_-]+\\\\s*$' |
|
||||
| .github/workflows/multiline2.yml:63:9:66:6 | Run Step | sed 's/\\\\r/\\\\n/g' |
|
||||
| .github/workflows/multiline2.yml:63:9:66:6 | Run Step | tee -a $GITHUB_ENV |
|
||||
@@ -93,12 +97,12 @@
|
||||
| .github/workflows/multiline.yml:11:9:15:6 | Run Step | echo "CHANGELOGEOF" >> $GITHUB_OUTPUT |
|
||||
| .github/workflows/multiline.yml:11:9:15:6 | Run Step | echo "changelog<<CHANGELOGEOF" >> $GITHUB_OUTPUT |
|
||||
| .github/workflows/multiline.yml:11:9:15:6 | Run Step | echo -e "$FILTERED_CHANGELOG" >> $GITHUB_OUTPUT |
|
||||
| .github/workflows/multiline.yml:15:9:20:6 | Run Step | EOF=SUBCOMMAND |
|
||||
| .github/workflows/multiline.yml:15:9:20:6 | Run Step | EOF=$(dd if=/dev/urandom bs=15 count=1 status=none \| base64) |
|
||||
| .github/workflows/multiline.yml:15:9:20:6 | Run Step | base64 |
|
||||
| .github/workflows/multiline.yml:15:9:20:6 | Run Step | cat status.output.json |
|
||||
| .github/workflows/multiline.yml:15:9:20:6 | Run Step | dd if=/dev/urandom bs=15 count=1 status=none |
|
||||
| .github/workflows/multiline.yml:15:9:20:6 | Run Step | echo "$(cat status.output.json)" >> $GITHUB_OUTPUT |
|
||||
| .github/workflows/multiline.yml:15:9:20:6 | Run Step | echo "$EOF" >> $GITHUB_OUTPUT |
|
||||
| .github/workflows/multiline.yml:15:9:20:6 | Run Step | echo "SUBCOMMAND" >> $GITHUB_OUTPUT |
|
||||
| .github/workflows/multiline.yml:15:9:20:6 | Run Step | echo "status<<$EOF" >> $GITHUB_OUTPUT |
|
||||
| .github/workflows/multiline.yml:20:9:24:6 | Run Step | echo "$EOF" >> $GITHUB_OUTPUT |
|
||||
| .github/workflows/multiline.yml:20:9:24:6 | Run Step | echo "response<<$EOF" >> $GITHUB_OUTPUT |
|
||||
@@ -132,7 +136,7 @@
|
||||
| .github/workflows/multiline.yml:58:9:63:6 | Run Step | cat <<-EOF >> "$GITHUB_ENV" |
|
||||
| .github/workflows/multiline.yml:58:9:63:6 | Run Step | echo "FOO=$TITLE" |
|
||||
| .github/workflows/multiline.yml:63:9:66:6 | Run Step | cat issue.txt |
|
||||
| .github/workflows/multiline.yml:63:9:66:6 | Run Step | echo REPO_NAME=SUBCOMMAND >> $GITHUB_ENV |
|
||||
| .github/workflows/multiline.yml:63:9:66:6 | Run Step | echo REPO_NAME=$(cat issue.txt \| sed 's/\\\\r/\\\\n/g' \| grep -ioE '\\\\s*[a-z0-9_-]+/[a-z0-9_-]+\\\\s*$' \| tr -d ' ') >> $GITHUB_ENV |
|
||||
| .github/workflows/multiline.yml:63:9:66:6 | Run Step | grep -ioE '\\\\s*[a-z0-9_-]+/[a-z0-9_-]+\\\\s*$' |
|
||||
| .github/workflows/multiline.yml:63:9:66:6 | Run Step | sed 's/\\\\r/\\\\n/g' |
|
||||
| .github/workflows/multiline.yml:63:9:66:6 | Run Step | tr -d ' ' |
|
||||
@@ -159,7 +163,7 @@
|
||||
| .github/workflows/poisonable_steps.yml:16:9:17:6 | Run Step | echo foo |
|
||||
| .github/workflows/poisonable_steps.yml:17:9:18:6 | Run Step | ./venv/bin/activate |
|
||||
| .github/workflows/poisonable_steps.yml:18:9:19:6 | Run Step | sh venv/bin/activate.sh |
|
||||
| .github/workflows/poisonable_steps.yml:19:9:20:6 | Run Step | echo SUBCOMMAND |
|
||||
| .github/workflows/poisonable_steps.yml:19:9:20:6 | Run Step | echo $(sh venv/bin/activate.sh) |
|
||||
| .github/workflows/poisonable_steps.yml:19:9:20:6 | Run Step | sh venv/bin/activate.sh |
|
||||
| .github/workflows/poisonable_steps.yml:20:9:21:6 | Run Step | echo bar |
|
||||
| .github/workflows/poisonable_steps.yml:20:9:21:6 | Run Step | echo foo |
|
||||
@@ -185,15 +189,11 @@
|
||||
| .github/workflows/poisonable_steps.yml:32:9:33:6 | Run Step | echo "bar" |
|
||||
| .github/workflows/poisonable_steps.yml:32:9:33:6 | Run Step | echo "foo" |
|
||||
| .github/workflows/poisonable_steps.yml:32:9:33:6 | Run Step | npm i |
|
||||
| .github/workflows/poisonable_steps.yml:33:9:34:6 | Run Step | echo "foo SUBCOMMAND bar" |
|
||||
| .github/workflows/poisonable_steps.yml:33:9:34:6 | Run Step | echo "foo `npm i` bar" |
|
||||
| .github/workflows/poisonable_steps.yml:33:9:34:6 | Run Step | npm i |
|
||||
| .github/workflows/poisonable_steps.yml:34:9:35:6 | Run Step | dotnet test foo/Tests.csproj -c Release |
|
||||
| .github/workflows/poisonable_steps.yml:35:9:36:6 | Run Step | go run foo.go |
|
||||
| .github/workflows/poisonable_steps.yml:36:9:37:6 | Run Step | " config.json |
|
||||
| .github/workflows/poisonable_steps.yml:36:9:37:6 | Run Step | git_branch = .* |
|
||||
| .github/workflows/poisonable_steps.yml:36:9:37:6 | Run Step | git_branch = \\"$GITHUB_HEAD_REF\\"\|" config.json |
|
||||
| .github/workflows/poisonable_steps.yml:36:9:37:6 | Run Step | sed -i "s |
|
||||
| .github/workflows/poisonable_steps.yml:36:9:37:6 | Run Step | sed -i "s\|git_branch = .*\|git_branch = \\"$GITHUB_HEAD_REF\\" |
|
||||
| .github/workflows/poisonable_steps.yml:36:9:37:6 | Run Step | sed -i "s\|git_branch = .*\|git_branch = \\"$GITHUB_HEAD_REF\\"\|" config.json |
|
||||
| .github/workflows/poisonable_steps.yml:37:9:38:6 | Run Step | sed -f ./config.sed file.txt > foo.txt |
|
||||
| .github/workflows/poisonable_steps.yml:38:9:39:6 | Run Step | sed -f config file.txt > foo.txt |
|
||||
| .github/workflows/poisonable_steps.yml:39:9:40:6 | Run Step | awk -f ./config.awk > foo.txt |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -81,7 +81,7 @@ query predicate writeToGitHubEnv1(string content) {
|
||||
//"FOO\necho \"VAR3<<EOF\" >> $GITHUB_ENV\necho \"$TITLE\" >> $GITHUB_ENV\necho \"EOF\" >> $GITHUB_ENV\nBAR",
|
||||
] and
|
||||
//linesFileWrite(t, _, "$GITHUB_ENV", content, _)
|
||||
blockFileWrite(t, _, "$GITHUB_ENV", content, _)
|
||||
Bash::blockFileWrite(t, _, "$GITHUB_ENV", content, _)
|
||||
//extractFileWrite(t, "GITHUB_ENV", content)
|
||||
)
|
||||
}
|
||||
@@ -113,8 +113,8 @@ query predicate writeToGitHubEnv(string key, string value, string content) {
|
||||
"echo VAR15=$(<test-results3/sha-number) >> $GITHUB_ENV",
|
||||
"echo VAR16=$(cat issue.txt | sed 's/\\r/\\n/g' | grep -ioE '\\s*[a-z0-9_-]+/[a-z0-9_-]+\\s*$' | tr -d ' ') >> $GITHUB_ENV",
|
||||
] and
|
||||
extractFileWrite(t, "GITHUB_ENV", content) and
|
||||
extractVariableAndValue(content, key, value)
|
||||
Bash::extractFileWrite(t, "GITHUB_ENV", content) and
|
||||
Bash::extractVariableAndValue(content, key, value)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -132,8 +132,8 @@ query predicate writeToGitHubOutput(string key, string value, string content) {
|
||||
"echo VAR8=$(<test-results5/sha-number) >> ${GITHUB_OUTPUT}",
|
||||
"echo VAR9=$(<test-results6/sha-number) >> \"${GITHUB_OUTPUT}\"",
|
||||
] and
|
||||
extractFileWrite(t, "GITHUB_OUTPUT", content) and
|
||||
extractVariableAndValue(content, key, value)
|
||||
Bash::extractFileWrite(t, "GITHUB_OUTPUT", content) and
|
||||
Bash::extractVariableAndValue(content, key, value)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -150,6 +150,6 @@ query predicate isBashParameterExpansion(string parameter, string operator, stri
|
||||
"${parameter21%%pattern}", "${parameter22/pattern/string}",
|
||||
"${parameter23//pattern/string}",
|
||||
] and
|
||||
isBashParameterExpansion(test, parameter, operator, params)
|
||||
Bash::isBashParameterExpansion(test, parameter, operator, params)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user