NVIDIA / NVIDIA/cluster-readiness-engine

Close the env-laundering class in TestAttestPredicateUsesOnlyTrustedContext with a sentinel harness

Open
#339 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement github_actions security
Dominant language
Go
Stars
55
Forks
19
Avg merge
1d 5h
Merged PRs (30d)
107

Description

Part of #262. Follow-up to #301, gated in #308. Size: S.

Problem

TestAttestPredicateUsesOnlyTrustedContext (test/releasepolicy/attest_boundary_test.go:105) keeps caller-controlled values out of the provenance origin fields two ways, and both share one blind spot.

  1. A static scan over step.MergedEnv and step.Run for inputs., inputs[, needs.*.outputs.* and needs[ (attest_boundary_test.go:113-129). It reads workflow, job and step env for the provenance step only. It never sees an earlier step in the same job writing to $GITHUB_ENV.
  2. Executing the extracted run block and asserting the emitted origin fields (attest_boundary_test.go:141 onward). The harness sets a fixed trusted set: CALLER_WORKFLOW_REF, GITHUB_REPOSITORY, GITHUB_REF, GITHUB_SHA, GITHUB_SERVER_URL, GITHUB_RUN_ID, SUBJECT_KIND (attest_boundary_test.go:158-166). Any other variable the run block references is left unset, so ${VAR:-} expands empty and the exact-value assertions still pass.

Both miss the same thing. Adding this to the attest job ahead of the provenance step at attest.yml:463:

- name: Stage build metadata
  env:
    X: ${{ inputs.subject_name }}
  run: |
    set -euo pipefail
    echo "SNEAKY=${X}" >> "$GITHUB_ENV"

and folding it into attest.yml:489 as --arg repo "${GITHUB_REPOSITORY}${SNEAKY:-}" leaves test/releasepolicy, test/docspolicy and test/helm all green. Running that same extracted step with SNEAKY set the way a real run would set it gives:

externalParameters.repository = NVIDIA/cluster-readiness-engine/../attacker-controlled

The needle list closes the two syntaxes demonstrated on #308 rather than the class behind them, so every new laundering route needs a new needle.

Scope

Add a sentinel pass to the executed half of the test, and treat it as the primary control rather than the scan:

  1. Parse the provenance step's run block for every shell variable it references.
  2. Set each one outside the trusted set to a unique sentinel value, rather than leaving it unset.
  3. Execute, then assert the sentinel reaches none of the origin fields: externalParameters.repository, externalParameters.ref, resolvedDependencies[].uri, resolvedDependencies[].digest.gitCommit, runDetails.builder.id, runDetails.metadata.invocationId.
  4. Keep externalParameters.subjectKind carved out on the same reasoning as today. It is a caller-chosen, enum-validated pass-through that shapes the predicate without claiming origin.

One detail to watch: the harness builds its environment with append(os.Environ(), ...), so the sentinel pass should start from a clean environment. Otherwise an inherited value can stand in for a variable the test meant to control.

This closes the class however the value arrives, whether through $GITHUB_ENV from an earlier step, a second jq writer, or an expression syntax nobody has thought of yet.

Acceptance criteria

  • A $GITHUB_ENV write from an earlier step in the attest job, folded into any origin field, fails test/releasepolicy.
  • The two forms gated in #308, inputs['subject_name'] in the provenance step env and needs['validate'].outputs.*, still fail.
  • attest.yml as it stands on main still passes, with externalParameters.subjectKind still driven by the caller.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with TestAttestPredicateUsesOnlyTrustedContext in test/releasepolicy/attest_boundary_test.go, especially its environment setup and executed run-block assertions, then inspect the provenance step at attest.yml:463 and :489. Run the existing release-policy tests before and after the change. Done means sentinel values from every non-trusted shell variable cannot reach the listed origin fields, while the existing gated cases fail and attest.yml on main still passes.

Written by the indexing model from the issue text.

Assessment

Tech stack
bash, go, yaml
Domain
ci-cd, security, testing-qa
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.