Merge pull request #99 from github/bash_parser

Improve Bash script parser
This commit is contained in:
Alvaro Muñoz
2024-10-03 14:13:53 +02:00
committed by GitHub
11 changed files with 492 additions and 394 deletions

View File

@@ -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 {

View File

@@ -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
)
}
}

View File

@@ -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)
}
}
/**

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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 |

View File

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

View File

@@ -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)
)
}