Skip to content

Commit 65d09b3

Browse files
author
Alvaro Muñoz
authored
Merge pull request #91 from github/fix/artpoison
Improve artifact poisoning query
2 parents 26f829e + 86c1d9c commit 65d09b3

10 files changed

Lines changed: 200 additions & 33 deletions

File tree

ql/lib/codeql/actions/dataflow/FlowSources.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ class MaDSource extends RemoteFlowSource {
107107
/**
108108
* A downloaded artifact.
109109
*/
110-
private class ArtifactSource extends RemoteFlowSource {
110+
class ArtifactSource extends RemoteFlowSource {
111111
ArtifactSource() { this.asExpr() instanceof UntrustedArtifactDownloadStep }
112112

113113
override string getSourceType() { result = "artifact" }

ql/lib/codeql/actions/dataflow/FlowSteps.qll

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,12 +263,11 @@ predicate artifactDownloadToRunStep(DataFlow::Node pred, DataFlow::Node succ) {
263263

264264
//
265265
/**
266-
* A download artifact step followed by a envvar-injection uses step .
266+
* A download artifact step followed by a uses step .
267267
*/
268268
predicate artifactDownloadToUsesStep(DataFlow::Node pred, DataFlow::Node succ) {
269269
exists(Step artifact, Uses uses |
270270
controlledCWD(artifact) and
271-
madSink(succ, "envvar-injection") and
272271
pred.asExpr() = artifact and
273272
succ.asExpr() = uses and
274273
artifact.getAFollowingStep() = uses

ql/lib/codeql/actions/security/ArtifactPoisoningQuery.qll

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@ import codeql.actions.security.PoisonableSteps
77

88
string unzipRegexp() { result = ".*(unzip|tar)\\s+.*" }
99

10-
string unzipDirArgRegexp() {
11-
result = "-d\\s+\"([^ ]+)\".*" or
12-
result = "-d\\s+'([^ ]+)'.*"
13-
}
10+
string unzipDirArgRegexp() { result = "-d\\s+([^ ]+).*" }
1411

1512
abstract class UntrustedArtifactDownloadStep extends Step {
1613
abstract string getPath();
@@ -164,11 +161,11 @@ class ActionsGitHubScriptDownloadStep extends UntrustedArtifactDownloadStep, Use
164161
.regexpMatch(unzipRegexp() + unzipDirArgRegexp())
165162
then
166163
result =
167-
this.getAFollowingStep()
168-
.(Run)
169-
.getScript()
170-
.splitAt("\n")
171-
.regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 2)
164+
trimQuotes(this.getAFollowingStep()
165+
.(Run)
166+
.getScript()
167+
.splitAt("\n")
168+
.regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 2))
172169
else
173170
if this.getAFollowingStep().(Run).getScript().splitAt("\n").regexpMatch(unzipRegexp())
174171
then result = ""
@@ -199,13 +196,14 @@ class GHRunArtifactDownloadStep extends UntrustedArtifactDownloadStep, Run {
199196
.regexpMatch(unzipRegexp() + unzipDirArgRegexp()) or
200197
script.splitAt("\n").regexpMatch(unzipRegexp() + unzipDirArgRegexp())
201198
then
202-
result = script.splitAt("\n").regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 2) or
203199
result =
204-
this.getAFollowingStep()
205-
.(Run)
206-
.getScript()
207-
.splitAt("\n")
208-
.regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 2)
200+
trimQuotes(script.splitAt("\n").regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 2)) or
201+
result =
202+
trimQuotes(this.getAFollowingStep()
203+
.(Run)
204+
.getScript()
205+
.splitAt("\n")
206+
.regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 2))
209207
else
210208
if
211209
this.getAFollowingStep().(Run).getScript().splitAt("\n").regexpMatch(unzipRegexp()) or
@@ -245,45 +243,55 @@ class DirectArtifactDownloadStep extends UntrustedArtifactDownloadStep, Run {
245243
.splitAt("\n")
246244
.regexpMatch(unzipRegexp() + unzipDirArgRegexp())
247245
then
248-
result = script.splitAt("\n").regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 2) or
249246
result =
250-
this.getAFollowingStep()
251-
.(Run)
252-
.getScript()
253-
.splitAt("\n")
254-
.regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 2)
247+
trimQuotes(script.splitAt("\n").regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 2)) or
248+
result =
249+
trimQuotes(this.getAFollowingStep()
250+
.(Run)
251+
.getScript()
252+
.splitAt("\n")
253+
.regexpCapture(unzipRegexp() + unzipDirArgRegexp(), 2))
255254
else result = ""
256255
}
257256
}
258257

259258
class ArtifactPoisoningSink extends DataFlow::Node {
259+
UntrustedArtifactDownloadStep download;
260+
PoisonableStep poisonable;
261+
260262
ArtifactPoisoningSink() {
261-
exists(UntrustedArtifactDownloadStep download, PoisonableStep poisonable |
262-
download.getAFollowingStep() = poisonable and
263-
(
264-
poisonable.(Run).getScriptScalar() = this.asExpr()
265-
or
266-
poisonable.(UsesStep) = this.asExpr()
267-
) and
263+
download.getAFollowingStep() = poisonable and
264+
// excluding artifacts downloaded to /tmp
265+
not download.getPath().regexpMatch("^/tmp.*") and
266+
(
267+
poisonable.(Run).getScriptScalar() = this.asExpr() and
268268
(
269269
// Check if the poisonable step is a local script execution step
270270
// and the path of the command or script matches the path of the downloaded artifact
271-
not poisonable instanceof LocalScriptExecutionRunStep or
271+
// Checking the path for non local script execution steps is very difficult
272+
not poisonable instanceof LocalScriptExecutionRunStep
273+
or
274+
// TODO: account for Run's working directory
272275
poisonable
273276
.(LocalScriptExecutionRunStep)
274277
.getCommand()
275278
.matches(["./", ""] + download.getPath() + "%")
276279
)
280+
or
281+
poisonable.(UsesStep) = this.asExpr() and
282+
download.getPath() = ""
277283
)
278284
}
285+
286+
string getPath() { result = download.getPath() }
279287
}
280288

281289
/**
282290
* A taint-tracking configuration for unsafe artifacts
283291
* that is used may lead to artifact poisoning
284292
*/
285293
private module ArtifactPoisoningConfig implements DataFlow::ConfigSig {
286-
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
294+
predicate isSource(DataFlow::Node source) { source instanceof ArtifactSource }
287295

288296
predicate isSink(DataFlow::Node sink) { sink instanceof ArtifactPoisoningSink }
289297
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: DownloadArtifacts
2+
description: 'Downloads and unarchives artifacts for a workflow that runs on workflow_run so that it can use its data'
3+
runs:
4+
using: "composite"
5+
steps:
6+
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
7+
with:
8+
script: |
9+
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
10+
owner: context.repo.owner,
11+
repo: context.repo.repo,
12+
run_id: context.payload.workflow_run.id,
13+
});
14+
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
15+
return artifact.name == "artifacts"
16+
})[0];
17+
let download = await github.rest.actions.downloadArtifact({
18+
owner: context.repo.owner,
19+
repo: context.repo.repo,
20+
artifact_id: matchArtifact.id,
21+
archive_format: 'zip',
22+
});
23+
let fs = require('fs');
24+
fs.writeFileSync(`/tmp/artifacts.zip`, Buffer.from(download.data));
25+
- run: |
26+
mkdir -p /tmp/artifacts
27+
unzip /tmp/artifacts.zip
28+
shell: bash
29+
- run: |
30+
echo "Downloaded artifacts:"
31+
ls -ablh
32+
shell: bash
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: DownloadArtifacts
2+
description: 'Downloads and unarchives artifacts for a workflow that runs on workflow_run so that it can use its data'
3+
runs:
4+
using: "composite"
5+
steps:
6+
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
7+
with:
8+
script: |
9+
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
10+
owner: context.repo.owner,
11+
repo: context.repo.repo,
12+
run_id: context.payload.workflow_run.id,
13+
});
14+
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
15+
return artifact.name == "artifacts"
16+
})[0];
17+
let download = await github.rest.actions.downloadArtifact({
18+
owner: context.repo.owner,
19+
repo: context.repo.repo,
20+
artifact_id: matchArtifact.id,
21+
archive_format: 'zip',
22+
});
23+
let fs = require('fs');
24+
fs.writeFileSync(`/tmp/artifacts.zip`, Buffer.from(download.data));
25+
- run: |
26+
mkdir -p /tmp/artifacts
27+
unzip /tmp/artifacts.zip -d /tmp/artifacts
28+
shell: bash
29+
- run: |
30+
echo "Downloaded artifacts:"
31+
ls -ablh /tmp/artifacts
32+
shell: bash
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: SnapshotPR
2+
on:
3+
workflow_run:
4+
workflows:
5+
- ApprovalComment
6+
types:
7+
- completed
8+
jobs:
9+
snapshot:
10+
permissions:
11+
id-token: write
12+
pull-requests: write
13+
statuses: write
14+
if: github.event.workflow_run.conclusion == 'success'
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
18+
- uses: ./.github/actions/download-artifact
19+
- id: metadata
20+
run: |
21+
pr_number="$(head -n 2 /tmp/artifacts/metadata.txt | tail -n 1)"
22+
pr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"
23+
echo PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"
24+
echo PR_NUMBER="$pr_number" >> "$GITHUB_ENV"
25+
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
26+
with:
27+
ref: ${{ env.PR_COMMIT }}
28+
- uses: ./.github/actions/install-deps
29+
- run: make snapshot
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: SnapshotPR
2+
on:
3+
workflow_run:
4+
workflows:
5+
- ApprovalComment
6+
types:
7+
- completed
8+
jobs:
9+
snapshot:
10+
permissions:
11+
id-token: write
12+
pull-requests: write
13+
statuses: write
14+
if: github.event.workflow_run.conclusion == 'success'
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
18+
- uses: ./.github/actions/download-artifact-2
19+
- id: metadata
20+
run: |
21+
pr_number="$(head -n 2 /tmp/artifacts/metadata.txt | tail -n 1)"
22+
pr_commit="$(tail -n 1 /tmp/artifacts/metadata.txt)"
23+
echo PR_COMMIT="$pr_commit" >> "$GITHUB_ENV"
24+
echo PR_NUMBER="$pr_number" >> "$GITHUB_ENV"
25+
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
26+
with:
27+
ref: ${{ env.PR_COMMIT }}
28+
- uses: ./.github/actions/install-deps
29+
- run: make snapshot

ql/test/query-tests/Security/CWE-829/ArtifactPoisoningCritical.expected

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
edges
2+
| .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:25:9:28:6 | Uses Step | provenance | |
3+
| .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | provenance | |
4+
| .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:29:14:29:26 | make snapshot | provenance | |
25
| .github/workflows/artifactpoisoning11.yml:13:9:32:6 | Uses Step | .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | provenance | |
36
| .github/workflows/artifactpoisoning12.yml:13:9:32:6 | Uses Step | .github/workflows/artifactpoisoning12.yml:38:11:38:25 | python foo/x.py | provenance | |
47
| .github/workflows/artifactpoisoning21.yml:13:9:18:6 | Uses Step | .github/workflows/artifactpoisoning21.yml:19:14:20:21 | sh foo/cmd\n | provenance | |
@@ -14,7 +17,10 @@ edges
1417
| .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n | provenance | |
1518
| .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | provenance | |
1619
| .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | provenance | |
20+
| .github/workflows/artifactpoisoning92.yml:25:9:28:6 | Uses Step | .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | provenance | |
21+
| .github/workflows/artifactpoisoning92.yml:25:9:28:6 | Uses Step | .github/workflows/artifactpoisoning92.yml:29:14:29:26 | make snapshot | provenance | |
1722
nodes
23+
| .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | semmle.label | Uses Step |
1824
| .github/workflows/artifactpoisoning11.yml:13:9:32:6 | Uses Step | semmle.label | Uses Step |
1925
| .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | semmle.label | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build |
2026
| .github/workflows/artifactpoisoning12.yml:13:9:32:6 | Uses Step | semmle.label | Uses Step |
@@ -45,6 +51,9 @@ nodes
4551
| .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | semmle.label | sed -f config foo.md > bar.md\n |
4652
| .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | semmle.label | Uses Step |
4753
| .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | semmle.label | python test.py |
54+
| .github/workflows/artifactpoisoning92.yml:25:9:28:6 | Uses Step | semmle.label | Uses Step |
55+
| .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | semmle.label | Uses Step |
56+
| .github/workflows/artifactpoisoning92.yml:29:14:29:26 | make snapshot | semmle.label | make snapshot |
4857
subpaths
4958
#select
5059
| .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | .github/workflows/artifactpoisoning11.yml:13:9:32:6 | Uses Step | .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build |
@@ -62,3 +71,5 @@ subpaths
6271
| .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n | .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n |
6372
| .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | sed -f config foo.md > bar.md\n |
6473
| .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | python test.py |
74+
| .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | Uses Step |
75+
| .github/workflows/artifactpoisoning92.yml:29:14:29:26 | make snapshot | .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:29:14:29:26 | make snapshot | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning92.yml:29:14:29:26 | make snapshot | make snapshot |

ql/test/query-tests/Security/CWE-829/ArtifactPoisoningMedium.expected

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
edges
2+
| .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:25:9:28:6 | Uses Step | provenance | |
3+
| .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | provenance | |
4+
| .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | .github/workflows/artifactpoisoning92.yml:29:14:29:26 | make snapshot | provenance | |
25
| .github/workflows/artifactpoisoning11.yml:13:9:32:6 | Uses Step | .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | provenance | |
36
| .github/workflows/artifactpoisoning12.yml:13:9:32:6 | Uses Step | .github/workflows/artifactpoisoning12.yml:38:11:38:25 | python foo/x.py | provenance | |
47
| .github/workflows/artifactpoisoning21.yml:13:9:18:6 | Uses Step | .github/workflows/artifactpoisoning21.yml:19:14:20:21 | sh foo/cmd\n | provenance | |
@@ -14,7 +17,10 @@ edges
1417
| .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n | provenance | |
1518
| .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | provenance | |
1619
| .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | provenance | |
20+
| .github/workflows/artifactpoisoning92.yml:25:9:28:6 | Uses Step | .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | provenance | |
21+
| .github/workflows/artifactpoisoning92.yml:25:9:28:6 | Uses Step | .github/workflows/artifactpoisoning92.yml:29:14:29:26 | make snapshot | provenance | |
1722
nodes
23+
| .github/actions/download-artifact-2/action.yaml:6:7:25:4 | Uses Step | semmle.label | Uses Step |
1824
| .github/workflows/artifactpoisoning11.yml:13:9:32:6 | Uses Step | semmle.label | Uses Step |
1925
| .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | semmle.label | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build |
2026
| .github/workflows/artifactpoisoning12.yml:13:9:32:6 | Uses Step | semmle.label | Uses Step |
@@ -45,5 +51,8 @@ nodes
4551
| .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | semmle.label | sed -f config foo.md > bar.md\n |
4652
| .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | semmle.label | Uses Step |
4753
| .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | semmle.label | python test.py |
54+
| .github/workflows/artifactpoisoning92.yml:25:9:28:6 | Uses Step | semmle.label | Uses Step |
55+
| .github/workflows/artifactpoisoning92.yml:28:9:29:6 | Uses Step | semmle.label | Uses Step |
56+
| .github/workflows/artifactpoisoning92.yml:29:14:29:26 | make snapshot | semmle.label | make snapshot |
4857
subpaths
4958
#select

0 commit comments

Comments
 (0)