Actions: Fix some Ql4Ql violations.

This commit is contained in:
Michael Nebel
2025-09-01 14:45:00 +02:00
parent b4d6cb6e5f
commit 64f9758c29
13 changed files with 55 additions and 78 deletions

View File

@@ -70,8 +70,8 @@ class Location extends TLocation, TBaseLocation {
/** /**
* Holds if this element is at the specified location. * Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to * The location spans column `sc` of line `sl` to
* column `endcolumn` of line `endline` in file `filepath`. * column `ec` of line `el` in file `p`.
* For more information, see * For more information, see
* [Providing locations in CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). * [Providing locations in CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/ */

View File

@@ -261,7 +261,7 @@ class If extends AstNode instanceof IfImpl {
} }
/** /**
* An Environemnt node representing a deployment environment. * An Environment node representing a deployment environment.
*/ */
class Environment extends AstNode instanceof EnvironmentImpl { class Environment extends AstNode instanceof EnvironmentImpl {
string getName() { result = super.getName() } string getName() { result = super.getName() }

View File

@@ -125,12 +125,11 @@ abstract class AstNodeImpl extends TAstNode {
* Gets the enclosing Step. * Gets the enclosing Step.
*/ */
StepImpl getEnclosingStep() { StepImpl getEnclosingStep() {
if this instanceof StepImpl this instanceof StepImpl and
then result = this result = this
else or
if this instanceof ScalarValueImpl this instanceof ScalarValueImpl and
then result.getAChildNode*() = this.getParentNode() result.getAChildNode*() = this.getParentNode()
else none()
} }
/** /**
@@ -1416,9 +1415,8 @@ class ExternalJobImpl extends JobImpl, UsesImpl {
override string getVersion() { override string getVersion() {
exists(YamlString name | exists(YamlString name |
n.lookup("uses") = name and n.lookup("uses") = name and
if not name.getValue().matches("\\.%") not name.getValue().matches("\\.%") and
then result = name.getValue().regexpCapture(repoUsesParser(), 4) result = name.getValue().regexpCapture(repoUsesParser(), 4)
else none()
) )
} }
} }

View File

@@ -286,7 +286,7 @@ private module Cached {
/** /**
* Holds if `cfn` is the `i`th node in basic block `bb`. * Holds if `cfn` is the `i`th node in basic block `bb`.
* *
* In other words, `i` is the shortest distance from a node `bb` * In other words, `i` is the shortest distance from a node `bbStart`
* that starts a basic block to `cfn` along the `intraBBSucc` relation. * that starts a basic block to `cfn` along the `intraBBSucc` relation.
*/ */
cached cached

View File

@@ -63,10 +63,10 @@ predicate madSource(DataFlow::Node source, string kind, string fieldName) {
( (
if fieldName.trim().matches("env.%") if fieldName.trim().matches("env.%")
then source.asExpr() = uses.getInScopeEnvVarExpr(fieldName.trim().replaceAll("env.", "")) then source.asExpr() = uses.getInScopeEnvVarExpr(fieldName.trim().replaceAll("env.", ""))
else else (
if fieldName.trim().matches("output.%") fieldName.trim().matches("output.%") and
then source.asExpr() = uses source.asExpr() = uses
else none() )
) )
) )
} }

View File

@@ -31,14 +31,14 @@ abstract class RemoteFlowSource extends SourceNode {
class GitHubCtxSource extends RemoteFlowSource { class GitHubCtxSource extends RemoteFlowSource {
string flag; string flag;
string event; string event;
GitHubExpression e;
GitHubCtxSource() { GitHubCtxSource() {
this.asExpr() = e and exists(GitHubExpression e |
// github.head_ref this.asExpr() = e and
e.getFieldName() = "head_ref" and // github.head_ref
flag = "branch" and e.getFieldName() = "head_ref" and
( flag = "branch"
|
event = e.getATriggerEvent().getName() and event = e.getATriggerEvent().getName() and
event = "pull_request_target" event = "pull_request_target"
or or
@@ -148,7 +148,6 @@ class GhCLICommandSource extends RemoteFlowSource, CommandSource {
class GitHubEventPathSource extends RemoteFlowSource, CommandSource { class GitHubEventPathSource extends RemoteFlowSource, CommandSource {
string cmd; string cmd;
string flag; string flag;
string access_path;
Run run; Run run;
// Examples // Examples
@@ -163,7 +162,7 @@ class GitHubEventPathSource extends RemoteFlowSource, CommandSource {
run.getScript().getACommand() = cmd and run.getScript().getACommand() = cmd and
cmd.matches("jq%") and cmd.matches("jq%") and
cmd.matches("%GITHUB_EVENT_PATH%") and cmd.matches("%GITHUB_EVENT_PATH%") and
exists(string regexp | exists(string regexp, string access_path |
untrustedEventPropertiesDataModel(regexp, flag) and untrustedEventPropertiesDataModel(regexp, flag) and
not flag = "json" and not flag = "json" and
access_path = "github.event" + cmd.regexpCapture(".*\\s+([^\\s]+)\\s+.*", 1) and access_path = "github.event" + cmd.regexpCapture(".*\\s+([^\\s]+)\\s+.*", 1) and

View File

@@ -19,7 +19,6 @@ abstract class ArgumentInjectionSink extends DataFlow::Node {
*/ */
class ArgumentInjectionFromEnvVarSink extends ArgumentInjectionSink { class ArgumentInjectionFromEnvVarSink extends ArgumentInjectionSink {
string command; string command;
string argument;
ArgumentInjectionFromEnvVarSink() { ArgumentInjectionFromEnvVarSink() {
exists(Run run, string var | exists(Run run, string var |
@@ -28,7 +27,7 @@ class ArgumentInjectionFromEnvVarSink extends ArgumentInjectionSink {
exists(run.getInScopeEnvVarExpr(var)) or exists(run.getInScopeEnvVarExpr(var)) or
var = "GITHUB_HEAD_REF" var = "GITHUB_HEAD_REF"
) and ) and
run.getScript().getAnEnvReachingArgumentInjectionSink(var, command, argument) run.getScript().getAnEnvReachingArgumentInjectionSink(var, command, _)
) )
} }
@@ -44,13 +43,12 @@ class ArgumentInjectionFromEnvVarSink extends ArgumentInjectionSink {
*/ */
class ArgumentInjectionFromCommandSink extends ArgumentInjectionSink { class ArgumentInjectionFromCommandSink extends ArgumentInjectionSink {
string command; string command;
string argument;
ArgumentInjectionFromCommandSink() { ArgumentInjectionFromCommandSink() {
exists(CommandSource source, Run run | exists(CommandSource source, Run run |
run = source.getEnclosingRun() and run = source.getEnclosingRun() and
this.asExpr() = run.getScript() and this.asExpr() = run.getScript() and
run.getScript().getACmdReachingArgumentInjectionSink(source.getCommand(), command, argument) run.getScript().getACmdReachingArgumentInjectionSink(source.getCommand(), command, _)
) )
} }

View File

@@ -125,8 +125,6 @@ class LegitLabsDownloadArtifactActionStep extends UntrustedArtifactDownloadStep,
} }
class ActionsGitHubScriptDownloadStep extends UntrustedArtifactDownloadStep, UsesStep { class ActionsGitHubScriptDownloadStep extends UntrustedArtifactDownloadStep, UsesStep {
string script;
ActionsGitHubScriptDownloadStep() { ActionsGitHubScriptDownloadStep() {
// eg: // eg:
// - uses: actions/github-script@v6 // - uses: actions/github-script@v6
@@ -149,12 +147,14 @@ class ActionsGitHubScriptDownloadStep extends UntrustedArtifactDownloadStep, Use
// var fs = require('fs'); // var fs = require('fs');
// fs.writeFileSync('${{github.workspace}}/test-results.zip', Buffer.from(download.data)); // fs.writeFileSync('${{github.workspace}}/test-results.zip', Buffer.from(download.data));
this.getCallee() = "actions/github-script" and this.getCallee() = "actions/github-script" and
this.getArgument("script") = script and exists(string script |
script.matches("%listWorkflowRunArtifacts(%") and this.getArgument("script") = script and
script.matches("%downloadArtifact(%") and script.matches("%listWorkflowRunArtifacts(%") and
script.matches("%writeFileSync(%") and script.matches("%downloadArtifact(%") and
// Filter out artifacts that were created by pull-request. script.matches("%writeFileSync(%") and
not script.matches("%exclude_pull_requests: true%") // Filter out artifacts that were created by pull-request.
not script.matches("%exclude_pull_requests: true%")
)
} }
override string getPath() { override string getPath() {
@@ -171,10 +171,10 @@ class ActionsGitHubScriptDownloadStep extends UntrustedArtifactDownloadStep, Use
.getScript() .getScript()
.getACommand() .getACommand()
.regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 3))) .regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 3)))
else else (
if this.getAFollowingStep().(Run).getScript().getACommand().regexpMatch(unzipRegexp()) this.getAFollowingStep().(Run).getScript().getACommand().regexpMatch(unzipRegexp()) and
then result = "GITHUB_WORKSPACE/" result = "GITHUB_WORKSPACE/"
else none() )
} }
} }
@@ -207,12 +207,13 @@ class GHRunArtifactDownloadStep extends UntrustedArtifactDownloadStep, Run {
.getScript() .getScript()
.getACommand() .getACommand()
.regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 3))) .regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 3)))
else else (
if (
this.getAFollowingStep().(Run).getScript().getACommand().regexpMatch(unzipRegexp()) or this.getAFollowingStep().(Run).getScript().getACommand().regexpMatch(unzipRegexp()) or
this.getScript().getACommand().regexpMatch(unzipRegexp()) this.getScript().getACommand().regexpMatch(unzipRegexp())
then result = "GITHUB_WORKSPACE/" ) and
else none() result = "GITHUB_WORKSPACE/"
)
} }
} }
@@ -259,15 +260,15 @@ class DirectArtifactDownloadStep extends UntrustedArtifactDownloadStep, Run {
class ArtifactPoisoningSink extends DataFlow::Node { class ArtifactPoisoningSink extends DataFlow::Node {
UntrustedArtifactDownloadStep download; UntrustedArtifactDownloadStep download;
PoisonableStep poisonable;
ArtifactPoisoningSink() { ArtifactPoisoningSink() {
download.getAFollowingStep() = poisonable and exists(PoisonableStep poisonable |
// excluding artifacts downloaded to the temporary directory download.getAFollowingStep() = poisonable and
not download.getPath().regexpMatch("^/tmp.*") and // excluding artifacts downloaded to the temporary directory
not download.getPath().regexpMatch("^\\$\\{\\{\\s*runner\\.temp\\s*}}.*") and not download.getPath().regexpMatch("^/tmp.*") and
not download.getPath().regexpMatch("^\\$RUNNER_TEMP.*") and not download.getPath().regexpMatch("^\\$\\{\\{\\s*runner\\.temp\\s*}}.*") and
( not download.getPath().regexpMatch("^\\$RUNNER_TEMP.*")
|
poisonable.(Run).getScript() = this.asExpr() and poisonable.(Run).getScript() = this.asExpr() and
( (
// Check if the poisonable step is a local script execution step // Check if the poisonable step is a local script execution step

View File

@@ -159,11 +159,8 @@ abstract class CommentVsHeadDateCheck extends ControlCheck {
/* Specific implementations of control checks */ /* Specific implementations of control checks */
class LabelIfCheck extends LabelCheck instanceof If { class LabelIfCheck extends LabelCheck instanceof If {
string condition;
LabelIfCheck() { LabelIfCheck() {
condition = normalizeExpr(this.getCondition()) and exists(string condition | condition = normalizeExpr(this.getCondition()) |
(
// eg: contains(github.event.pull_request.labels.*.name, 'safe to test') // eg: contains(github.event.pull_request.labels.*.name, 'safe to test')
condition.regexpMatch(".*(^|[^!])contains\\(\\s*github\\.event\\.pull_request\\.labels\\b.*") condition.regexpMatch(".*(^|[^!])contains\\(\\s*github\\.event\\.pull_request\\.labels\\b.*")
or or

View File

@@ -55,12 +55,8 @@ class EnvVarInjectionFromFileReadSink extends EnvVarInjectionSink {
* echo "COMMIT_MESSAGE=${COMMIT_MESSAGE}" >> $GITHUB_ENV * echo "COMMIT_MESSAGE=${COMMIT_MESSAGE}" >> $GITHUB_ENV
*/ */
class EnvVarInjectionFromCommandSink extends EnvVarInjectionSink { class EnvVarInjectionFromCommandSink extends EnvVarInjectionSink {
CommandSource inCommand;
string injectedVar;
string command;
EnvVarInjectionFromCommandSink() { EnvVarInjectionFromCommandSink() {
exists(Run run | exists(Run run, CommandSource inCommand, string injectedVar, string command |
this.asExpr() = inCommand.getEnclosingRun().getScript() and this.asExpr() = inCommand.getEnclosingRun().getScript() and
run = inCommand.getEnclosingRun() and run = inCommand.getEnclosingRun() and
run.getScript().getACmdReachingGitHubEnvWrite(inCommand.getCommand(), injectedVar) and run.getScript().getACmdReachingGitHubEnvWrite(inCommand.getCommand(), injectedVar) and
@@ -86,12 +82,8 @@ class EnvVarInjectionFromCommandSink extends EnvVarInjectionSink {
* echo "FOO=$BODY" >> $GITHUB_ENV * echo "FOO=$BODY" >> $GITHUB_ENV
*/ */
class EnvVarInjectionFromEnvVarSink extends EnvVarInjectionSink { class EnvVarInjectionFromEnvVarSink extends EnvVarInjectionSink {
string inVar;
string injectedVar;
string command;
EnvVarInjectionFromEnvVarSink() { EnvVarInjectionFromEnvVarSink() {
exists(Run run | exists(Run run, string inVar, string injectedVar, string command |
run.getScript() = this.asExpr() and run.getScript() = this.asExpr() and
exists(run.getInScopeEnvVarExpr(inVar)) and exists(run.getInScopeEnvVarExpr(inVar)) and
run.getScript().getAnEnvReachingGitHubEnvWrite(inVar, injectedVar) and run.getScript().getAnEnvReachingGitHubEnvWrite(inVar, injectedVar) and

View File

@@ -99,18 +99,14 @@ class OutputClobberingFromEnvVarSink extends OutputClobberingSink {
* echo $BODY * echo $BODY
*/ */
class WorkflowCommandClobberingFromEnvVarSink extends OutputClobberingSink { class WorkflowCommandClobberingFromEnvVarSink extends OutputClobberingSink {
string clobbering_var;
string clobbered_value;
WorkflowCommandClobberingFromEnvVarSink() { WorkflowCommandClobberingFromEnvVarSink() {
exists(Run run, string workflow_cmd_stmt, string clobbering_stmt | exists(Run run, string workflow_cmd_stmt, string clobbering_stmt, string clobbering_var |
run.getScript() = this.asExpr() and run.getScript() = this.asExpr() and
run.getScript().getAStmt() = clobbering_stmt and run.getScript().getAStmt() = clobbering_stmt and
clobbering_stmt.regexpMatch("echo\\s+(-e\\s+)?(\"|')?\\$(\\{)?" + clobbering_var + ".*") and clobbering_stmt.regexpMatch("echo\\s+(-e\\s+)?(\"|')?\\$(\\{)?" + clobbering_var + ".*") and
exists(run.getInScopeEnvVarExpr(clobbering_var)) and exists(run.getInScopeEnvVarExpr(clobbering_var)) and
run.getScript().getAStmt() = workflow_cmd_stmt and run.getScript().getAStmt() = workflow_cmd_stmt and
clobbered_value = exists(trimQuotes(workflow_cmd_stmt.regexpCapture(".*::set-output\\s+name=.*::(.*)", 1)))
trimQuotes(workflow_cmd_stmt.regexpCapture(".*::set-output\\s+name=.*::(.*)", 1))
) )
} }
} }

View File

@@ -1,10 +1,8 @@
import actions import actions
class UnversionedImmutableAction extends UsesStep { class UnversionedImmutableAction extends UsesStep {
string immutable_action;
UnversionedImmutableAction() { UnversionedImmutableAction() {
isImmutableAction(this, immutable_action) and isImmutableAction(this, _) and
not isSemVer(this.getVersion()) not isSemVer(this.getVersion())
} }
} }

View File

@@ -37,8 +37,6 @@ where
) )
or or
// upload artifact is not used in the same workflow // upload artifact is not used in the same workflow
not exists(UsesStep upload | not download.getEnclosingWorkflow().getAJob().(LocalJob).getAStep() instanceof UsesStep
download.getEnclosingWorkflow().getAJob().(LocalJob).getAStep() = upload
)
) )
select download, "Potential artifact poisoning" select download, "Potential artifact poisoning"