diff --git a/opa/opa_test.go b/opa/opa_test.go index 429762a..37cda3e 100644 --- a/opa/opa_test.go +++ b/opa/opa_test.go @@ -113,6 +113,43 @@ func TestSemverConstraintCheck(t *testing.T) { } } +// A major or minor ref is a moving tag, so it is checked as the latest release +// in its line (#443). run-terraform.yml is vulnerable below 2.7.5. +func TestKnownVulnerabilityInBuildComponentFloatingRefs(t *testing.T) { + opa, err := NewOpa(context.TODO(), &models.Config{ + Include: []models.ConfigInclude{}, + }) + noOpaErrors(t, err) + + cases := map[string]bool{ + "v1": true, // every 1.x release is vulnerable + "v2": false, // 2.7.5 has the fix + "v2.2": true, // every 2.2.x release is vulnerable + "v2.7": false, // 2.7.5 has the fix + "v2.7.1": true, + "v2.7.5": false, + "2.7.1": true, + "v3": false, + } + + for ref, vulnerable := range cases { + t.Run(ref, func(t *testing.T) { + var result []string + err := opa.Eval(context.TODO(), + `[a.osv_id | a := data.rules.known_vulnerability_in_build_component.step_advisory({"uses": input.uses})]`, + map[string]interface{}{"uses": "kartverket/github-workflows/.github/workflows/run-terraform.yml@" + ref}, + &result) + noOpaErrors(t, err) + + if vulnerable { + assert.Equal(t, []string{"GHSA-f9qj-7gh3-mhj4"}, result) + } else { + assert.Empty(t, result) + } + }) + } +} + func TestJobUsesSelfHostedRunner(t *testing.T) { // based on https://github.com/actions/runner-images/ cases := map[string]bool{ diff --git a/opa/rego/rules/known_vulnerability_in_build_component.rego b/opa/rego/rules/known_vulnerability_in_build_component.rego index 42202c0..28e5313 100644 --- a/opa/rego/rules/known_vulnerability_in_build_component.rego +++ b/opa/rego/rules/known_vulnerability_in_build_component.rego @@ -24,7 +24,16 @@ step_advisory(step) = advisory if { regex.match("^[0-9]+(\\.[0-9]+)*?$", version) - semver.constraint_check(advisory.vulnerable_version_ranges[_], version) + semver.constraint_check(advisory.vulnerable_version_ranges[_], latest_version(version)) +} + +# A ref like v4 or v4.1 is a tag that action authors move to each new release +# in that line, so it runs the latest 4.x (or 4.1.x), not 4.0.0. Check the +# highest version the ref can stand for: it is vulnerable only if no release +# in that line has the fix. +latest_version(version) := concat(".", array.concat(parts, padding)) if { + parts := split(version, ".") + padding := array.slice(["999999999", "999999999"], 0, 3 - count(parts)) } results contains poutine.finding(rule, pkg.purl, { diff --git a/test/snapshot/__snapshots__/snapshot_test.snap b/test/snapshot/__snapshots__/snapshot_test.snap index 9fa25d8..3664758 100755 --- a/test/snapshot/__snapshots__/snapshot_test.snap +++ b/test/snapshot/__snapshots__/snapshot_test.snap @@ -280,24 +280,6 @@ "purl": "pkg:github/messypoutine/tiktoken", "rule_id": "injection" }, - { - "meta": { - "details": "Package: rlespinasse/github-slug-action", - "event_triggers": [ - "issues", - "issue_comment", - "pull_request_target", - "pull_request" - ], - "job": "fries", - "line": 36, - "osv_id": "GHSA-6q4m-7476-932w", - "path": ".github/workflows/level0.yml", - "step": "1" - }, - "purl": "pkg:github/messypoutine/gravy-overflow", - "rule_id": "known_vulnerability_in_build_component" - }, { "meta": { "path": ".github/actions/auto-format/action.yml" @@ -770,24 +752,6 @@ "purl": "pkg:github/messypoutine/gravy-overflow", "rule_id": "injection" }, - { - "meta": { - "details": "Package: rlespinasse/github-slug-action", - "event_triggers": [ - "issues", - "issue_comment", - "pull_request_target", - "pull_request" - ], - "job": "fries", - "line": 36, - "osv_id": "GHSA-6q4m-7476-932w", - "path": ".github/workflows/level0.yml", - "step": "1" - }, - "purl": "pkg:github/messypoutine/gravy-overflow", - "rule_id": "known_vulnerability_in_build_component" - }, { "meta": { "details": "Detected usage the GitHub Action `actions/setup-node`",