mirror of
https://github.com/github/codeql.git
synced 2025-12-17 17:23:36 +01:00
377 lines
12 KiB
Plaintext
377 lines
12 KiB
Plaintext
/**
|
|
* PENDING DEPRECATION. Models for GitHub Actions workflow files are part of the actions qlpack now.
|
|
*
|
|
* Libraries for modeling GitHub Actions workflow files written in YAML.
|
|
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
|
|
*/
|
|
|
|
import javascript
|
|
|
|
/**
|
|
* Libraries for modeling GitHub Actions workflow files written in YAML.
|
|
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
|
|
*/
|
|
module Actions {
|
|
/** A YAML node in a GitHub Actions workflow or a custom composite action file. */
|
|
private class Node extends YamlNode {
|
|
Node() {
|
|
exists(File f |
|
|
f = this.getLocation().getFile() and
|
|
(
|
|
f.getRelativePath().regexpMatch("(^|.*/)\\.github/workflows/.*\\.ya?ml$")
|
|
or
|
|
f.getBaseName() = ["action.yml", "action.yaml"]
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A custom composite action. This is a mapping at the top level of an Actions YAML action file.
|
|
* See https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions.
|
|
*/
|
|
class CompositeAction extends Node, YamlDocument, YamlMapping {
|
|
CompositeAction() {
|
|
this.getFile().getBaseName() = ["action.yml", "action.yaml"] and
|
|
this.lookup("runs").(YamlMapping).lookup("using").(YamlScalar).getValue() = "composite"
|
|
}
|
|
|
|
/** Gets the `runs` mapping. */
|
|
Runs getRuns() { result = this.lookup("runs") }
|
|
}
|
|
|
|
/**
|
|
* An `runs` mapping in a custom composite action YAML.
|
|
* See https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs
|
|
*/
|
|
class Runs extends StepsContainer {
|
|
CompositeAction action;
|
|
|
|
Runs() { action.lookup("runs") = this }
|
|
|
|
/** Gets the action that this `runs` mapping is in. */
|
|
CompositeAction getAction() { result = action }
|
|
|
|
/** Gets the `using` mapping. */
|
|
Using getUsing() { result = this.lookup("using") }
|
|
}
|
|
|
|
/**
|
|
* The parent class of the class that can contain `steps` mappings. (`Job` or `Runs` currently.)
|
|
*/
|
|
abstract class StepsContainer extends YamlNode, YamlMapping {
|
|
/** Gets the sequence of `steps` within this YAML node. */
|
|
YamlSequence getSteps() { result = this.lookup("steps") }
|
|
}
|
|
|
|
/**
|
|
* A `using` mapping in a custom composite action YAML.
|
|
*/
|
|
class Using extends YamlNode, YamlScalar {
|
|
Runs runs;
|
|
|
|
Using() { runs.lookup("using") = this }
|
|
|
|
/** Gets the `runs` mapping that this `using` mapping is in. */
|
|
Runs getRuns() { result = runs }
|
|
}
|
|
|
|
/**
|
|
* An Actions workflow. This is a mapping at the top level of an Actions YAML workflow file.
|
|
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
|
|
*/
|
|
class Workflow extends Node, YamlDocument, YamlMapping {
|
|
/** Gets the `jobs` mapping from job IDs to job definitions in this workflow. */
|
|
YamlMapping getJobs() { result = this.lookup("jobs") }
|
|
|
|
/** Gets the 'global' `env` mapping in this workflow. */
|
|
WorkflowEnv getEnv() { result = this.lookup("env") }
|
|
|
|
/** Gets the name of the workflow. */
|
|
string getName() { result = this.lookup("name").(YamlString).getValue() }
|
|
|
|
/** Gets the name of the workflow file. */
|
|
string getFileName() { result = this.getFile().getBaseName() }
|
|
|
|
/** Gets the `on:` in this workflow. */
|
|
On getOn() { result = this.lookup("on") }
|
|
|
|
/** Gets the job within this workflow with the given job ID. */
|
|
Job getJob(string jobId) { result.getWorkflow() = this and result.getId() = jobId }
|
|
}
|
|
|
|
/**
|
|
* An Actions On trigger within a workflow.
|
|
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#on.
|
|
*/
|
|
class On extends YamlNode, YamlMappingLikeNode {
|
|
Workflow workflow;
|
|
|
|
On() { workflow.lookup("on") = this }
|
|
|
|
/** Gets the workflow that this trigger is in. */
|
|
Workflow getWorkflow() { result = workflow }
|
|
}
|
|
|
|
/** A common class for `env` in workflow, job or step. */
|
|
abstract class Env extends YamlNode, YamlMapping { }
|
|
|
|
/** A workflow level `env` mapping. */
|
|
class WorkflowEnv extends Env {
|
|
Workflow workflow;
|
|
|
|
WorkflowEnv() { workflow.lookup("env") = this }
|
|
|
|
/** Gets the workflow this field belongs to. */
|
|
Workflow getWorkflow() { result = workflow }
|
|
}
|
|
|
|
/** A job level `env` mapping. */
|
|
class JobEnv extends Env {
|
|
Job job;
|
|
|
|
JobEnv() { job.lookup("env") = this }
|
|
|
|
/** Gets the job this field belongs to. */
|
|
Job getJob() { result = job }
|
|
}
|
|
|
|
/** A step level `env` mapping. */
|
|
class StepEnv extends Env {
|
|
Step step;
|
|
|
|
StepEnv() { step.lookup("env") = this }
|
|
|
|
/** Gets the step this field belongs to. */
|
|
Step getStep() { result = step }
|
|
}
|
|
|
|
/**
|
|
* An Actions job within a workflow.
|
|
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobs.
|
|
*/
|
|
class Job extends StepsContainer {
|
|
string jobId;
|
|
Workflow workflow;
|
|
|
|
Job() { this = workflow.getJobs().lookup(jobId) }
|
|
|
|
/**
|
|
* Gets the ID of this job, as a string.
|
|
* This is the job's key within the `jobs` mapping.
|
|
*/
|
|
string getId() { result = jobId }
|
|
|
|
/**
|
|
* Gets the ID of this job, as a YAML scalar node.
|
|
* This is the job's key within the `jobs` mapping.
|
|
*/
|
|
YamlString getIdNode() { workflow.getJobs().maps(result, this) }
|
|
|
|
/** Gets the human-readable name of this job, if any, as a string. */
|
|
string getName() { result = this.getNameNode().getValue() }
|
|
|
|
/** Gets the human-readable name of this job, if any, as a YAML scalar node. */
|
|
YamlString getNameNode() { result = this.lookup("name") }
|
|
|
|
/** Gets the step at the given index within this job. */
|
|
Step getStep(int index) { result.getJob() = this and result.getIndex() = index }
|
|
|
|
/** Gets the `env` mapping in this job. */
|
|
JobEnv getEnv() { result = this.lookup("env") }
|
|
|
|
/** Gets the workflow this job belongs to. */
|
|
Workflow getWorkflow() { result = workflow }
|
|
|
|
/** Gets the value of the `if` field in this job, if any. */
|
|
JobIf getIf() { result.getJob() = this }
|
|
|
|
/** Gets the value of the `runs-on` field in this job. */
|
|
JobRunson getRunsOn() { result.getJob() = this }
|
|
}
|
|
|
|
/**
|
|
* An `if` within a job.
|
|
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif.
|
|
*/
|
|
class JobIf extends YamlNode, YamlScalar {
|
|
Job job;
|
|
|
|
JobIf() { job.lookup("if") = this }
|
|
|
|
/** Gets the step this field belongs to. */
|
|
Job getJob() { result = job }
|
|
}
|
|
|
|
/**
|
|
* A `runs-on` within a job.
|
|
* See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idruns-on.
|
|
*/
|
|
class JobRunson extends YamlNode, YamlScalar {
|
|
Job job;
|
|
|
|
JobRunson() { job.lookup("runs-on") = this }
|
|
|
|
/** Gets the step this field belongs to. */
|
|
Job getJob() { result = job }
|
|
}
|
|
|
|
/**
|
|
* A step within an Actions job.
|
|
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idsteps.
|
|
*/
|
|
class Step extends YamlNode, YamlMapping {
|
|
int index;
|
|
StepsContainer parent;
|
|
|
|
Step() { this = parent.getSteps().getElement(index) }
|
|
|
|
/** Gets the 0-based position of this step within the sequence of `steps`. */
|
|
int getIndex() { result = index }
|
|
|
|
/** Gets the `job` this step belongs to, if the step belongs to a `job` in a workflow. Has no result if the step belongs to `runs` in a custom composite action. */
|
|
Job getJob() { result = parent }
|
|
|
|
/** Gets the `runs` this step belongs to, if the step belongs to a `runs` in a custom composite action. Has no result if the step belongs to a `job` in a workflow. */
|
|
Runs getRuns() { result = parent }
|
|
|
|
/** Gets the value of the `uses` field in this step, if any. */
|
|
Uses getUses() { result.getStep() = this }
|
|
|
|
/** Gets the value of the `run` field in this step, if any. */
|
|
Run getRun() { result.getStep() = this }
|
|
|
|
/** Gets the value of the `if` field in this step, if any. */
|
|
StepIf getIf() { result.getStep() = this }
|
|
|
|
/** Gets the value of the `env` field in this step, if any. */
|
|
StepEnv getEnv() { result = this.lookup("env") }
|
|
|
|
/** Gets the ID of this step, if any. */
|
|
string getId() { result = this.lookup("id").(YamlString).getValue() }
|
|
}
|
|
|
|
/**
|
|
* An `if` within a step.
|
|
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsif.
|
|
*/
|
|
class StepIf extends YamlNode, YamlScalar {
|
|
Step step;
|
|
|
|
StepIf() { step.lookup("if") = this }
|
|
|
|
/** Gets the step this field belongs to. */
|
|
Step getStep() { result = step }
|
|
}
|
|
|
|
/**
|
|
* Gets a regular expression that parses an `owner/repo@version` reference within a `uses` field in an Actions job step.
|
|
* The capture groups are:
|
|
* 1: The owner of the repository where the Action comes from, e.g. `actions` in `actions/checkout@v2`
|
|
* 2: The name of the repository where the Action comes from, e.g. `checkout` in `actions/checkout@v2`.
|
|
* 3: The version reference used when checking out the Action, e.g. `v2` in `actions/checkout@v2`.
|
|
*/
|
|
private string usesParser() { result = "([^/]+)/([^/@]+)@(.+)" }
|
|
|
|
/**
|
|
* A `uses` field within an Actions job step, which references an action as a reusable unit of code.
|
|
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsuses.
|
|
*
|
|
* For example:
|
|
* ```
|
|
* uses: actions/checkout@v2
|
|
* ```
|
|
*
|
|
* Does not handle local repository references, e.g. `.github/actions/action-name`.
|
|
*/
|
|
class Uses extends YamlNode, YamlScalar {
|
|
Step step;
|
|
|
|
Uses() { step.lookup("uses") = this }
|
|
|
|
/** Gets the step this field belongs to. */
|
|
Step getStep() { result = step }
|
|
|
|
/** Gets the owner and name of the repository where the Action comes from, e.g. `actions/checkout` in `actions/checkout@v2`. */
|
|
string getGitHubRepository() {
|
|
result =
|
|
this.getValue().regexpCapture(usesParser(), 1) + "/" +
|
|
this.getValue().regexpCapture(usesParser(), 2)
|
|
}
|
|
|
|
/** Gets the version reference used when checking out the Action, e.g. `v2` in `actions/checkout@v2`. */
|
|
string getVersion() { result = this.getValue().regexpCapture(usesParser(), 3) }
|
|
}
|
|
|
|
/**
|
|
* A `with` field within an Actions job step, which references an action as a reusable unit of code.
|
|
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepswith.
|
|
*
|
|
* For example:
|
|
* ```
|
|
* with:
|
|
* arg1: 1
|
|
* arg2: abc
|
|
* ```
|
|
*/
|
|
class With extends YamlNode, YamlMapping {
|
|
Step step;
|
|
|
|
With() { step.lookup("with") = this }
|
|
|
|
/** Gets the step this field belongs to. */
|
|
Step getStep() { result = step }
|
|
}
|
|
|
|
/**
|
|
* A `ref:` field within an Actions `with:` specific to `actions/checkout` action.
|
|
*
|
|
* For example:
|
|
* ```
|
|
* uses: actions/checkout@v2
|
|
* with:
|
|
* ref: ${{ github.event.pull_request.head.sha }}
|
|
* ```
|
|
*/
|
|
class Ref extends YamlNode, YamlString {
|
|
With with;
|
|
|
|
Ref() { with.lookup("ref") = this }
|
|
|
|
/** Gets the `with` field this field belongs to. */
|
|
With getWith() { result = with }
|
|
}
|
|
|
|
/**
|
|
* Holds if `${{ e }}` is a GitHub Actions expression evaluated within this YAML string.
|
|
* See https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions.
|
|
* Only finds simple expressions like `${{ github.event.comment.body }}`, where the expression contains only alphanumeric characters, underscores, dots, or dashes.
|
|
* Does not identify more complicated expressions like `${{ fromJSON(env.time) }}`, or ${{ format('{{Hello {0}!}}', github.event.head_commit.author.name) }}
|
|
*/
|
|
string getASimpleReferenceExpression(YamlString node) {
|
|
// We use `regexpFind` to obtain *all* matches of `${{...}}`,
|
|
// not just the last (greedy match) or first (reluctant match).
|
|
result =
|
|
node.getValue()
|
|
.regexpFind("\\$\\{\\{\\s*[A-Za-z0-9_\\[\\]\\*\\(\\)\\.\\-]+\\s*\\}\\}", _, _)
|
|
.regexpCapture("\\$\\{\\{\\s*([A-Za-z0-9_\\[\\]\\*\\((\\)\\.\\-]+)\\s*\\}\\}", 1)
|
|
}
|
|
|
|
/** Extracts the 'name' part from env.name */
|
|
bindingset[name]
|
|
string getEnvName(string name) { result = name.regexpCapture("env\\.([A-Za-z0-9_]+)", 1) }
|
|
|
|
/**
|
|
* A `run` field within an Actions job step, which runs command-line programs using an operating system shell.
|
|
* See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun.
|
|
*/
|
|
class Run extends YamlNode, YamlString {
|
|
Step step;
|
|
|
|
Run() { step.lookup("run") = this }
|
|
|
|
/** Gets the step that executes this `run` command. */
|
|
Step getStep() { result = step }
|
|
}
|
|
}
|