Initial implementaion of env context support

This commit is contained in:
Alvaro Muñoz
2024-02-12 15:12:36 +01:00
parent 4f0b66ea03
commit 4b57cee300
4 changed files with 101 additions and 46 deletions

View File

@@ -330,11 +330,14 @@ class ExprAccessExpr extends Expression instanceof YamlString {
string getExpression() { result = expr }
JobStmt getJobStmt() { result.getAChildNode*() = this }
abstract Expression getRefExpr();
}
/**
* A ExprAccessExpr where the expression evaluated is a step output read.
* eg: `${{ steps.changed-files.outputs.all_changed_files }}`
* Holds for an ExprAccessExpr accesing the `steps` context.
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
* e.g. `${{ steps.changed-files.outputs.all_changed_files }}`
*/
class StepOutputAccessExpr extends ExprAccessExpr {
string stepId;
@@ -347,17 +350,16 @@ class StepOutputAccessExpr extends ExprAccessExpr {
this.getExpression().regexpCapture("steps\\.[A-Za-z0-9_-]+\\.outputs\\.([A-Za-z0-9_-]+)", 1)
}
string getStepId() { result = stepId }
string getVarName() { result = varName }
StepStmt getStepStmt() { result.getId() = stepId }
override Expression getRefExpr() {
this.getJobStmt() = result.(StepStmt).getJobStmt() and
result.(StepStmt).getId() = stepId
}
}
/**
* A ExprAccessExpr where the expression evaluated is a job output read.
* eg: `${{ needs.job1.outputs.foo}}`
* eg: `${{ jobs.job1.outputs.foo}}` (for reusable workflows)
* Holds for an ExprAccessExpr accesing the `needs` or `job` contexts.
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
* e.g. `${{ needs.job1.outputs.foo}}` or `${{ jobs.job1.outputs.foo}}` (for reusable workflows)
*/
class JobOutputAccessExpr extends ExprAccessExpr {
string jobId;
@@ -372,9 +374,7 @@ class JobOutputAccessExpr extends ExprAccessExpr {
.regexpCapture("(needs|jobs)\\.[A-Za-z0-9_-]+\\.outputs\\.([A-Za-z0-9_-]+)", 2)
}
string getVarName() { result = varName }
Expression getOutputExpr() {
override Expression getRefExpr() {
exists(JobStmt job |
job.getId() = jobId and
job.getLocation().getFile() = this.getLocation().getFile() and
@@ -391,8 +391,9 @@ class JobOutputAccessExpr extends ExprAccessExpr {
}
/**
* A ExprAccessExpr where the expression evaluated is a reusable workflow input read.
* eg: `${{ inputs.foo}}`
* Holds for an ExprAccessExpr accesing the `inputs` context.
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
* e.g. `${{ inputs.foo }}`
*/
class ReusableWorkflowInputAccessExpr extends ExprAccessExpr {
string paramName;
@@ -401,12 +402,23 @@ class ReusableWorkflowInputAccessExpr extends ExprAccessExpr {
paramName = this.getExpression().regexpCapture("inputs\\.([A-Za-z0-9_-]+)", 1)
}
string getParamName() { result = paramName }
Expression getInputExpr() {
override Expression getRefExpr() {
exists(ReusableWorkflowStmt w |
w.getLocation().getFile() = this.getLocation().getFile() and
w.getInputsStmt().getInputExpr(paramName) = result
)
}
}
/**
* Holds for an ExprAccessExpr accesing the `env` context.
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
* e.g. `${{ env.foo }}`
*/
class EnvAccessExpr extends ExprAccessExpr {
string varName;
EnvAccessExpr() { varName = this.getExpression().regexpCapture("env\\.([A-Za-z0-9_-]+)", 1) }
override Expression getRefExpr() { exists(RunExpr s | s.getEnvExpr(varName) = result) }
}

View File

@@ -81,7 +81,10 @@ predicate runEnvToScriptstep(DataFlow::Node pred, DataFlow::Node succ) {
exists(string script, string line |
script = r.getScript() and
line = script.splitAt("\n") and
line.regexpMatch(".*::set-output\\s+name.*") and
(
line.regexpMatch(".*::set-output\\s+name.*") or
line.regexpMatch(".*>>\\s*$GITHUB_ENV.*")
) and
script.indexOf("$" + ["", "{", "ENV{"] + varName) > 0
) and
succ.asExpr() = r

View File

@@ -164,41 +164,52 @@ class ArgumentPosition extends string {
*/
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { ppos = apos }
predicate stepUsesOutputDefToUse(Node nodeFrom, Node nodeTo) {
// nodeTo is an OutputVarAccessExpr scoped with the namespace of the nodeFrom Step output
exists(StepUsesExpr uses, StepOutputAccessExpr outputRead |
uses = nodeFrom.asExpr() and
outputRead = nodeTo.asExpr() and
outputRead.getStepId() = uses.getId() and
uses.getJobStmt() = outputRead.getJobStmt()
/**
* Holds if there is a local flow step between a ${{}} expression accesing a step output variable and the step output itself
* e.g. ${{ steps.step1.output.foo }}
*/
predicate stepsCtxLocalStep(Node nodeFrom, Node nodeTo) {
exists(StepStmt astFrom, StepOutputAccessExpr astTo |
(astFrom instanceof UsesExpr or astFrom instanceof RunExpr) and
astFrom = nodeFrom.asExpr() and
astTo = nodeTo.asExpr() and
astTo.getRefExpr() = astFrom
)
}
predicate runOutputDefToUse(Node nodeFrom, Node nodeTo) {
// nodeTo is an OutputVarAccessExpr scoped with the namespace of the nodeFrom Step output
exists(RunExpr uses, StepOutputAccessExpr outputRead |
uses = nodeFrom.asExpr() and
outputRead = nodeTo.asExpr() and
outputRead.getStepId() = uses.getId() and
uses.getJobStmt() = outputRead.getJobStmt()
)
}
predicate jobOutputDefToUse(Node nodeFrom, Node nodeTo) {
// nodeTo is a JobOutputAccessExpr and nodeFrom is the Job output expression
/**
* Holds if there is a local flow step between a ${{}} expression accesing a job output variable and the job output itself
* e.g. ${{ needs.job1.output.foo }} or ${{ job.job1.output.foo }}
*/
predicate jobsCtxLocalStep(Node nodeFrom, Node nodeTo) {
exists(Expression astFrom, JobOutputAccessExpr astTo |
astFrom = nodeFrom.asExpr() and
astTo = nodeTo.asExpr() and
astTo.getOutputExpr() = astFrom
astTo.getRefExpr() = astFrom
)
}
predicate reusableWorkflowInputDefToUse(Node nodeFrom, Node nodeTo) {
// nodeTo is a ReusableWorkflowInputAccessExpr and nodeFrom is the ReusableWorkflowStmt corresponding parameter expression
/**
* Holds if there is a local flow step between a ${{}} expression accesing a reusable workflow input variable and the input itself
* e.g. ${{ inputs.foo }}
*/
predicate inputsCtxLocalStep(Node nodeFrom, Node nodeTo) {
exists(Expression astFrom, ReusableWorkflowInputAccessExpr astTo |
astFrom = nodeFrom.asExpr() and
astTo = nodeTo.asExpr() and
astTo.getInputExpr() = astFrom
astTo.getRefExpr() = astFrom
)
}
/**
* Holds if there is a local flow step between a ${{}} expression accesing an env var and the var definition itself
* e.g. ${{ env.foo }}
*/
predicate envCtxLocalStep(Node nodeFrom, Node nodeTo) {
exists(Expression astFrom, EnvAccessExpr astTo |
astFrom = nodeFrom.asExpr() and
astTo = nodeTo.asExpr() and
astTo.getRefExpr() = astFrom
)
}
@@ -209,10 +220,10 @@ predicate reusableWorkflowInputDefToUse(Node nodeFrom, Node nodeTo) {
*/
pragma[nomagic]
predicate localFlowStep(Node nodeFrom, Node nodeTo) {
stepUsesOutputDefToUse(nodeFrom, nodeTo) or
runOutputDefToUse(nodeFrom, nodeTo) or
jobOutputDefToUse(nodeFrom, nodeTo) or
reusableWorkflowInputDefToUse(nodeFrom, nodeTo)
stepsCtxLocalStep(nodeFrom, nodeTo) or
jobsCtxLocalStep(nodeFrom, nodeTo) or
inputsCtxLocalStep(nodeFrom, nodeTo) or
envCtxLocalStep(nodeFrom, nodeTo)
}
/**

View File

@@ -0,0 +1,29 @@
name: Issue Workflow
on:
issues:
types: [opened, edited]
jobs:
redirectIssue:
runs-on: ubuntu-latest
name: Check for issue transfer
env:
content_analysis_response: undefined
steps:
- uses: actions/checkout@v2
- name: Remove conflicting chars
env:
ISSUE_TITLE: ${{github.event.issue.title}}
uses: frabert/replace-string-action@1.2
id: remove_quotations
with:
pattern: "\""
string: ${{env.ISSUE_TITLE}}
replace-with: "-"
- name: Check info
id: check-info
run: |
echo "foo $(pwsh bar ${{steps.remove_quotations.outputs.replaced}}) " >> $GITHUB_ENV