initial commit of actions
This commit is contained in:
commit
949ece5785
44660 changed files with 12034344 additions and 0 deletions
45
github/codeql-action-v2/queries/binary-planting.ql
Normal file
45
github/codeql-action-v2/queries/binary-planting.ql
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* @name Exec call vulnerable to binary planting
|
||||
* @description On Windows, executing a binary with an unqualified name will execute a binary in the working directory in preference to a binary on PATH.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @id javascript/codeql-action/binary-planting
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class WhichBarrierGuardNode extends DataFlow::BarrierGuardNode, DataFlow::InvokeNode {
|
||||
WhichBarrierGuardNode() { getCalleeName() = "which" }
|
||||
|
||||
override predicate blocks(boolean outcome, Expr e) {
|
||||
outcome = true and
|
||||
e = getArgument(0).asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
class BinaryPlantingConfiguration extends DataFlow::Configuration {
|
||||
BinaryPlantingConfiguration() {
|
||||
this = "BinaryPlantingConfiguration"
|
||||
}
|
||||
|
||||
override predicate isSource(Node node) {
|
||||
node.asExpr() instanceof StringLiteral and
|
||||
not node.asExpr().(StringLiteral).getValue().matches("%/%") and
|
||||
not node.getFile().getBaseName().matches("%.test.ts")
|
||||
}
|
||||
|
||||
override predicate isSink(Node node) {
|
||||
node instanceof SystemCommandExecution or
|
||||
exists(InvokeExpr e | e.getCalleeName() = "ToolRunner" and e.getArgument(0) = node.asExpr())
|
||||
}
|
||||
|
||||
override predicate isBarrierGuard(DataFlow::BarrierGuardNode guard) {
|
||||
guard instanceof WhichBarrierGuardNode
|
||||
}
|
||||
}
|
||||
|
||||
from BinaryPlantingConfiguration cfg, PathNode source, PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select source.getNode(), source, sink, "This exec call might be vulnerable to Windows binary planting vulnerabilities."
|
||||
14
github/codeql-action-v2/queries/codeql-pack.lock.yml
Normal file
14
github/codeql-action-v2/queries/codeql-pack.lock.yml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
lockVersion: 1.0.0
|
||||
dependencies:
|
||||
codeql-javascript:
|
||||
version: 0.6.1
|
||||
codeql/regex:
|
||||
version: 0.0.12
|
||||
codeql/tutorial:
|
||||
version: 0.0.9
|
||||
codeql/util:
|
||||
version: 0.0.9
|
||||
codeql/yaml:
|
||||
version: 0.0.1
|
||||
compiled: false
|
||||
4
github/codeql-action-v2/queries/codeql-pack.yml
Normal file
4
github/codeql-action-v2/queries/codeql-pack.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
name: codeql-action-custom-queries-javascript
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
codeql/javascript-all: 0.6.1
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* @name Some environment variables may not exist in default setup workflows
|
||||
* @id javascript/codeql-action/default-setup-env-vars
|
||||
* @kind problem
|
||||
* @severity warning
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
bindingset[envVar]
|
||||
predicate isSafeForDefaultSetup(string envVar) {
|
||||
// Ignore internal Code Scanning environment variables
|
||||
envVar.matches("CODE_SCANNING_%") or
|
||||
envVar.matches("CODEQL_%") or
|
||||
envVar.matches("CODESCANNING_%") or
|
||||
envVar.matches("LGTM_%") or
|
||||
// We flag up usage of potentially unsafe parts of the GitHub event in `default-setup-event-context.ql`.
|
||||
envVar = "GITHUB_EVENT_PATH" or
|
||||
// The following environment variables are known to be safe for use with default setup
|
||||
envVar =
|
||||
[
|
||||
"GITHUB_ACTION_REF", "GITHUB_ACTION_REPOSITORY", "GITHUB_ACTOR", "GITHUB_API_URL",
|
||||
"GITHUB_BASE_REF", "GITHUB_EVENT_NAME", "GITHUB_JOB", "GITHUB_RUN_ATTEMPT", "GITHUB_RUN_ID",
|
||||
"GITHUB_SHA", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "GITHUB_TOKEN", "GITHUB_WORKFLOW",
|
||||
"GITHUB_WORKSPACE", "GOFLAGS", "ImageVersion", "JAVA_TOOL_OPTIONS", "RUNNER_ARCH",
|
||||
"RUNNER_ENVIRONMENT", "RUNNER_NAME", "RUNNER_OS", "RUNNER_TEMP", "RUNNER_TOOL_CACHE"
|
||||
]
|
||||
}
|
||||
|
||||
predicate envVarRead(DataFlow::Node node, string envVar) {
|
||||
node =
|
||||
any(DataFlow::PropRead read |
|
||||
read = NodeJSLib::process().getAPropertyRead("env").getAPropertyRead() and
|
||||
envVar = read.getPropertyName()
|
||||
) or
|
||||
node =
|
||||
any(DataFlow::CallNode call |
|
||||
call.getCalleeName().matches("get%EnvParam") and
|
||||
envVar = call.getArgument(0).getStringValue()
|
||||
)
|
||||
}
|
||||
|
||||
from DataFlow::Node read, string envVar
|
||||
where
|
||||
envVarRead(read, envVar) and
|
||||
not read.getFile().getBaseName().matches("%.test.ts") and
|
||||
not isSafeForDefaultSetup(envVar)
|
||||
select read,
|
||||
"The environment variable " + envVar +
|
||||
" may not exist in default setup workflows. If all uses are safe, add it to the list of " +
|
||||
"environment variables that are known to be safe in " +
|
||||
"'queries/default-setup-environment-variables.ql'. If this use is safe but others are not, " +
|
||||
"dismiss this alert as a false positive."
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* @name Some context properties may not exist in default setup workflows
|
||||
* @id javascript/codeql-action/default-setup-context-properties
|
||||
* @kind path-problem
|
||||
* @severity warning
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class NotParsedLabel extends DataFlow::FlowLabel {
|
||||
NotParsedLabel() { this = "not-parsed" }
|
||||
}
|
||||
|
||||
class ParsedLabel extends DataFlow::FlowLabel {
|
||||
ParsedLabel() { this = "parsed" }
|
||||
}
|
||||
|
||||
class EventContextAccessConfiguration extends DataFlow::Configuration {
|
||||
EventContextAccessConfiguration() { this = "EventContextAccessConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel lbl) {
|
||||
source = NodeJSLib::process().getAPropertyRead("env").getAPropertyRead("GITHUB_EVENT_PATH") and
|
||||
lbl instanceof NotParsedLabel
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel lbl) {
|
||||
sink instanceof DataFlow::PropRead and
|
||||
lbl instanceof ParsedLabel and
|
||||
not exists(DataFlow::PropRead n | sink = n.getBase()) and
|
||||
not sink.asExpr().getFile().getBaseName().matches("%.test.ts")
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(
|
||||
DataFlow::Node src, DataFlow::Node trg, DataFlow::FlowLabel inlbl, DataFlow::FlowLabel outlbl
|
||||
) {
|
||||
src = trg.(FileSystemReadAccess).getAPathArgument() and inlbl = outlbl
|
||||
or
|
||||
exists(JsonParserCall c |
|
||||
src = c.getInput() and
|
||||
trg = c.getOutput() and
|
||||
inlbl instanceof NotParsedLabel and
|
||||
outlbl instanceof ParsedLabel
|
||||
)
|
||||
or
|
||||
(
|
||||
TaintTracking::sharedTaintStep(src, trg) or
|
||||
DataFlow::SharedFlowStep::step(src, trg) or
|
||||
DataFlow::SharedFlowStep::step(src, trg, _, _)
|
||||
) and
|
||||
inlbl = outlbl
|
||||
}
|
||||
}
|
||||
|
||||
from EventContextAccessConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"This event context property may not exist in default setup workflows."
|
||||
24
github/codeql-action-v2/queries/import-action-entrypoint.ql
Normal file
24
github/codeql-action-v2/queries/import-action-entrypoint.ql
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* @name Import action entrypoint
|
||||
* @description Importing the entrypoint file for an action is dangerous
|
||||
* because the code from that action will be run when the file is imported.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @id javascript/codeql-action/import-action-entrypoint
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
class ActionEntrypointFile extends File {
|
||||
ActionEntrypointFile() {
|
||||
exists(Module m | m.getPath() = this.getAbsolutePath() and
|
||||
// This is quite a broad check and relies on the function name, but hopefully it'll be accurate enough
|
||||
m.getAStmt().getAChildExpr+().(CallExpr).getCalleeName() = "run") and
|
||||
// Requiring the relative path to exist limits us to files in the code repository and avoid libraries
|
||||
exists(this.getRelativePath())
|
||||
}
|
||||
}
|
||||
|
||||
from ImportDeclaration i
|
||||
where exists(ActionEntrypointFile f | i.getImportedModule().getPath() = f.getAbsolutePath())
|
||||
select i, "This imports the entrypoint file for an action. This will execute the code from the action."
|
||||
55
github/codeql-action-v2/queries/inconsistent-action-input.ql
Normal file
55
github/codeql-action-v2/queries/inconsistent-action-input.ql
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* @name Inconsistent action input
|
||||
* @description If multiple actions define an input with the same name, then the input
|
||||
* must be defined in an identical way to avoid confusion for the user.
|
||||
* This also makes writing queries like required-action-input.ql easier.
|
||||
* @kind problem
|
||||
* @severity error
|
||||
* @id javascript/codeql-action/inconsistent-action-input
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* A declaration of a github action.
|
||||
*/
|
||||
class ActionDeclaration extends File {
|
||||
ActionDeclaration() {
|
||||
getRelativePath().matches("%/action.yml") and
|
||||
// Ignore internal Actions
|
||||
not getRelativePath().matches(".github/actions/%")
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the action.
|
||||
*/
|
||||
string getName() {
|
||||
result = getRelativePath().regexpCapture("(.*)/action.yml", 1)
|
||||
}
|
||||
|
||||
YamlDocument getRootNode() {
|
||||
result.getFile() = this
|
||||
}
|
||||
|
||||
YamlValue getInput(string inputName) {
|
||||
result = getRootNode().(YamlMapping).lookup("inputs").(YamlMapping).lookup(inputName)
|
||||
}
|
||||
}
|
||||
|
||||
predicate areNotEquivalent(YamlValue x, YamlValue y) {
|
||||
x.getTag() != y.getTag()
|
||||
or
|
||||
x.(YamlScalar).getValue() != y.(YamlScalar).getValue()
|
||||
or
|
||||
x.getNumChild() != y.getNumChild()
|
||||
or
|
||||
exists(int i | areNotEquivalent(x.getChild(i), y.getChild(i)))
|
||||
}
|
||||
|
||||
from ActionDeclaration actionA, ActionDeclaration actionB, string inputName
|
||||
where actionA.getName() < actionB.getName() // prevent duplicates which are permutations of the names
|
||||
and areNotEquivalent(actionA.getInput(inputName), actionB.getInput(inputName))
|
||||
// ram and threads inputs in different actions are supposed to have different description
|
||||
and inputName != "ram" and inputName != "threads"
|
||||
select actionA, "Action $@ and action $@ both declare input $@, however their definitions are not identical. This may be confusing to users.",
|
||||
actionA, actionA.getName(), actionB, actionB.getName(), inputName, inputName
|
||||
86
github/codeql-action-v2/queries/required-action-input.ql
Normal file
86
github/codeql-action-v2/queries/required-action-input.ql
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* @name Required action input
|
||||
* @description For action inputs the core.input represents input with no value as the emptystring.
|
||||
* This doesn't promote good type checking. Instead, use either actions-util.getOptionalInput or
|
||||
* actions-util.getRequiredInput depending on if the input always has a value or not. The input
|
||||
* will always have a value if it is required or has a default value.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @id javascript/codeql-action/required-action-input
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* A declaration of a github action.
|
||||
*/
|
||||
class ActionDeclaration extends File {
|
||||
ActionDeclaration() {
|
||||
getRelativePath().matches("%/action.yml")
|
||||
}
|
||||
|
||||
YamlDocument getRootNode() {
|
||||
result.getFile() = this
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of any input to this action.
|
||||
*/
|
||||
string getAnInput() {
|
||||
result = getRootNode().(YamlMapping).lookup("inputs").(YamlMapping).getKey(_).(YamlString).getValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* The given input always has a value, either because it is required,
|
||||
* or because it has a default value.
|
||||
*/
|
||||
predicate inputAlwaysHasValue(string input) {
|
||||
exists(YamlMapping value |
|
||||
value = getRootNode().(YamlMapping).lookup("inputs").(YamlMapping).lookup(input) and
|
||||
(exists(value.lookup("default")) or
|
||||
value.lookup("required").(YamlBool).getBoolValue() = true))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An import from "@actions/core"
|
||||
*/
|
||||
class ActionsLibImport extends ImportDeclaration {
|
||||
ActionsLibImport() {
|
||||
getImportedPath().getValue() = "@actions/core"
|
||||
}
|
||||
|
||||
Variable getAProvidedVariable() {
|
||||
result = getASpecifier().getLocal().getVariable()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the core.getInput method.
|
||||
*/
|
||||
class CoreGetInputMethodCallExpr extends MethodCallExpr {
|
||||
CoreGetInputMethodCallExpr() {
|
||||
getMethodName() = "getInput" and
|
||||
exists(ActionsLibImport libImport |
|
||||
this.getReceiver() = libImport.getAProvidedVariable().getAnAccess() or
|
||||
this.getReceiver().(PropAccess).getBase() = libImport.getAProvidedVariable().getAnAccess())
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the input being accessed.
|
||||
*/
|
||||
string getInputName() {
|
||||
result = getArgument(0).(StringLiteral).getValue()
|
||||
}
|
||||
}
|
||||
|
||||
from ActionDeclaration action, CoreGetInputMethodCallExpr getInputCall, string inputName, string alternateFunction
|
||||
where action.getAnInput() = inputName
|
||||
// We don't want to create an alert for the users core.getInput in the getRequiredInput
|
||||
// and getOptionalInput functions themselves, and this check here does that in a
|
||||
// roundabout way by checking the parameter is a string literal. This should be enough
|
||||
// and hopefully won't discount any real calls to core.getInput, but is worth noting here.
|
||||
and getInputCall.getInputName() = inputName
|
||||
and ((action.inputAlwaysHasValue(inputName) and alternateFunction = "getRequiredInput")
|
||||
or (not action.inputAlwaysHasValue(inputName) and alternateFunction = "geOptionalInput"))
|
||||
select getInputCall, "This input may be undefined. Please use actions-util.$@ instead.", alternateFunction, alternateFunction
|
||||
103
github/codeql-action-v2/queries/undeclared-action-input.ql
Normal file
103
github/codeql-action-v2/queries/undeclared-action-input.ql
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* @name Undeclared action input
|
||||
* @description Code tries to use an input parameter that is not defined for this action.
|
||||
Perhaps this code is shared by multiple actions.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @id javascript/codeql-action/undeclared-action-input
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* A declaration of a github action, including its inputs and entrypoint.
|
||||
*/
|
||||
class ActionDeclaration extends File {
|
||||
ActionDeclaration() {
|
||||
getRelativePath().matches("%/action.yml")
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the action.
|
||||
*/
|
||||
string getName() {
|
||||
result = getRelativePath().regexpCapture("(.*)/action.yml", 1)
|
||||
}
|
||||
|
||||
YamlDocument getRootNode() {
|
||||
result.getFile() = this
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of any input to this action.
|
||||
*/
|
||||
string getAnInput() {
|
||||
result = getRootNode().(YamlMapping).lookup("inputs").(YamlMapping).getKey(_).(YamlString).getValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* The function that is the entrypoint to this action.
|
||||
*/
|
||||
FunctionDeclStmt getEntrypoint() {
|
||||
result.getFile().getRelativePath() = getRootNode().
|
||||
(YamlMapping).lookup("runs").
|
||||
(YamlMapping).lookup("main").
|
||||
(YamlString).getValue().regexpReplaceAll("\\.\\./lib/(.*)\\.js", "src/$1.ts") and
|
||||
result.getName() = "run"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function declared on CodeQL interface from codeql.ts
|
||||
*/
|
||||
class CodeQLFunction extends Function {
|
||||
CodeQLFunction() {
|
||||
exists(Function getCodeQLForCmd, ObjectExpr obj |
|
||||
getCodeQLForCmd.getName() = "getCodeQLForCmd" and
|
||||
obj = getCodeQLForCmd.getAStmt().(ReturnStmt).getExpr() and
|
||||
obj.getAProperty().getInit() = this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Any expr that is a transitive child of the given function.
|
||||
*/
|
||||
Expr getAFunctionChildExpr(Function f) {
|
||||
result.getContainer() = f
|
||||
}
|
||||
|
||||
/*
|
||||
* Result is a function that is called from the body of the given function `f`
|
||||
*/
|
||||
Function calledBy(Function f) {
|
||||
result = getAFunctionChildExpr(f).(InvokeExpr).getResolvedCallee()
|
||||
or
|
||||
// Assume outer function causes inner function to be called,
|
||||
// except for the special case of the CodeQL functions.
|
||||
(result.getEnclosingContainer() = f and not result instanceof CodeQLFunction)
|
||||
or
|
||||
// Handle calls to CodeQL functions by name
|
||||
getAFunctionChildExpr(f).(InvokeExpr).getCalleeName() = result.(CodeQLFunction).getName()
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the core.getInput method.
|
||||
*/
|
||||
class GetInputMethodCallExpr extends MethodCallExpr {
|
||||
GetInputMethodCallExpr() {
|
||||
getMethodName() = "getInput"
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the input being accessed.
|
||||
*/
|
||||
string getInputName() {
|
||||
result = getArgument(0).(StringLiteral).getValue()
|
||||
}
|
||||
}
|
||||
|
||||
from ActionDeclaration action, GetInputMethodCallExpr getInputCall, string inputName
|
||||
where getAFunctionChildExpr(calledBy*(action.getEntrypoint())) = getInputCall and
|
||||
inputName = getInputCall.getInputName() and
|
||||
not inputName = action.getAnInput()
|
||||
select getInputCall, "The $@ input is not defined for the $@ action", inputName, inputName, action, action.getName()
|
||||
Loading…
Add table
Add a link
Reference in a new issue