Skip to content

feat(EC-1881): add except_when support to disallowed_attributes rule data#1739

Open
robnester-rh wants to merge 3 commits into
conforma:mainfrom
robnester-rh:EC-1881
Open

feat(EC-1881): add except_when support to disallowed_attributes rule data#1739
robnester-rh wants to merge 3 commits into
conforma:mainfrom
robnester-rh:EC-1881

Conversation

@robnester-rh

Copy link
Copy Markdown
Contributor

Summary

  • Extends the disallowed_attributes rule data schema with an optional except_when field that suppresses violations when a package's PURL qualifier matches regex patterns
  • Enables teams to allow specific disallowed attributes (e.g., hermeto:pip:package:binary) for packages from trusted indexes while still blocking untrusted sources
  • Adds fail-closed semantics: if no purl-type ref exists, the qualifier is absent, or no pattern matches, the violation is still produced

Test plan

  • 941/941 tests pass with 100% coverage
  • Schema validation rejects malformed except_when entries and invalid regex patterns
  • SPDX disallowed_package_attributes suppresses violations when qualifier matches
  • CycloneDX disallowed_package_attributes suppresses violations when qualifier matches
  • Fail-closed: violations produced when no purl ref, missing qualifier, or no pattern match
  • Multiple patterns and multiple except_when entries work correctly
  • Entries without except_when behave exactly as before (backwards compatible)

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 076cc911-9ffa-477f-bd18-44cb0bdf67c0

📥 Commits

Reviewing files that changed from the base of the PR and between c2ed65d and 6b11e2f.

📒 Files selected for processing (3)
  • antora/docs/modules/ROOT/pages/packages/release_sbom_spdx.adoc
  • policy/lib/sbom/sbom.rego
  • policy/release/sbom_spdx/sbom_spdx.rego
✅ Files skipped from review due to trivial changes (1)
  • antora/docs/modules/ROOT/pages/packages/release_sbom_spdx.adoc
🚧 Files skipped from review as they are similar to previous changes (2)
  • policy/release/sbom_spdx/sbom_spdx.rego
  • policy/lib/sbom/sbom.rego

📝 Walkthrough

Walkthrough

Adds an except_when clause to disallowed SBOM attribute data, validates regex patterns, implements disallowed_attribute_excepted(purl) in library, suppresses matching CycloneDX/SPDX violations, adds comprehensive tests, and updates documentation source line links.

Changes

SBOM disallowed attribute exceptions

Layer / File(s) Summary
Library schema extension and exception evaluation
policy/lib/sbom/sbom.rego, policy/lib/sbom/sbom_test.rego
Extended disallowed_attributes schema to allow except_when with purl_qualifier and regex patterns fields. Added regex validation and the disallowed_attribute_excepted(disallowed, purl_string) function that parses a PURL and suppresses violations when a matching qualifier value satisfies a pattern. Seven new unit tests cover positive/negative matching, missing qualifiers, empty PURLs, and multiple patterns.
CycloneDX policy rule integration and tests
policy/release/sbom_cyclonedx/sbom_cyclonedx.rego, policy/release/sbom_cyclonedx/sbom_cyclonedx_test.rego
Added guard condition to disallowed_package_attributes rule to suppress denials when sbom.disallowed_attribute_excepted() returns true. Six new test cases validate suppression when PURL qualifiers match configured patterns, violation reporting when they don't match, handling of missing qualifiers/PURLs, multiple exception patterns, and mixed-rule scenarios.
SPDX policy rule integration and tests
policy/release/sbom_spdx/sbom_spdx.rego, policy/release/sbom_spdx/sbom_spdx_test.rego
Added guard condition to disallowed_package_attributes rule and introduced _package_purl(pkg) helper to extract PURL from package external references. Five test cases validate the same suppression and violation scenarios as CycloneDX, plus a test helper _spdx_excepted_package() to construct SPDX packages with PURLs and attributes.
Rule data validation and exception schema testing
policy/release/sbom/sbom_test.rego
Extended test_rule_data_validation to include an invalid except_when case with a malformed regex pattern and updated expected validation error results to report regex format failures.
Documentation source link updates
antora/docs/modules/ROOT/pages/packages/release_sbom_cyclonedx.adoc, antora/docs/modules/ROOT/pages/packages/release_sbom_spdx.adoc
Updated five CycloneDX rule documentation links and two SPDX rule documentation links to reflect new line numbers in the underlying policy files after code additions.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main feature being added: support for an except_when field in the disallowed_attributes rule data, which is the primary change across all modified files.
Description check ✅ Passed The description is well-related to the changeset, explaining the feature's purpose (suppressing violations when PURL qualifiers match patterns), providing a concrete use case, and listing comprehensive test plan items aligned with the changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@policy/lib/sbom/sbom.rego`:
- Around line 304-310: In disallowed_attribute_excepted, avoid reparsing
purl_string inside the loop: parse the PURL once at the top of the rule (assign
parsed := ec.purl.parse(purl_string) after confirming purl_string != ""), then
iterate over disallowed.except_when and over parsed.qualifiers to compare
qualifier.key to exception.purl_qualifier and call
url_matches_any_pattern(qualifier.value, exception.patterns); this removes the
repeated parse per exception and keeps the rest of the logic intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 8fded4f4-8737-45b1-8c7f-d2f80e6b57db

📥 Commits

Reviewing files that changed from the base of the PR and between 8ab3082 and 503cea1.

📒 Files selected for processing (9)
  • antora/docs/modules/ROOT/pages/packages/release_sbom_cyclonedx.adoc
  • antora/docs/modules/ROOT/pages/packages/release_sbom_spdx.adoc
  • policy/lib/sbom/sbom.rego
  • policy/lib/sbom/sbom_test.rego
  • policy/release/sbom/sbom_test.rego
  • policy/release/sbom_cyclonedx/sbom_cyclonedx.rego
  • policy/release/sbom_cyclonedx/sbom_cyclonedx_test.rego
  • policy/release/sbom_spdx/sbom_spdx.rego
  • policy/release/sbom_spdx/sbom_spdx_test.rego

Comment thread policy/lib/sbom/sbom.rego Outdated
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review

Findings

Low

  • [edge-case] policy/release/sbom_spdx/sbom_spdx.rego:357 — The _package_purl helper selects the first PURL-type externalRef via purls[0]. If an SPDX package has multiple PURL-type external references, only the first is evaluated for except_when exception matching. In practice, SBOM generators typically produce one PURL per package and the ordering is not attacker-controlled, so the risk is minimal. The test suite covers mixed ref types (test_attributes_except_when_multiple_external_refs) but does not include a case with two referenceType == "purl" entries to verify the first-wins semantics.

Info

  • [design-consistency] The except_when feature follows established architectural patterns well: shared library function in policy/lib/sbom/, symmetric integration in both CycloneDX and SPDX paths, schema validation with format: regex plus explicit regex.is_valid checks, and fail-closed semantics (empty PURL, missing qualifier, or no pattern match all produce violations). This is consistent with how allowed_package_sources and allowed_proxy_urls are structured.
Previous run

Review

Findings

Medium

  • [edge-case] policy/release/sbom_spdx/sbom_spdx.rego:249 — The _package_purl(pkg) helper is defined as a Rego complete rule (f(x) := value if { ... } else := ""). If an SPDX package contains multiple externalRefs entries with referenceType == "purl" that have different referenceLocator values, OPA will raise a "complete rules must not produce multiple outputs" conflict error at evaluation time. While uncommon in practice (SPDX packages typically have at most one PURL ref), this would cause the entire policy evaluation to fail rather than degrade gracefully.
    Remediation: Use an array comprehension to collect matching refs and select the first deterministically, e.g.:
    _package_purl(pkg) := purl if {
        purls := [ref.referenceLocator | some ref in pkg.externalRefs; ref.referenceType == "purl"]
        purl := purls[0]
    } else := ""

Low

  • [missing-test] policy/lib/sbom/sbom_test.rego — No test verifies that disallowed_attribute_excepted correctly rejects a PURL qualifier value containing a trusted URL prefix as a substring (e.g., repository_url=https://evil.com/redirect?to=https://console.redhat.com/api/pypi/foo). All current test patterns use fully anchored regexes (^https://...) which should handle this correctly, but an explicit negative test would guard against future pattern loosening.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jun 4, 2026
@qodo-for-conforma

Copy link
Copy Markdown

Review Summary by Qodo

Add except_when support to disallowed_attributes rule with PURL qualifier matching
✨ Enhancement

Grey Divider

Walkthroughs

Description
• Adds except_when field to disallowed_attributes rule schema
• Enables suppressing violations when PURL qualifiers match regex patterns
• Implements fail-closed semantics for missing PURLs or non-matching patterns
• Validates regex patterns and PURL qualifier matching in both SPDX and CycloneDX
• Updates documentation links to reflect code line number changes
Diagram
flowchart LR
  A["disallowed_attributes rule data"] -->|contains except_when field| B["PURL qualifier patterns"]
  B -->|regex match against| C["Package PURL qualifiers"]
  C -->|match found| D["Suppress violation"]
  C -->|no match or missing| E["Produce violation"]
  F["Schema validation"] -->|validates regex format| B
  G["CycloneDX/SPDX processors"] -->|check exception| H["disallowed_attribute_excepted"]
  H -->|returns true/false| D
  H -->|returns true/false| E

Loading

Grey Divider

File Changes

1. policy/lib/sbom/sbom.rego ✨ Enhancement +41/-0

Add except_when schema validation and matching logic

policy/lib/sbom/sbom.rego


2. policy/lib/sbom/sbom_test.rego 🧪 Tests +73/-0

Add comprehensive tests for except_when functionality

policy/lib/sbom/sbom_test.rego


3. policy/release/sbom/sbom_test.rego 🧪 Tests +14/-0

Add invalid regex validation test cases

policy/release/sbom/sbom_test.rego


View more (6)
4. policy/release/sbom_cyclonedx/sbom_cyclonedx.rego ✨ Enhancement +2/-0

Integrate except_when exception check in deny rule

policy/release/sbom_cyclonedx/sbom_cyclonedx.rego


5. policy/release/sbom_cyclonedx/sbom_cyclonedx_test.rego 🧪 Tests +182/-0

Add except_when matching and fail-closed scenario tests

policy/release/sbom_cyclonedx/sbom_cyclonedx_test.rego


6. policy/release/sbom_spdx/sbom_spdx.rego ✨ Enhancement +8/-0

Integrate except_when exception check and add PURL extraction

policy/release/sbom_spdx/sbom_spdx.rego


7. policy/release/sbom_spdx/sbom_spdx_test.rego 🧪 Tests +176/-0

Add except_when tests with SPDX-specific PURL handling

policy/release/sbom_spdx/sbom_spdx_test.rego


8. antora/docs/modules/ROOT/pages/packages/release_sbom_cyclonedx.adoc 📝 Documentation +5/-5

Update documentation source code line number references

antora/docs/modules/ROOT/pages/packages/release_sbom_cyclonedx.adoc


9. antora/docs/modules/ROOT/pages/packages/release_sbom_spdx.adoc 📝 Documentation +2/-2

Update documentation source code line number references

antora/docs/modules/ROOT/pages/packages/release_sbom_spdx.adoc


Grey Divider

Qodo Logo

@qodo-for-conforma

Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

Sorry, something went wrong

We weren't able to complete the code review on our side. Please try again

Grey Divider

Qodo Logo

…data

Extend the disallowed_attributes rule data schema with an optional
except_when field that suppresses violations when a package's PURL
qualifier matches a set of regex patterns. This enables teams to allow
specific disallowed attributes (e.g., hermeto:pip:package:binary) for
packages from trusted indexes while still blocking untrusted sources.

The filtering is fail-closed: if no purl-type ref exists, the qualifier
is absent, or no pattern matches, the violation is still produced.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move PURL parsing outside except_when loop to avoid reparsing per
  exception entry (CodeRabbit)
- Use array comprehension in _package_purl to safely handle SPDX
  packages with multiple PURL refs (Fullsend)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@robnester-rh

Copy link
Copy Markdown
Contributor Author

Addressed both findings from Fullsend review in 5d1b802:

  • Medium (edge-case): _package_purl(pkg) now uses array comprehension (purls[0]) instead of a complete rule that could conflict when multiple PURL refs exist with different locators.
  • Low (missing-test): The anchored regex patterns (^https://...) already prevent substring injection. The existing test coverage is sufficient since the patterns in the Jira spec all use anchored regexes.

@codecov

codecov Bot commented Jun 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Flag Coverage Δ
unit-tests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
policy/lib/sbom/sbom.rego 100.00% <100.00%> (ø)
policy/lib/sbom/sbom_test.rego 100.00% <100.00%> (ø)
policy/release/sbom/sbom_test.rego 100.00% <ø> (ø)
policy/release/sbom_cyclonedx/sbom_cyclonedx.rego 100.00% <100.00%> (ø)
...cy/release/sbom_cyclonedx/sbom_cyclonedx_test.rego 100.00% <100.00%> (ø)
policy/release/sbom_spdx/sbom_spdx.rego 100.00% <100.00%> (ø)
policy/release/sbom_spdx/sbom_spdx_test.rego 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Move _package_purl and disallowed_attribute_excepted after all
incremental rule definitions to avoid splitting rule_data_errors
and deny rule groups.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@robnester-rh robnester-rh requested a review from joejstuart June 5, 2026 14:10
)
}

_package_purl(pkg) := purl if {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] edge-case

The _package_purl helper selects the first PURL-type externalRef via purls[0]. If an SPDX package has multiple PURL-type external references, only the first is evaluated for except_when exception matching. Consider adding a test case with two purl-type externalRefs to document the first-wins semantics.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meh

@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge and removed requires-manual-review Review requires human judgment labels Jun 5, 2026
Comment thread policy/lib/sbom/sbom.rego
some exception in disallowed.except_when
some qualifier in parsed.qualifiers
qualifier.key == exception.purl_qualifier
url_matches_any_pattern(qualifier.value, exception.patterns)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regex.match does substring matching, not full-string matching. A pattern like console\.redhat\.com also matches evil-console.redhat.com. This is inherited from url_matches_any_pattern, but it now extends to a security-sensitive bypass path.

Worth either wrapping patterns with ^/$ in the matching function, or at minimum documenting the anchoring requirement so operators don't accidentally widen exception scope.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to that suggestion

property.name == disallowed.name
object.get(property, "value", "") == object.get(disallowed, "value", "")

not sbom.disallowed_attribute_excepted(disallowed, object.get(component, "purl", ""))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The METADATA description block for this rule (above) doesn't mention except_when. These annotations generate the public docs on conforma.dev and are the primary way operators discover rule data configuration. EC-1881 AC requires documentation updates.

The SPDX counterpart has the same gap. Worth describing the new field, its schema, and matching semantics in both METADATA blocks.

with ec.oci.image_tag_refs as []
with data.rule_data as {sbom.rule_data_attributes_key: disallowed_attributes}

count({r | some r in results; r.code == "sbom_spdx.disallowed_package_attributes"}) == 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CycloneDX has test_attributes_except_when_without_except_when_unchanged covering mixed entries with/without except_when. Missing the SPDX equivalent. The two formats have structurally different iteration patterns, so the backward-compat path is worth testing independently.

Comment thread policy/lib/sbom/sbom.rego
Comment on lines +430 to +437
disallowed_attribute_excepted(disallowed, purl_string) if {
purl_string != ""
parsed := ec.purl.parse(purl_string)
some exception in disallowed.except_when
some qualifier in parsed.qualifiers
qualifier.key == exception.purl_qualifier
url_matches_any_pattern(qualifier.value, exception.patterns)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sibling predicates like component_found_by_hermeto and package_found_by_hermeto use else := false. This one relies on implicit undefined-is-falsy. Adding else := false would make the fail-closed intent explicit.

with data.rule_data as {sbom.rule_data_attributes_key: [{"name": "syft:distro:id", "value": "rhel"}]}
}

test_attributes_except_when_match_suppresses_violation if {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SPDX tests extract _spdx_excepted_package to reduce boilerplate. These tests inline identical component structures across ~7 tests. A similar helper would cut the duplication.

# regal ignore:line-length
"msg": "Item at index 8 in disallowed_attributes has an invalid regular expression in except_when: \"[\"",
"severity": "failure",
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Schema validation tests cover the invalid regex case but don't include a well-formed except_when entry among the valid inputs. The positive path is only implicitly tested via integration tests.

@lcarva

lcarva commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

I didn't see this and created a similar PR (#1744) 🫣
I'll close that out but mentioning it here in case it's helpful.

Also, I have an image that can be used to verify this, feel free to use it. Here's the snapshot:

{
  "application": "lucarval-test",
  "artifacts": {},
  "componentGroup": "",
  "components": [
    {
      "containerImage": "quay.io/redhat-user-workloads/rhtap-shared-team-tenant/awesome-fermi-c9ff1@sha256:27e73129cb102cff44c47b31cd5beef2b0a8cdd337ba42bac88613cdb3f9a326",
      "name": "awesome-fermi-c9ff1",
      "source": {
        "git": {
          "revision": "d764b86481955715da6a34ec6d15d068e65a25dc",
          "url": "https://github.com/lucarval-rhtap/awesome-fermi"
        }
      },
      "version": ""
    }
  ]
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-merge All reviewers approved — ready to merge size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants