Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions antora/docs/modules/ROOT/pages/authoring.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ The https://conforma.dev/docs/cli/index.html[cli] is reponsible for gathering
the information to be validated which is made available to policies via the `input` object. Its
structure is defined https://conforma.dev/docs/cli/policy_input.html[here].

== Regex pattern anchoring

When writing regex patterns for rule data (e.g. `allowed_package_sources`, `allowed_external_references`,
`allowed_target_branch_patterns`, `disallowed_platform_patterns`), always anchor patterns with `^` and
where appropriate `$` to prevent unintended substring matches.

For example, use `^https://console\.redhat\.com/.*` instead of `console\.redhat\.com`.

Conforma will emit a warning when it detects unanchored patterns in rule data.

== Pitfalls

Today, EC takes the https://www.conftest.dev/[conftest] approach for asserting violations and
Expand Down
10 changes: 10 additions & 0 deletions antora/docs/modules/ROOT/pages/packages/release_git_branch.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,13 @@ Build must target a configured branch pattern (e.g., 'c10s')
* Code: `git_branch.git_branch`
* Effective from: `2025-07-01T00:00:00Z`
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/git_branch/git_branch.rego#L16[Source, window="_blank"]

[#git_branch__allowed_target_branch_patterns_format]
=== link:#git_branch__allowed_target_branch_patterns_format[allowed_target_branch_patterns format]

Confirm the `allowed_target_branch_patterns` rule data uses anchored regex patterns.

* Rule type: [rule-type-indicator failure]#FAILURE#
* FAILURE message: `%s`
* Code: `git_branch.allowed_target_branch_patterns_format`
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/git_branch/git_branch.rego#L36[Source, window="_blank"]
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,13 @@ Builds have valid download locations for RPM build dependencies
* WARNING message: `RPM build dependency source %s is not in the allowed list %v.`
* Code: `rpm_build_deps.download_location_valid`
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/rpm_build_deps/rpm_build_deps.rego#L15[Source, window="_blank"]

[#rpm_build_deps__allowed_rpm_build_dependency_sources_format]
=== link:#rpm_build_deps__allowed_rpm_build_dependency_sources_format[allowed_rpm_build_dependency_sources format]

Confirm the `allowed_rpm_build_dependency_sources` rule data uses anchored regex patterns.

* Rule type: [rule-type-indicator failure]#FAILURE#
* FAILURE message: `%s`
* Code: `rpm_build_deps.allowed_rpm_build_dependency_sources_format`
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/rpm_build_deps/rpm_build_deps.rego#L40[Source, window="_blank"]
4 changes: 4 additions & 0 deletions antora/docs/modules/ROOT/pages/release_policy.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,11 @@ Rules included:
* xref:packages/release_buildah_build_task.adoc#buildah_build_task__disallowed_platform_patterns_pattern[Buildah build task: disallowed_platform_patterns format]
* xref:packages/release_cve.adoc#cve__rule_data_provided[CVE checks: Rule data provided]
* xref:packages/release_external_parameters.adoc#external_parameters__pipeline_run_params_provided[External parameters: PipelineRun params provided]
* xref:packages/release_git_branch.adoc#git_branch__allowed_target_branch_patterns_format[Git branch checks: allowed_target_branch_patterns format]
* xref:packages/release_github_certificate.adoc#github_certificate__rule_data_provided[GitHub Certificate Checks: Rule data provided]
* xref:packages/release_labels.adoc#labels__rule_data_provided[Labels: Rule data provided]
* xref:packages/release_olm.adoc#olm__required_olm_features_annotations_provided[OLM: Required OLM feature annotations list provided]
* xref:packages/release_rpm_build_deps.adoc#rpm_build_deps__allowed_rpm_build_dependency_sources_format[RPM Build Dependencies: allowed_rpm_build_dependency_sources format]
* xref:packages/release_rpm_repos.adoc#rpm_repos__rule_data_provided[RPM Repos: Known repo id list provided]
* xref:packages/release_rpm_signature.adoc#rpm_signature__rule_data_provided[RPM Signature: Rule data provided]
* xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__allowed_package_external_references[SBOM CycloneDX: Allowed package external references]
Expand Down Expand Up @@ -276,9 +278,11 @@ Rules included:
* xref:packages/release_attestation_type.adoc#attestation_type__known_attestation_types_provided[Attestation type: Known attestation types provided]
* xref:packages/release_attestation_type.adoc#attestation_type__pipelinerun_attestation_found[Attestation type: PipelineRun attestation found]
* xref:packages/release_git_branch.adoc#git_branch__git_branch[Git branch checks: Builds have a trusted target branch]
* xref:packages/release_git_branch.adoc#git_branch__allowed_target_branch_patterns_format[Git branch checks: allowed_target_branch_patterns format]
* xref:packages/release_provenance_materials.adoc#provenance_materials__git_clone_source_matches_provenance[Provenance Materials: Git clone source matches materials provenance]
* xref:packages/release_provenance_materials.adoc#provenance_materials__git_clone_task_found[Provenance Materials: Git clone task found]
* xref:packages/release_rpm_build_deps.adoc#rpm_build_deps__download_location_valid[RPM Build Dependencies: Builds have valid download locations]
* xref:packages/release_rpm_build_deps.adoc#rpm_build_deps__allowed_rpm_build_dependency_sources_format[RPM Build Dependencies: allowed_rpm_build_dependency_sources format]
* xref:packages/release_rpm_pipeline.adoc#rpm_pipeline__invalid_pipeline[RPM Pipeline: Task version invalid_pipeline]
* xref:packages/release_rpm_repos.adoc#rpm_repos__ids_known[RPM Repos: All rpms have known repo ids]
* xref:packages/release_rpm_repos.adoc#rpm_repos__rule_data_provided[RPM Repos: Known repo id list provided]
Expand Down
2 changes: 2 additions & 0 deletions antora/docs/modules/ROOT/partials/release_policy_nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
**** xref:packages/release_external_parameters.adoc#external_parameters__restrict_shared_volumes[Restrict shared volumes]
*** xref:packages/release_git_branch.adoc[Git branch checks]
**** xref:packages/release_git_branch.adoc#git_branch__git_branch[Builds have a trusted target branch]
**** xref:packages/release_git_branch.adoc#git_branch__allowed_target_branch_patterns_format[allowed_target_branch_patterns format]
*** xref:packages/release_github_certificate.adoc[GitHub Certificate Checks]
**** xref:packages/release_github_certificate.adoc#github_certificate__gh_workflow_extensions[GitHub Workflow Certificate Extensions]
**** xref:packages/release_github_certificate.adoc#github_certificate__gh_workflow_name[GitHub Workflow Name]
Expand Down Expand Up @@ -93,6 +94,7 @@
**** xref:packages/release_rhtap_multi_ci.adoc#rhtap_multi_ci__attestation_found[SLSA Provenance Attestation Found]
*** xref:packages/release_rpm_build_deps.adoc[RPM Build Dependencies]
**** xref:packages/release_rpm_build_deps.adoc#rpm_build_deps__download_location_valid[Builds have valid download locations]
**** xref:packages/release_rpm_build_deps.adoc#rpm_build_deps__allowed_rpm_build_dependency_sources_format[allowed_rpm_build_dependency_sources format]
*** xref:packages/release_rpm_packages.adoc[RPM Packages]
**** xref:packages/release_rpm_packages.adoc#rpm_packages__unique_version[Unique Version]
*** xref:packages/release_rpm_pipeline.adoc[RPM Pipeline]
Expand Down
11 changes: 11 additions & 0 deletions policy/lib/rule_data/rule_data.rego
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,14 @@ get(key_name) := value if {
# If the key is not found, default to an empty list
value := []
}

# Returns warnings for patterns in the given rule data key that lack ^ anchoring.
anchoring_errors(key) := {error |
some pattern in get(key)
is_string(pattern)
not startswith(pattern, "^")
error := {
"message": sprintf("Pattern %q in %s is not anchored with ^", [pattern, key]),
"severity": "warning",
}
}
59 changes: 59 additions & 0 deletions policy/lib/sbom/sbom.rego
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,65 @@ rule_data_errors contains error if {
}
}

# Warn when external_references URL patterns lack ^ anchoring.

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] correctness

The anchoring check only verifies ^ at the start of the pattern. Users can silence the warning by prepending ^.* without improving security, since ^.*X is equivalent to unanchored X for substring matching. This is a known design trade-off — stricter heuristics would produce false positives for legitimate patterns.

rule_data_errors contains error if {
some key in {rule_data_allowed_external_references_key, rule_data_disallowed_external_references_key}
some index, ref in rule_data.get(key)
pattern := object.get(ref, "url", "")
pattern != ""
not startswith(pattern, "^")
error := {
"message": sprintf("Pattern %q at index %d in %s is not anchored with ^", [pattern, index, key]),
"severity": "warning",
}
}

# Warn when allowed_package_sources patterns lack ^ anchoring.
rule_data_errors contains error if {
some index, source in rule_data.get(rule_data_allowed_package_sources_key)
some pattern in source.patterns
is_string(pattern)
not startswith(pattern, "^")
error := {
"message": sprintf(
"Pattern %q at index %d in %s is not anchored with ^",
[pattern, index, rule_data_allowed_package_sources_key],
),
"severity": "warning",
}
}

# Warn when allowed_proxy_url_patterns lack ^ anchoring.
rule_data_errors contains error if {
some purl_type, patterns in rule_data.get("allowed_proxy_url_patterns")
some pattern in patterns
is_string(pattern)
not startswith(pattern, "^")
error := {
"message": sprintf(
"Pattern %q for PURL type %q in %s is not anchored with ^",
[pattern, purl_type, "allowed_proxy_url_patterns"],
),
"severity": "warning",
}
}

# Warn when disallowed_attributes except_when patterns lack ^ anchoring.
rule_data_errors contains error if {
some attr_index, attr in rule_data.get(rule_data_attributes_key)
some ew in object.get(attr, "except_when", [])
some pattern in ew.patterns
is_string(pattern)
not startswith(pattern, "^")
error := {
"message": sprintf(
"Pattern %q at index %d in %s except_when is not anchored with ^",
[pattern, attr_index, rule_data_attributes_key],
),
"severity": "warning",
}
}

# disallowed_attribute_excepted checks if the package's PURL has a qualifier
# matching an except_when condition, meaning the violation should be suppressed.
disallowed_attribute_excepted(disallowed, purl_string) if {
Expand Down
4 changes: 4 additions & 0 deletions policy/release/buildah_build_task/buildah_build_task.rego
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,8 @@ _rule_data_errors contains error if {
}
}

_rule_data_errors contains error 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] correctness

Anchoring check fires on non-string values (e.g., integer 1) because startswith is undefined for non-strings in Rego and not-undefined evaluates to true. This produces a confusing warning message alongside the existing type-error failure from schema validation.

Suggested fix: Add an is_string(r) guard before the not startswith(r, "^") check to avoid noisy duplicate warnings on already-invalid data.

some error in rule_data.anchoring_errors(_plat_patterns_rule_data_key)
}

_plat_patterns_rule_data_key := "disallowed_platform_patterns"
47 changes: 41 additions & 6 deletions policy/release/buildah_build_task/buildah_build_task_test.rego
Original file line number Diff line number Diff line change
Expand Up @@ -247,11 +247,20 @@ test_add_capabilities_param if {
}

test_platform_param_disallowed if {
# Test v1.0 attestation with disallowed platform pattern
expected := {{
"code": "buildah_build_task.platform_param",
"msg": "PLATFORM parameter value \"linux-root/arm64\" is disallowed by regex \".*root.*\"",
}}
_anchoring_warning := {
"code": "buildah_build_task.disallowed_platform_patterns_pattern",
# regal ignore:line-length
"msg": "Pattern \".*root.*\" in disallowed_platform_patterns is not anchored with ^",
"severity": "warning",
}

expected := {
{
"code": "buildah_build_task.platform_param",
"msg": "PLATFORM parameter value \"linux-root/arm64\" is disallowed by regex \".*root.*\"",
},
_anchoring_warning,
}

_task1_base := tekton_test.slsav1_task("buildah")
_task1_w_params = tekton_test.with_params(
Expand Down Expand Up @@ -297,7 +306,7 @@ test_platform_param_disallowed if {
assertions.assert_equal_results(expected, buildah_build_task.deny) with input.attestations as [tekton_test.slsav1_attestation([task1])]
with data.rule_data.disallowed_platform_patterns as [".*root.*"]

assertions.assert_empty(buildah_build_task.deny) with input.attestations as [tekton_test.slsav1_attestation([task2])]
assertions.assert_equal_results({_anchoring_warning}, buildah_build_task.deny) with input.attestations as [tekton_test.slsav1_attestation([task2])]
with data.rule_data.disallowed_platform_patterns as [".*root.*"]
}

Expand Down Expand Up @@ -334,11 +343,37 @@ test_plat_patterns_rule_data_validation if {
"msg": "\"(?=a)?b\" is not a valid regular expression in rego",
"severity": "failure",
},
{
"code": "buildah_build_task.disallowed_platform_patterns_pattern",
# regal ignore:line-length
"msg": "Pattern \".*foo\" in disallowed_platform_patterns is not anchored with ^",
"severity": "warning",
},
{
"code": "buildah_build_task.disallowed_platform_patterns_pattern",
# regal ignore:line-length
"msg": "Pattern \"(?=a)?b\" in disallowed_platform_patterns is not anchored with ^",
"severity": "warning",
},
}

assertions.assert_equal_results(buildah_build_task.deny, expected) with data.rule_data as d
}

test_plat_patterns_anchoring_warning if {
d_unanchored := {"disallowed_platform_patterns": ["linux/amd64"]}
expected_warn := {{
"code": "buildah_build_task.disallowed_platform_patterns_pattern",
# regal ignore:line-length
"msg": "Pattern \"linux/amd64\" in disallowed_platform_patterns is not anchored with ^",
"severity": "warning",
}}
assertions.assert_equal_results(buildah_build_task.deny, expected_warn) with data.rule_data as d_unanchored

d_anchored := {"disallowed_platform_patterns": ["^linux/amd64$"]}
assertions.assert_empty(buildah_build_task.deny) with data.rule_data as d_anchored
}

test_privileged_nested_param if {
expected := {{
"code": "buildah_build_task.privileged_nested_param",
Expand Down
20 changes: 20 additions & 0 deletions policy/release/git_branch/git_branch.rego
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,27 @@ deny contains result if {
result := metadata.result_helper(rego.metadata.chain(), [branch])
}

# METADATA
# title: allowed_target_branch_patterns format
# description: >-
# Confirm the `allowed_target_branch_patterns` rule data uses anchored regex patterns.
# custom:
# short_name: allowed_target_branch_patterns_format
# failure_msg: "%s"
# collections:
# - redhat_rpms
# - policy_data
#
deny contains result if {
some error in _rule_data_errors
result := metadata.result_helper_with_severity(rego.metadata.chain(), [error.message], error.severity)
}

matches_any(branch) if {
some pattern in rule_data.get("allowed_target_branch_patterns")
regex.match(pattern, branch)
}

_rule_data_errors contains error if {
some error in rule_data.anchoring_errors("allowed_target_branch_patterns")
}
16 changes: 15 additions & 1 deletion policy/release/git_branch/git_branch_test.rego
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ single_test_case(branch, expected_results) if {
"^rhel-10\\.[0-9]+$",
"^rhel-[0-9]+\\.[0-9]\\.0$",
"^rhel-[0-9]+-main$",
"branch[0-9]+-rhel-[0-9]+.[0-9]+.[0-9]+$",
"^.*branch[0-9]+-rhel-[0-9]+.[0-9]+.[0-9]+$",

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] correctness

Test mock data pattern changed from unanchored to ^.* prefix to suppress the new anchoring warning. While ^.* is semantically equivalent to no anchor (matches same strings), this is test infrastructure only with no production impact.

]

mock_tasks := mock_input.attestations[0].statement.predicate.buildConfig.tasks
Expand Down Expand Up @@ -271,3 +271,17 @@ test_hotfix_branch_with_extra_suffix_denied if {
}}
single_test_case("kernel-5.14.0-570.42.1.el9_6-branch1-rhel-9.6.0-extra", expected)
}

test_anchoring_warning_unanchored_pattern 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] test-integrity

Tests assert directly on git_branch.rule_data_errors instead of asserting through the deny interface. This masks the fact that rule_data_errors is not wired into deny and would never surface during policy evaluation.

expected := {{
"code": "git_branch.allowed_target_branch_patterns_format",
# regal ignore:line-length
"msg": "Pattern \"main\" in allowed_target_branch_patterns is not anchored with ^",
"severity": "warning",
}}
assertions.assert_equal_results(expected, git_branch.deny) with data.rule_data.allowed_target_branch_patterns as ["main"]
}

test_anchoring_warning_anchored_pattern if {
assertions.assert_empty(git_branch.deny) with data.rule_data.allowed_target_branch_patterns as ["^main$"]
}
20 changes: 20 additions & 0 deletions policy/release/rpm_build_deps/rpm_build_deps.rego
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,23 @@ matches_any(branch, valid_locations) if {
some pattern in valid_locations
regex.match(pattern, branch)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[high] correctness

The rule_data_errors rule is defined but never consumed by any deny or warn rule in this package. Warnings about unanchored patterns in allowed_rpm_build_dependency_sources will never surface during policy evaluation. Tests pass only because they directly assert on rule_data_errors, not through warn.

Suggested fix: Add a deny or warn rule with METADATA annotations that consumes rule_data_errors, consistent with how other packages wire up rule data validation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] style/conventions

Uses public rule_data_errors (no underscore prefix) contrary to codebase convention. All other packages use _rule_data_errors (private) consumed by a local deny rule.

Suggested fix: Rename to _rule_data_errors and add a local deny/warn consumer with METADATA annotations.

# METADATA
# title: allowed_rpm_build_dependency_sources format
# description: >-
# Confirm the `allowed_rpm_build_dependency_sources` rule data uses anchored regex patterns.
# custom:
# short_name: allowed_rpm_build_dependency_sources_format
# failure_msg: "%s"
# collections:
# - redhat_rpms
# - policy_data
#
deny contains result if {
some error in _rule_data_errors
result := metadata.result_helper_with_severity(rego.metadata.chain(), [error.message], error.severity)
}

_rule_data_errors contains error if {
some error in rule_data.anchoring_errors("allowed_rpm_build_dependency_sources")
}
16 changes: 16 additions & 0 deletions policy/release/rpm_build_deps/rpm_build_deps_test.rego
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,22 @@ test_matches_any_multiple_patterns if {
rpm_build_deps.matches_any("https://codeload.github.com/user/repo/tar.gz/v1.0", patterns)
}

test_anchoring_warning_unanchored_pattern 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] test-integrity

Tests assert directly on rpm_build_deps.rule_data_errors instead of asserting through the warn interface. This masks the fact that rule_data_errors is not wired into warn and would never surface during policy evaluation.

expected := {{
"code": "rpm_build_deps.allowed_rpm_build_dependency_sources_format",
# regal ignore:line-length
"msg": "Pattern \"download.example.com\" in allowed_rpm_build_dependency_sources is not anchored with ^",
"severity": "warning",
}}
assertions.assert_equal_results(expected, rpm_build_deps.deny) with input.attestations as [_sbom_attestation_with_download_location("NOASSERTION")]
with data.rule_data.allowed_rpm_build_dependency_sources as ["download.example.com"]
}

test_anchoring_warning_anchored_pattern if {
assertions.assert_empty(rpm_build_deps.deny) with input.attestations as [_sbom_attestation_with_download_location("NOASSERTION")]
with data.rule_data.allowed_rpm_build_dependency_sources as ["^https://download\\.example\\.com/.*"]
}

# Helper function to create SBOM attestation with specific download location
_sbom_attestation_with_download_location(location) := {"statement": {
"predicateType": "https://spdx.dev/Document",
Expand Down
Loading
Loading