mirror of
https://github.com/github/codeql-action.git
synced 2025-12-25 16:50:21 +08:00
229 lines
6.5 KiB
Plaintext
229 lines
6.5 KiB
Plaintext
/**
|
|
* @name Unguarded actions library use
|
|
* @description Code that runs outside of GitHub Actions tries to use a library that should only be used when running on actions.
|
|
* @kind problem
|
|
* @problem.severity error
|
|
* @id javascript/codeql-action/unguarded-action-lib
|
|
*/
|
|
|
|
import javascript
|
|
|
|
/**
|
|
* Although these libraries are designed for use on actions they
|
|
* have been deemed safe to use outside of actions as well.
|
|
*/
|
|
bindingset[lib]
|
|
predicate isSafeActionLib(string lib) {
|
|
lib = "@actions/http-client" or
|
|
lib = "@actions/exec" or
|
|
lib = "@actions/io" or
|
|
lib.matches("@actions/exec/%")
|
|
}
|
|
|
|
/**
|
|
* Matches libraries that are not always safe to use outside of actions
|
|
* but can be made so by setting certain environment variables.
|
|
*/
|
|
predicate isSafeActionLibWithActionsEnvVars(string lib) {
|
|
lib = "@actions/tool-cache"
|
|
}
|
|
|
|
/**
|
|
* Matches the names of runner commands that set action env vars
|
|
*/
|
|
predicate commandSetsActionsEnvVars(string commandName) {
|
|
commandName = "init" or commandName = "autobuild" or commandName = "analyze"
|
|
}
|
|
|
|
/**
|
|
* An import from a library that is meant for GitHub Actions and
|
|
* we do not want to be using outside of actions.
|
|
*/
|
|
class ActionsLibImport extends ImportDeclaration {
|
|
ActionsLibImport() {
|
|
getImportedPath().getValue().matches("@actions/%") and
|
|
not isSafeActionLib(getImportedPath().getValue()) or
|
|
getImportedPath().getValue().matches("%/actions-util$")
|
|
}
|
|
|
|
string getName() {
|
|
result = getImportedPath().getValue()
|
|
}
|
|
|
|
Variable getAProvidedVariable() {
|
|
result = getASpecifier().getLocal().getVariable()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An entrypoint to the CodeQL runner.
|
|
*/
|
|
class RunnerEntrypoint extends Function {
|
|
RunnerEntrypoint() {
|
|
getFile().getAbsolutePath().matches("%/runner.ts")
|
|
}
|
|
|
|
/**
|
|
* Does this runner entry point set the RUNNER_TEMP and
|
|
* RUNNER_TOOL_CACHE env vars which make some actions libraries
|
|
* safe to use outside of actions.
|
|
* See "setupActionsVars" in "util.ts".
|
|
*/
|
|
predicate setsActionsEnvVars() {
|
|
// This is matching code of the following format, where "this"
|
|
// is the function being passed to the "action" method.
|
|
//
|
|
// program
|
|
// .command("init")
|
|
// ...
|
|
// .action(async (cmd: InitArgs) => {
|
|
// ...
|
|
// })
|
|
exists(MethodCallExpr actionCall,
|
|
MethodCallExpr commandCall |
|
|
commandCall.getMethodName() = "command" and
|
|
commandCall.getReceiver().(VarAccess).getVariable().getName() = "program" and
|
|
commandSetsActionsEnvVars(commandCall.getArgument(0).(StringLiteral).getValue()) and
|
|
actionCall.getMethodName() = "action" and
|
|
actionCall.getReceiver().getAChildExpr*() = commandCall and
|
|
actionCall.getArgument(0).getAChildExpr*() = this)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A generic check to see if we are in actions or runner mode in a particular block of code.
|
|
*/
|
|
abstract class ActionsGuard extends IfStmt {
|
|
|
|
/**
|
|
* Get a statement block that is only executed on actions
|
|
*/
|
|
abstract Stmt getActionsBlock();
|
|
|
|
/**
|
|
* Gets an expr that is only executed on actions
|
|
*/
|
|
final Expr getAnActionsExpr() { getActionsBlock().getAChildStmt*().getAChildExpr*() = result }
|
|
|
|
}
|
|
|
|
/**
|
|
* A check of whether we are in actions mode or runner mode, based on
|
|
* the presense of a call to `isActions()` in the condition of an if statement.
|
|
*/
|
|
class IsActionsGuard extends ActionsGuard {
|
|
IsActionsGuard() {
|
|
getCondition().(CallExpr).getCalleeName() = "isActions"
|
|
}
|
|
|
|
/**
|
|
* Get the "then" block that is the "actions" path.
|
|
*/
|
|
override Stmt getActionsBlock() {
|
|
result = getThen()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A check of whether we are in actions mode or runner mode, based on
|
|
* the presense of a call to `!isActions()` in the condition of an if statement.
|
|
*/
|
|
class NegatedIsActionsGuard extends ActionsGuard {
|
|
NegatedIsActionsGuard() {
|
|
getCondition().(LogNotExpr).getOperand().(CallExpr).getCalleeName() = "isActions"
|
|
}
|
|
|
|
/**
|
|
* Get the "else" block that is the "actions" path.
|
|
*/
|
|
override Stmt getActionsBlock() {
|
|
result = getElse()
|
|
}
|
|
}
|
|
|
|
class ModeAccess extends PropAccess {
|
|
ModeAccess() {
|
|
(
|
|
// eg- Mode.actions
|
|
getBase().(Identifier).getName() = "Mode" or
|
|
// eg- actionUtil.Mode.actions
|
|
getBase().(PropAccess).getPropertyName() = "Mode"
|
|
) and
|
|
(getPropertyName() = "actions" or getPropertyName() = "runner")
|
|
}
|
|
|
|
predicate isActions() {
|
|
getPropertyName() = "actions"
|
|
}
|
|
|
|
predicate isRunner() {
|
|
getPropertyName() = "runner"
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A check of whether we are in actions mode or runner mode.
|
|
*/
|
|
class ModeGuard extends ActionsGuard {
|
|
ModeGuard() {
|
|
getCondition().(EqualityTest).getAnOperand().(ModeAccess).isActions() or
|
|
getCondition().(EqualityTest).getAnOperand().(ModeAccess).isRunner()
|
|
}
|
|
|
|
ModeAccess getOperand() {
|
|
result = getCondition().(EqualityTest).getAnOperand()
|
|
}
|
|
|
|
predicate isPositive() {
|
|
getCondition().(EqualityTest).getPolarity() = true
|
|
}
|
|
|
|
/**
|
|
* Get the then or else block that is the "actions" path.
|
|
*/
|
|
override Stmt getActionsBlock() {
|
|
(getOperand().isActions() and isPositive() and result = getThen())
|
|
or
|
|
(getOperand().isRunner() and not isPositive() and result = getThen())
|
|
or
|
|
(getOperand().isActions() and not isPositive() and result = getElse())
|
|
or
|
|
(getOperand().isRunner() and isPositive() and result = getElse())
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Any expr that is a transitive child of the given function
|
|
* and is not only called on actions.
|
|
*/
|
|
Expr getAFunctionChildExpr(Function f) {
|
|
not exists(ActionsGuard guard | guard.getAnActionsExpr() = result) and
|
|
result.getContainer() = f
|
|
}
|
|
|
|
/*
|
|
* Result is a function that is called from the body of the given function `f`
|
|
* and is not only called on actions.
|
|
*/
|
|
Function calledBy(Function f) {
|
|
exists(InvokeExpr invokeExpr |
|
|
invokeExpr = getAFunctionChildExpr(f) and
|
|
invokeExpr.getResolvedCallee() = result and
|
|
not exists(ActionsGuard guard | guard.getAnActionsExpr() = invokeExpr)
|
|
)
|
|
or
|
|
// Assume outer function causes inner function to be called
|
|
(result instanceof Expr and
|
|
result.getEnclosingContainer() = f and
|
|
not exists(ActionsGuard guard | guard.getAnActionsExpr() = result))
|
|
}
|
|
|
|
from VarAccess v, ActionsLibImport actionsLib, RunnerEntrypoint runnerEntry
|
|
where actionsLib.getAProvidedVariable() = v.getVariable()
|
|
and getAFunctionChildExpr(calledBy*(runnerEntry)) = v
|
|
and not (isSafeActionLibWithActionsEnvVars(actionsLib.getName()) and runnerEntry.setsActionsEnvVars())
|
|
select v, "$@ is imported from $@ and this code can be called from $@",
|
|
v, v.getName(),
|
|
actionsLib, actionsLib.getName(),
|
|
runnerEntry, "the runner"
|