Skip to content

Commit 2a157f8

Browse files
Actions: Resolve $/ self repository references to local callables
Calls using `uses: $/path` were not linked to the reusable workflow or composite action they reference, so callees were analyzed without their callers. Add `UsesImpl.getCallableName()`, which strips the `$/` prefix, and use it for call resolution. `getCallee()` keeps the prefix, since `actions/unpinned-tag` and `LocalActionUsesStep` depend on it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b5b8165 commit 2a157f8

22 files changed

Lines changed: 244 additions & 5 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Calls to reusable workflows and composite actions using the `$/` self repository syntax (for example `uses: $/.github/workflows/build.yml`) are now resolved in the same way as `./` references. This removes some false positive results and reports some results at their correct severity.

‎actions/ql/lib/codeql/actions/ast/internal/Ast.qll‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,13 @@ class EnvImpl extends AstNodeImpl, TEnvNode {
13361336
abstract class UsesImpl extends AstNodeImpl {
13371337
abstract string getCallee();
13381338

1339+
/** Gets the name of the callable referenced by this `uses` value, without any `$/` prefix. */
1340+
string getCallableName() {
1341+
if this.getCallee().matches("$/%")
1342+
then result = this.getCallee().suffix(2)
1343+
else result = this.getCallee()
1344+
}
1345+
13391346
abstract ScalarValueImpl getCalleeNode();
13401347

13411348
abstract string getVersion();
@@ -1394,6 +1401,9 @@ private string repoUsesParser() { result = "([^/]+)/([^/]+)/([^@]+)@(.+)" }
13941401

13951402
private string pathUsesParser() { result = "\\./(.+)" }
13961403

1404+
/** Gets a regular expression that parses a `$/path` reference, ignoring any `@ref` suffix. */
1405+
private string selfRepositoryUsesParser() { result = "(\\$/[^@]+)(@.*)?" }
1406+
13971407
class ExternalJobImpl extends JobImpl, UsesImpl {
13981408
YamlScalar u;
13991409

@@ -1403,10 +1413,13 @@ class ExternalJobImpl extends JobImpl, UsesImpl {
14031413
if u.getValue().matches("./%")
14041414
then result = u.getValue().regexpCapture(pathUsesParser(), 1)
14051415
else
1406-
result =
1407-
u.getValue().regexpCapture(repoUsesParser(), 1) + "/" +
1408-
u.getValue().regexpCapture(repoUsesParser(), 2) + "/" +
1409-
u.getValue().regexpCapture(repoUsesParser(), 3)
1416+
if u.getValue().matches("$/%")
1417+
then result = u.getValue().regexpCapture(selfRepositoryUsesParser(), 1)
1418+
else
1419+
result =
1420+
u.getValue().regexpCapture(repoUsesParser(), 1) + "/" +
1421+
u.getValue().regexpCapture(repoUsesParser(), 2) + "/" +
1422+
u.getValue().regexpCapture(repoUsesParser(), 3)
14101423
}
14111424

14121425
override ScalarValueImpl getCalleeNode() { result.getNode() = u }

‎actions/ql/lib/codeql/actions/dataflow/internal/DataFlowPrivate.qll‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class DataFlowCall instanceof Cfg::Node {
8383
/** Gets a textual representation of this element. */
8484
string toString() { result = super.toString() }
8585

86-
string getName() { result = super.getAstNode().(Uses).getCallee() }
86+
string getName() { result = super.getAstNode().(UsesImpl).getCallableName() }
8787

8888
DataFlowCallable getEnclosingCallable() { result = super.getEnclosingCallable() }
8989

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: Greet
2+
description: Echo a greeting
3+
inputs:
4+
who:
5+
description: Name
6+
required: true
7+
runs:
8+
using: composite
9+
steps:
10+
- run: echo "Hello"
11+
shell: bash
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# `$/` references must not have an `@ref` suffix, but it is ignored if present.
2+
on: push
3+
4+
jobs:
5+
call-reusable:
6+
uses: $/.github/workflows/reusable.yml@v1
7+
call-action:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- uses: $/.github/actions/greet@v1
11+
with:
12+
who: world
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
on:
2+
schedule:
3+
- cron: '0 9 * * 1'
4+
5+
jobs:
6+
call-reusable:
7+
uses: $/.github/workflows/reusable.yml
8+
call-action:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: $/.github/actions/greet
12+
with:
13+
who: world
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
on: workflow_dispatch
2+
3+
jobs:
4+
call-reusable:
5+
uses: ./.github/workflows/reusable.yml
6+
call-action:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: ./.github/actions/greet
10+
with:
11+
who: world
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
on: workflow_call
2+
3+
jobs:
4+
build:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- run: echo build
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
callees
2+
| .github/workflows/caller-dollar-ref.yml:6:5:7:2 | Job: call-reusable | $/.github/workflows/reusable.yml |
3+
| .github/workflows/caller-dollar-ref.yml:10:9:12:21 | Uses Step | $/.github/actions/greet |
4+
| .github/workflows/caller-dollar.yml:7:5:8:2 | Job: call-reusable | $/.github/workflows/reusable.yml |
5+
| .github/workflows/caller-dollar.yml:11:9:13:21 | Uses Step | $/.github/actions/greet |
6+
| .github/workflows/caller-dot.yml:5:5:6:2 | Job: call-reusable | .github/workflows/reusable.yml |
7+
| .github/workflows/caller-dot.yml:9:9:11:21 | Uses Step | ./.github/actions/greet |
8+
reusableWorkflowCallers
9+
| .github/workflows/reusable.yml:1:1:7:24 | on: workflow_call | .github/workflows/caller-dollar-ref.yml:6:5:7:2 | Job: call-reusable | push |
10+
| .github/workflows/reusable.yml:1:1:7:24 | on: workflow_call | .github/workflows/caller-dollar.yml:7:5:8:2 | Job: call-reusable | schedule |
11+
| .github/workflows/reusable.yml:1:1:7:24 | on: workflow_call | .github/workflows/caller-dot.yml:5:5:6:2 | Job: call-reusable | workflow_dispatch |
12+
compositeActionCallers
13+
| .github/actions/greet/action.yml:1:1:11:18 | name: Greet | .github/workflows/caller-dollar-ref.yml:10:9:12:21 | Uses Step | push |
14+
| .github/actions/greet/action.yml:1:1:11:18 | name: Greet | .github/workflows/caller-dollar.yml:11:9:13:21 | Uses Step | schedule |
15+
| .github/actions/greet/action.yml:1:1:11:18 | name: Greet | .github/workflows/caller-dot.yml:9:9:11:21 | Uses Step | workflow_dispatch |
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import actions
2+
3+
query predicate callees(Uses uses, string callee) { uses.getCallee() = callee }
4+
5+
query predicate reusableWorkflowCallers(ReusableWorkflow workflow, ExternalJob caller, string event) {
6+
workflow.getACaller() = caller and
7+
exists(LocalJob job |
8+
job.getEnclosingWorkflow() = workflow and
9+
job.getATriggerEvent().getName() = event and
10+
caller.getATriggerEvent().getName() = event
11+
)
12+
}
13+
14+
query predicate compositeActionCallers(CompositeAction action, UsesStep caller, string event) {
15+
action.getACallerStep() = caller and
16+
action.getATriggerEvent().getName() = event and
17+
caller.getATriggerEvent().getName() = event
18+
}

0 commit comments

Comments
 (0)