Skip to content

Commit 4b57cee

Browse files
author
Alvaro Muñoz
committed
Initial implementaion of env context support
1 parent 4f0b66e commit 4b57cee

File tree

4 files changed

+100
-45
lines changed

4 files changed

+100
-45
lines changed

ql/lib/codeql/actions/Ast.qll

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -330,11 +330,14 @@ class ExprAccessExpr extends Expression instanceof YamlString {
330330
string getExpression() { result = expr }
331331

332332
JobStmt getJobStmt() { result.getAChildNode*() = this }
333+
334+
abstract Expression getRefExpr();
333335
}
334336

335337
/**
336-
* A ExprAccessExpr where the expression evaluated is a step output read.
337-
* eg: `${{ steps.changed-files.outputs.all_changed_files }}`
338+
* Holds for an ExprAccessExpr accesing the `steps` context.
339+
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
340+
* e.g. `${{ steps.changed-files.outputs.all_changed_files }}`
338341
*/
339342
class StepOutputAccessExpr extends ExprAccessExpr {
340343
string stepId;
@@ -347,17 +350,16 @@ class StepOutputAccessExpr extends ExprAccessExpr {
347350
this.getExpression().regexpCapture("steps\\.[A-Za-z0-9_-]+\\.outputs\\.([A-Za-z0-9_-]+)", 1)
348351
}
349352

350-
string getStepId() { result = stepId }
351-
352-
string getVarName() { result = varName }
353-
354-
StepStmt getStepStmt() { result.getId() = stepId }
353+
override Expression getRefExpr() {
354+
this.getJobStmt() = result.(StepStmt).getJobStmt() and
355+
result.(StepStmt).getId() = stepId
356+
}
355357
}
356358

357359
/**
358-
* A ExprAccessExpr where the expression evaluated is a job output read.
359-
* eg: `${{ needs.job1.outputs.foo}}`
360-
* eg: `${{ jobs.job1.outputs.foo}}` (for reusable workflows)
360+
* Holds for an ExprAccessExpr accesing the `needs` or `job` contexts.
361+
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
362+
* e.g. `${{ needs.job1.outputs.foo}}` or `${{ jobs.job1.outputs.foo}}` (for reusable workflows)
361363
*/
362364
class JobOutputAccessExpr extends ExprAccessExpr {
363365
string jobId;
@@ -372,9 +374,7 @@ class JobOutputAccessExpr extends ExprAccessExpr {
372374
.regexpCapture("(needs|jobs)\\.[A-Za-z0-9_-]+\\.outputs\\.([A-Za-z0-9_-]+)", 2)
373375
}
374376

375-
string getVarName() { result = varName }
376-
377-
Expression getOutputExpr() {
377+
override Expression getRefExpr() {
378378
exists(JobStmt job |
379379
job.getId() = jobId and
380380
job.getLocation().getFile() = this.getLocation().getFile() and
@@ -391,8 +391,9 @@ class JobOutputAccessExpr extends ExprAccessExpr {
391391
}
392392

393393
/**
394-
* A ExprAccessExpr where the expression evaluated is a reusable workflow input read.
395-
* eg: `${{ inputs.foo}}`
394+
* Holds for an ExprAccessExpr accesing the `inputs` context.
395+
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
396+
* e.g. `${{ inputs.foo }}`
396397
*/
397398
class ReusableWorkflowInputAccessExpr extends ExprAccessExpr {
398399
string paramName;
@@ -401,12 +402,23 @@ class ReusableWorkflowInputAccessExpr extends ExprAccessExpr {
401402
paramName = this.getExpression().regexpCapture("inputs\\.([A-Za-z0-9_-]+)", 1)
402403
}
403404

404-
string getParamName() { result = paramName }
405-
406-
Expression getInputExpr() {
405+
override Expression getRefExpr() {
407406
exists(ReusableWorkflowStmt w |
408407
w.getLocation().getFile() = this.getLocation().getFile() and
409408
w.getInputsStmt().getInputExpr(paramName) = result
410409
)
411410
}
412411
}
412+
413+
/**
414+
* Holds for an ExprAccessExpr accesing the `env` context.
415+
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
416+
* e.g. `${{ env.foo }}`
417+
*/
418+
class EnvAccessExpr extends ExprAccessExpr {
419+
string varName;
420+
421+
EnvAccessExpr() { varName = this.getExpression().regexpCapture("env\\.([A-Za-z0-9_-]+)", 1) }
422+
423+
override Expression getRefExpr() { exists(RunExpr s | s.getEnvExpr(varName) = result) }
424+
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,10 @@ predicate runEnvToScriptstep(DataFlow::Node pred, DataFlow::Node succ) {
8181
exists(string script, string line |
8282
script = r.getScript() and
8383
line = script.splitAt("\n") and
84-
line.regexpMatch(".*::set-output\\s+name.*") and
84+
(
85+
line.regexpMatch(".*::set-output\\s+name.*") or
86+
line.regexpMatch(".*>>\\s*$GITHUB_ENV.*")
87+
) and
8588
script.indexOf("$" + ["", "{", "ENV{"] + varName) > 0
8689
) and
8790
succ.asExpr() = r

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

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -164,41 +164,52 @@ class ArgumentPosition extends string {
164164
*/
165165
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { ppos = apos }
166166

167-
predicate stepUsesOutputDefToUse(Node nodeFrom, Node nodeTo) {
168-
// nodeTo is an OutputVarAccessExpr scoped with the namespace of the nodeFrom Step output
169-
exists(StepUsesExpr uses, StepOutputAccessExpr outputRead |
170-
uses = nodeFrom.asExpr() and
171-
outputRead = nodeTo.asExpr() and
172-
outputRead.getStepId() = uses.getId() and
173-
uses.getJobStmt() = outputRead.getJobStmt()
167+
/**
168+
* Holds if there is a local flow step between a ${{}} expression accesing a step output variable and the step output itself
169+
* e.g. ${{ steps.step1.output.foo }}
170+
*/
171+
predicate stepsCtxLocalStep(Node nodeFrom, Node nodeTo) {
172+
exists(StepStmt astFrom, StepOutputAccessExpr astTo |
173+
(astFrom instanceof UsesExpr or astFrom instanceof RunExpr) and
174+
astFrom = nodeFrom.asExpr() and
175+
astTo = nodeTo.asExpr() and
176+
astTo.getRefExpr() = astFrom
174177
)
175178
}
176179

177-
predicate runOutputDefToUse(Node nodeFrom, Node nodeTo) {
178-
// nodeTo is an OutputVarAccessExpr scoped with the namespace of the nodeFrom Step output
179-
exists(RunExpr uses, StepOutputAccessExpr outputRead |
180-
uses = nodeFrom.asExpr() and
181-
outputRead = nodeTo.asExpr() and
182-
outputRead.getStepId() = uses.getId() and
183-
uses.getJobStmt() = outputRead.getJobStmt()
180+
/**
181+
* Holds if there is a local flow step between a ${{}} expression accesing a job output variable and the job output itself
182+
* e.g. ${{ needs.job1.output.foo }} or ${{ job.job1.output.foo }}
183+
*/
184+
predicate jobsCtxLocalStep(Node nodeFrom, Node nodeTo) {
185+
exists(Expression astFrom, JobOutputAccessExpr astTo |
186+
astFrom = nodeFrom.asExpr() and
187+
astTo = nodeTo.asExpr() and
188+
astTo.getRefExpr() = astFrom
184189
)
185190
}
186191

187-
predicate jobOutputDefToUse(Node nodeFrom, Node nodeTo) {
188-
// nodeTo is a JobOutputAccessExpr and nodeFrom is the Job output expression
189-
exists(Expression astFrom, JobOutputAccessExpr astTo |
192+
/**
193+
* Holds if there is a local flow step between a ${{}} expression accesing a reusable workflow input variable and the input itself
194+
* e.g. ${{ inputs.foo }}
195+
*/
196+
predicate inputsCtxLocalStep(Node nodeFrom, Node nodeTo) {
197+
exists(Expression astFrom, ReusableWorkflowInputAccessExpr astTo |
190198
astFrom = nodeFrom.asExpr() and
191199
astTo = nodeTo.asExpr() and
192-
astTo.getOutputExpr() = astFrom
200+
astTo.getRefExpr() = astFrom
193201
)
194202
}
195203

196-
predicate reusableWorkflowInputDefToUse(Node nodeFrom, Node nodeTo) {
197-
// nodeTo is a ReusableWorkflowInputAccessExpr and nodeFrom is the ReusableWorkflowStmt corresponding parameter expression
198-
exists(Expression astFrom, ReusableWorkflowInputAccessExpr astTo |
204+
/**
205+
* Holds if there is a local flow step between a ${{}} expression accesing an env var and the var definition itself
206+
* e.g. ${{ env.foo }}
207+
*/
208+
predicate envCtxLocalStep(Node nodeFrom, Node nodeTo) {
209+
exists(Expression astFrom, EnvAccessExpr astTo |
199210
astFrom = nodeFrom.asExpr() and
200211
astTo = nodeTo.asExpr() and
201-
astTo.getInputExpr() = astFrom
212+
astTo.getRefExpr() = astFrom
202213
)
203214
}
204215

@@ -209,10 +220,10 @@ predicate reusableWorkflowInputDefToUse(Node nodeFrom, Node nodeTo) {
209220
*/
210221
pragma[nomagic]
211222
predicate localFlowStep(Node nodeFrom, Node nodeTo) {
212-
stepUsesOutputDefToUse(nodeFrom, nodeTo) or
213-
runOutputDefToUse(nodeFrom, nodeTo) or
214-
jobOutputDefToUse(nodeFrom, nodeTo) or
215-
reusableWorkflowInputDefToUse(nodeFrom, nodeTo)
223+
stepsCtxLocalStep(nodeFrom, nodeTo) or
224+
jobsCtxLocalStep(nodeFrom, nodeTo) or
225+
inputsCtxLocalStep(nodeFrom, nodeTo) or
226+
envCtxLocalStep(nodeFrom, nodeTo)
216227
}
217228

218229
/**
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Issue Workflow
2+
3+
on:
4+
issues:
5+
types: [opened, edited]
6+
7+
jobs:
8+
redirectIssue:
9+
runs-on: ubuntu-latest
10+
name: Check for issue transfer
11+
env:
12+
content_analysis_response: undefined
13+
steps:
14+
- uses: actions/checkout@v2
15+
- name: Remove conflicting chars
16+
env:
17+
ISSUE_TITLE: ${{github.event.issue.title}}
18+
uses: frabert/[email protected]
19+
id: remove_quotations
20+
with:
21+
pattern: "\""
22+
string: ${{env.ISSUE_TITLE}}
23+
replace-with: "-"
24+
- name: Check info
25+
id: check-info
26+
run: |
27+
echo "foo $(pwsh bar ${{steps.remove_quotations.outputs.replaced}}) " >> $GITHUB_ENV
28+
29+

0 commit comments

Comments
 (0)