From 2edf2db0e2a040e0e8d8dcc1e7ec90042956e657 Mon Sep 17 00:00:00 2001 From: Daksh Pathak Date: Sun, 28 Jun 2026 20:58:26 +0530 Subject: [PATCH 1/4] ci: enforce Rego v1 policies with Regal --- .github/workflows/lint.yaml | 28 ++++++++++ .regal/config.yaml | 12 +++++ scripts/extract-rego.py | 103 ++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 .regal/config.yaml create mode 100644 scripts/extract-rego.py diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 5cc9af7..f0e5ed8 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -23,6 +23,34 @@ jobs: - name: Lint Rule Types run: go run github.com/mindersec/minder/cmd/dev@latest ruletype lint -r rule-types/github --skip-rego + - name: Install OPA + run: go install github.com/open-policy-agent/opa@v1.15.2 + + - name: Check embedded Rego formatting + run: python3 scripts/migrate-rego-v1.py --check . + + - name: Extract Rego policies + run: >- + python3 scripts/extract-rego.py + --output "${RUNNER_TEMP}/rego" + rule-types + security-baseline/rule-types + autofill-insights/rule-types + + # YAML block indentation uses spaces. Restore OPA's canonical tab + # indentation before asking Regal to enforce opa-fmt. + - name: Normalize extracted policies + run: find "${RUNNER_TEMP}/rego" -name '*.rego' -exec opa fmt --write {} + + + - name: Enforce Rego v1 + run: >- + go run github.com/open-policy-agent/regal@v0.41.1 lint + --disable-all + --enable use-rego-v1 + --enable opa-fmt + --config-file .regal/config.yaml + "${RUNNER_TEMP}/rego" + - name: Ensure rule type release_phase is set run: | # Directory containing YAML files diff --git a/.regal/config.yaml b/.regal/config.yaml new file mode 100644 index 0000000..bae5580 --- /dev/null +++ b/.regal/config.yaml @@ -0,0 +1,12 @@ +rules: + imports: + use-rego-v1: + level: error + style: + opa-fmt: + level: error + +capabilities: + from: + engine: opa + version: v0.68.0 diff --git a/scripts/extract-rego.py b/scripts/extract-rego.py new file mode 100644 index 0000000..4908824 --- /dev/null +++ b/scripts/extract-rego.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +"""Extract Rego policies embedded in YAML rule type definitions.""" + +from __future__ import annotations + +import argparse +import pathlib +import re + + +DEF_BLOCK_RE = re.compile(r"^(?P\s*)def:\s*\|[+-]?\s*$") + + +def yaml_files(paths: list[pathlib.Path]) -> list[pathlib.Path]: + files: list[pathlib.Path] = [] + for path in paths: + if path.is_dir(): + files.extend(path.rglob("*.yaml")) + files.extend(path.rglob("*.yml")) + elif path.suffix in {".yaml", ".yml"}: + files.append(path) + return sorted(set(files)) + + +def leading_spaces(line: str) -> int: + return len(line) - len(line.lstrip(" ")) + + +def extract_block(lines: list[str], start: int, parent_indent: int) -> tuple[str, int]: + block: list[str] = [] + current = start + while current < len(lines): + line = lines[current] + if line.strip() and leading_spaces(line) <= parent_indent: + break + block.append(line) + current += 1 + + nonblank_indents = [leading_spaces(line) for line in block if line.strip()] + if not nonblank_indents: + return "", current + + block_indent = min(nonblank_indents) + source = "".join( + line[block_indent:] if len(line) >= block_indent else "\n" + for line in block + ) + return source, current + + +def extract_policies(path: pathlib.Path) -> list[str]: + lines = path.read_text().splitlines(True) + policies: list[str] = [] + index = 0 + + while index < len(lines): + match = DEF_BLOCK_RE.match(lines[index]) + if not match: + index += 1 + continue + + source, index = extract_block( + lines, + index + 1, + len(match.group("indent")), + ) + if source.lstrip().startswith("package "): + policies.append(source) + + return policies + + +def output_name(path: pathlib.Path, policy_index: int) -> str: + normalized = "__".join(path.parts) + return f"{normalized}.{policy_index}.rego" + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Extract embedded Rego definitions into standalone files.", + ) + parser.add_argument("paths", nargs="+", type=pathlib.Path) + parser.add_argument("--output", required=True, type=pathlib.Path) + args = parser.parse_args() + + args.output.mkdir(parents=True, exist_ok=True) + + extracted = 0 + for path in yaml_files(args.paths): + for policy_index, source in enumerate(extract_policies(path)): + output = args.output / output_name(path, policy_index) + output.write_text(source) + extracted += 1 + + if extracted == 0: + parser.error("no embedded Rego policies found") + + print(f"Extracted {extracted} Rego policies to {args.output}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 7d806d32f2facc6e26f0309081acf48f8eed5955 Mon Sep 17 00:00:00 2001 From: Daksh Pathak Date: Mon, 29 Jun 2026 00:10:37 +0530 Subject: [PATCH 2/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .regal/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.regal/config.yaml b/.regal/config.yaml index bae5580..4eec013 100644 --- a/.regal/config.yaml +++ b/.regal/config.yaml @@ -9,4 +9,4 @@ rules: capabilities: from: engine: opa - version: v0.68.0 + version: v1.15.2 From 0c5b0eb560834378f5a347fc484a2ff366c3073c Mon Sep 17 00:00:00 2001 From: Daksh Pathak Date: Mon, 29 Jun 2026 00:35:56 +0530 Subject: [PATCH 3/4] fix: use supported Regal capabilities --- .regal/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.regal/config.yaml b/.regal/config.yaml index 4eec013..bae5580 100644 --- a/.regal/config.yaml +++ b/.regal/config.yaml @@ -9,4 +9,4 @@ rules: capabilities: from: engine: opa - version: v1.15.2 + version: v0.68.0 From 1cd15d4274707c68f9cabddb15d99501d2dc386c Mon Sep 17 00:00:00 2001 From: Daksh Pathak Date: Sun, 12 Jul 2026 00:40:41 +0530 Subject: [PATCH 4/4] fix: parse Rego definitions with PyYAML --- .github/workflows/lint.yaml | 3 ++ scripts/extract-rego.py | 58 +++++++------------------------------ 2 files changed, 13 insertions(+), 48 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index f0e5ed8..0ea80c4 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -29,6 +29,9 @@ jobs: - name: Check embedded Rego formatting run: python3 scripts/migrate-rego-v1.py --check . + - name: Install PyYAML + run: python3 -m pip install PyYAML==6.0.3 + - name: Extract Rego policies run: >- python3 scripts/extract-rego.py diff --git a/scripts/extract-rego.py b/scripts/extract-rego.py index 4908824..44a4ab3 100644 --- a/scripts/extract-rego.py +++ b/scripts/extract-rego.py @@ -5,10 +5,8 @@ import argparse import pathlib -import re - -DEF_BLOCK_RE = re.compile(r"^(?P\s*)def:\s*\|[+-]?\s*$") +import yaml def yaml_files(paths: list[pathlib.Path]) -> list[pathlib.Path]: @@ -19,55 +17,19 @@ def yaml_files(paths: list[pathlib.Path]) -> list[pathlib.Path]: files.extend(path.rglob("*.yml")) elif path.suffix in {".yaml", ".yml"}: files.append(path) - return sorted(set(files)) - - -def leading_spaces(line: str) -> int: - return len(line) - len(line.lstrip(" ")) - - -def extract_block(lines: list[str], start: int, parent_indent: int) -> tuple[str, int]: - block: list[str] = [] - current = start - while current < len(lines): - line = lines[current] - if line.strip() and leading_spaces(line) <= parent_indent: - break - block.append(line) - current += 1 - - nonblank_indents = [leading_spaces(line) for line in block if line.strip()] - if not nonblank_indents: - return "", current - - block_indent = min(nonblank_indents) - source = "".join( - line[block_indent:] if len(line) >= block_indent else "\n" - for line in block + test_suffixes = ( + ".test.yaml", + ".test.yml", + ".no-datasource-test.yaml", + ".no-datasource-test.yml", ) - return source, current + return sorted({path for path in files if not path.name.endswith(test_suffixes)}) def extract_policies(path: pathlib.Path) -> list[str]: - lines = path.read_text().splitlines(True) - policies: list[str] = [] - index = 0 - - while index < len(lines): - match = DEF_BLOCK_RE.match(lines[index]) - if not match: - index += 1 - continue - - source, index = extract_block( - lines, - index + 1, - len(match.group("indent")), - ) - if source.lstrip().startswith("package "): - policies.append(source) - - return policies + contents = yaml.safe_load(path.read_text()) or {} + rego = contents.get("def", {}).get("eval", {}).get("rego", {}).get("def", "") + return [rego] if rego else [] def output_name(path: pathlib.Path, policy_index: int) -> str: