Add queries
This commit is contained in:
parent
121fd331cd
commit
d88fa5cef6
2 changed files with 144 additions and 0 deletions
51
queries/inconsistent-action-input.ql
Normal file
51
queries/inconsistent-action-input.ql
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
* @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")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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))
|
||||||
|
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
|
||||||
93
queries/required-action-input.ql
Normal file
93
queries/required-action-input.ql
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
/**
|
||||||
|
* @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))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue