-
Notifications
You must be signed in to change notification settings - Fork 2k
Actions: Add new models for external actions/workflows #22263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
62e81cd
9c4c1a4
38ffa26
f3f6b42
52ee51b
ae4ce2e
558a0e0
658a065
d14f439
66160b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -372,6 +372,14 @@ | |
| } | ||
| } | ||
|
|
||
| bindingset[owner, repo, action_path] | ||
| private string externalCompositeActionName(string owner, string repo, string action_path) { | ||
| action_path.trim() = "" and result = owner.trim() + "/" + repo.trim() | ||
| or | ||
| not action_path.trim() = "" and | ||
| result = owner.trim() + "/" + repo.trim() + "/" + action_path.trim() | ||
|
JarLob marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| class CompositeActionImpl extends AstNodeImpl, TCompositeAction { | ||
| YamlMapping n; | ||
|
|
||
|
|
@@ -415,7 +423,38 @@ | |
| ) | ||
| } | ||
|
|
||
| predicate getAnExternalCompositeActionModel( | ||
Check warningCode scanning / CodeQL Predicates starting with "get" or "as" should return a value Warning
This predicate starts with 'get' but does not return a value.
|
||
| string owner, string repo, string action_path, string requested_ref, string resolved_commit_sha, | ||
| string local_path | ||
| ) { | ||
| externalCompositeActionDataModel(owner, repo, action_path, requested_ref, resolved_commit_sha, | ||
| local_path) and | ||
| local_path.trim() = this.getLocation().getFile().getRelativePath() | ||
|
Comment on lines
+435
to
+437
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Quick check: for tuples in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. local_path is not unique: the producer intentionally lets requested refs with identical content share one materialized file while retaining separate provenance rows. |
||
| } | ||
|
|
||
| predicate isExternalCompositeAction() { | ||
| exists( | ||
| string owner, string repo, string action_path, string requested_ref, | ||
Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading |
||
| string resolved_commit_sha, string local_path | ||
Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading |
||
| | | ||
| this.getAnExternalCompositeActionModel(owner, repo, action_path, requested_ref, | ||
| resolved_commit_sha, local_path) | ||
| ) | ||
| or | ||
| this.getLocation().getFile().getRelativePath().matches("9466014afba34ef28239871ceabf4132/%") | ||
| } | ||
|
|
||
| string getResolvedPath() { | ||
| exists( | ||
| string owner, string repo, string action_path, string requested_ref, | ||
| string resolved_commit_sha, string local_path | ||
Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading |
||
| | | ||
| this.getAnExternalCompositeActionModel(owner, repo, action_path, requested_ref, | ||
| resolved_commit_sha, local_path) and | ||
| result = externalCompositeActionName(owner, repo, action_path) + "@" + requested_ref.trim() | ||
| ) | ||
| or | ||
| not this.isExternalCompositeAction() and | ||
| result = | ||
| ["", "./"] + | ||
| this.getLocation() | ||
|
|
@@ -424,7 +463,6 @@ | |
| .replaceAll(getRepoRoot(), "") | ||
| .replaceAll("/action.yml", "") | ||
| .replaceAll("/action.yaml", "") | ||
| .replaceAll(".github/actions/external/", "") | ||
| } | ||
|
|
||
| private predicate hasExplicitSecretAccess() { | ||
|
|
@@ -542,14 +580,41 @@ | |
| ) | ||
| } | ||
|
|
||
| predicate getAnExternalReusableWorkflowModel( | ||
Check warningCode scanning / CodeQL Predicates starting with "get" or "as" should return a value Warning
This predicate starts with 'get' but does not return a value.
|
||
| string owner, string repo, string workflow_path, string requested_ref, | ||
| string resolved_commit_sha, string local_path | ||
| ) { | ||
| externalReusableWorkflowDataModel(owner, repo, workflow_path, requested_ref, | ||
| resolved_commit_sha, local_path) and | ||
| local_path.trim() = this.getLocation().getFile().getRelativePath() | ||
|
Comment on lines
+587
to
+589
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same question applies here as the pattern is the same.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tested this on the Grafana Actions database. All eight reusable-workflow model rows resolved to extracted workflow files, with each local_path matching the AST file’s relative path. The current plan scans externalReusableWorkflowDataModel first and uses keyed joins through file, location, and ReusableWorkflowImpl. |
||
| } | ||
|
|
||
| predicate isExternalReusableWorkflow() { | ||
| exists( | ||
| string owner, string repo, string workflow_path, string requested_ref, | ||
Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading |
||
| string resolved_commit_sha, string local_path | ||
Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading |
||
| | | ||
| this.getAnExternalReusableWorkflowModel(owner, repo, workflow_path, requested_ref, | ||
| resolved_commit_sha, local_path) | ||
| ) | ||
| or | ||
| this.getLocation().getFile().getRelativePath().matches("9466014afba34ef28239871ceabf4132/%") // root folder for external workflows and composite actions | ||
| } | ||
|
|
||
| string getResolvedPath() { | ||
| exists( | ||
| string owner, string repo, string workflow_path, string requested_ref, | ||
| string resolved_commit_sha, string local_path | ||
Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading |
||
| | | ||
| this.getAnExternalReusableWorkflowModel(owner, repo, workflow_path, requested_ref, | ||
| resolved_commit_sha, local_path) and | ||
| result = | ||
| owner.trim() + "/" + repo.trim() + "/" + workflow_path.trim() + "@" + requested_ref.trim() | ||
| ) | ||
| or | ||
| not this.isExternalReusableWorkflow() and | ||
| result = | ||
| ["", "./"] + | ||
| this.getLocation() | ||
| .getFile() | ||
| .getRelativePath() | ||
| .replaceAll(getRepoRoot(), "") | ||
| .replaceAll(".github/workflows/external/", "") | ||
| ["", "./"] + this.getLocation().getFile().getRelativePath().replaceAll(getRepoRoot(), "") | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1336,6 +1401,8 @@ | |
| abstract class UsesImpl extends AstNodeImpl { | ||
| abstract string getCallee(); | ||
|
|
||
| abstract string getCallableName(); | ||
|
|
||
| abstract ScalarValueImpl getCalleeNode(); | ||
|
|
||
| abstract string getVersion(); | ||
|
|
@@ -1374,6 +1441,77 @@ | |
| else result = u.getValue() | ||
| } | ||
|
|
||
| private predicate isWorkspaceLocalCall() { u.getValue().matches(["./%", ".github/%"]) } | ||
|
|
||
| private predicate isSelfCall() { u.getValue().matches("$/%") } | ||
|
|
||
| private predicate isLocalCall() { this.isWorkspaceLocalCall() or this.isSelfCall() } | ||
|
|
||
| private predicate hasModeledExternalCallee() { | ||
| exists( | ||
| string owner, string repo, string action_path, string requested_ref, | ||
| string resolved_commit_sha, string local_path | ||
Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading |
||
| | | ||
| externalCompositeActionDataModel(owner, repo, action_path, requested_ref, resolved_commit_sha, | ||
| local_path) and | ||
| this.getCallee() = externalCompositeActionName(owner, repo, action_path) and | ||
| this.getVersion() = requested_ref.trim() | ||
| ) | ||
| } | ||
|
|
||
| private predicate hasExternalEnclosingCompositeAction() { | ||
| exists(CompositeActionImpl action | | ||
| action = this.getEnclosingCompositeAction() and action.isExternalCompositeAction() | ||
| ) | ||
| } | ||
|
|
||
| private predicate hasModeledExternalEnclosingCompositeAction() { | ||
| exists( | ||
| CompositeActionImpl action, string owner, string repo, string action_path, | ||
Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading |
||
| string requested_ref, string resolved_commit_sha, string local_path | ||
Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading |
||
| | | ||
| action = this.getEnclosingCompositeAction() and | ||
| action | ||
| .getAnExternalCompositeActionModel(owner, repo, action_path, requested_ref, | ||
| resolved_commit_sha, local_path) | ||
| ) | ||
| } | ||
|
|
||
| private string getSelfCallableName() { | ||
| exists( | ||
| CompositeActionImpl action, string owner, string repo, string action_path, | ||
Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading |
||
| string requested_ref, string resolved_commit_sha, string local_path | ||
Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading |
||
| | | ||
| action = this.getEnclosingCompositeAction() and | ||
| action | ||
| .getAnExternalCompositeActionModel(owner, repo, action_path, requested_ref, | ||
| resolved_commit_sha, local_path) and | ||
| result = | ||
| externalCompositeActionName(owner, repo, this.getCallee().suffix(2)) + "@" + | ||
|
JarLob marked this conversation as resolved.
|
||
| requested_ref.trim() | ||
| ) | ||
| or | ||
| not this.hasExternalEnclosingCompositeAction() and | ||
| result = this.getCallee().suffix(2) | ||
| } | ||
|
|
||
| override string getCallableName() { | ||
| this.isWorkspaceLocalCall() and | ||
| ( | ||
| this.hasModeledExternalEnclosingCompositeAction() | ||
| or | ||
| not this.hasExternalEnclosingCompositeAction() | ||
| ) and | ||
| result = this.getCallee() | ||
| or | ||
| this.isSelfCall() and | ||
| result = this.getSelfCallableName() | ||
| or | ||
| not this.isLocalCall() and | ||
| this.hasModeledExternalCallee() and | ||
| result = this.getCallee() + "@" + this.getVersion() | ||
| } | ||
|
|
||
| override ScalarValueImpl getCalleeNode() { result.getNode() = u } | ||
|
|
||
| /** Gets the version reference used when checking out the Action, e.g. `v2` in `actions/checkout@v2`. */ | ||
|
|
@@ -1388,27 +1526,70 @@ | |
| * Gets a regular expression that parses an `owner/repo@version` reference within a `uses` field in an Actions job step. | ||
| * local repo: octo-org/this-repo/.github/workflows/workflow-1.yml@172239021f7ba04fe7327647b213799853a9eb89 | ||
| * local repo: ./.github/workflows/workflow-2.yml | ||
| * local repo: $/.github/workflows/workflow-2.yml | ||
| * remote repo: octo-org/another-repo/.github/workflows/workflow.yml@v1 | ||
| */ | ||
| private string repoUsesParser() { result = "([^/]+)/([^/]+)/([^@]+)@(.+)" } | ||
|
|
||
| private string pathUsesParser() { result = "\\./(.+)" } | ||
|
|
||
| class ExternalJobImpl extends JobImpl, UsesImpl { | ||
| YamlScalar u; | ||
|
|
||
| ExternalJobImpl() { n.lookup("uses") = u } | ||
|
|
||
| override string getCallee() { | ||
| if u.getValue().matches("./%") | ||
| then result = u.getValue().regexpCapture(pathUsesParser(), 1) | ||
| if u.getValue().matches(["./%", "$/%"]) | ||
| then result = u.getValue().suffix(2) | ||
| else | ||
| result = | ||
| u.getValue().regexpCapture(repoUsesParser(), 1) + "/" + | ||
| u.getValue().regexpCapture(repoUsesParser(), 2) + "/" + | ||
| u.getValue().regexpCapture(repoUsesParser(), 3) | ||
| } | ||
|
|
||
| private predicate isLocalCall() { u.getValue().matches(["./%", "$/%"]) } | ||
|
|
||
| private predicate hasExternalEnclosingWorkflow() { | ||
| exists(ReusableWorkflowImpl enclosing_workflow | | ||
| enclosing_workflow = this.getEnclosingWorkflow() and | ||
| enclosing_workflow.isExternalReusableWorkflow() | ||
| ) | ||
| } | ||
|
|
||
| private predicate hasModeledExternalCallee() { | ||
| exists( | ||
| string owner, string repo, string workflow_path, string requested_ref, | ||
| string resolved_commit_sha, string local_path | ||
Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading |
||
| | | ||
| externalReusableWorkflowDataModel(owner, repo, workflow_path, requested_ref, | ||
| resolved_commit_sha, local_path) and | ||
| this.getCallee() = owner.trim() + "/" + repo.trim() + "/" + workflow_path.trim() and | ||
| this.getVersion() = requested_ref.trim() | ||
| ) | ||
| } | ||
|
|
||
| override string getCallableName() { | ||
| this.isLocalCall() and | ||
| exists( | ||
| ReusableWorkflowImpl enclosing_workflow, string owner, string repo, string workflow_path, | ||
Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading |
||
| string requested_ref, string resolved_commit_sha, string local_path | ||
Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading Check warningCode scanning / CodeQL Omittable 'exists' variable Warning
This exists variable can be omitted by using a don't-care expression
in this argument Error loading related location Loading |
||
| | | ||
| enclosing_workflow = this.getEnclosingWorkflow() and | ||
| enclosing_workflow | ||
| .getAnExternalReusableWorkflowModel(owner, repo, workflow_path, requested_ref, | ||
| resolved_commit_sha, local_path) and | ||
| result = | ||
| owner.trim() + "/" + repo.trim() + "/" + this.getCallee() + "@" + requested_ref.trim() | ||
| ) | ||
| or | ||
| this.isLocalCall() and | ||
| not this.hasExternalEnclosingWorkflow() and | ||
| result = this.getCallee() | ||
| or | ||
| not this.isLocalCall() and | ||
| this.hasModeledExternalCallee() and | ||
| result = this.getCallee() + "@" + this.getVersion() | ||
| } | ||
|
|
||
| override ScalarValueImpl getCalleeNode() { result.getNode() = u } | ||
|
|
||
| /** Gets the version reference used when checking out the Action, e.g. `v2` in `actions/checkout@v2`. */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This magic number approach does not sound right to me. But I lack sufficient contextual knowledge here to suggest anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is just a GUID without dashes. On one hand, especially because it becomes visible in web ui alert, it could be something human readable. On the other hand something very short such as
reusable_workflowmay collide with a folder in the repository. So something likedownloaded_external_workflows_and_actionsmay work. I went with a unique GUID, but I'm open for discussions.