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