Skip to content
Merged
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
23 changes: 23 additions & 0 deletions .github/config/regal-sigilix.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
rules:
imports:
use-rego-v1:
level: ignore
implicit-future-keywords:
level: ignore
import-after-rule:
level: ignore
prefer-package-imports:
level: ignore
redundant-alias:
level: ignore
redundant-data-import:
level: ignore
ignore:
files:
- "**/.git/**"
- "**/node_modules/**"
- "**/dist/**"
- "**/build/**"
- "**/coverage/**"
- "**/vendor/**"
- "**/.terraform/**"
5 changes: 5 additions & 0 deletions .github/config/tool-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@
"env": "AST_GREP_ENABLED",
"output": "ast-grep.sarif"
},
{
"id": "regal",
"env": "REGAL_ENABLED",
"output": "regal.sarif"
},
{
"id": "htmlhint",
"env": "HTMLHINT_ENABLED",
Expand Down
101 changes: 101 additions & 0 deletions .github/scripts/policy_iac_tools_workflow_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import json
import os
import re
import unittest


ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
WORKFLOW_PATH = os.path.join(ROOT, ".github", "workflows", "scan.yml")
MANIFEST_PATH = os.path.join(ROOT, ".github", "config", "tool-manifest.json")
REGAL_CONFIG_PATH = os.path.join(ROOT, ".github", "config", "regal-sigilix.yaml")
SCRIPT_DIR = os.path.join(ROOT, ".github", "scripts")


class PolicyIacToolsWorkflowTest(unittest.TestCase):
def read_file(self, path):
with open(path, encoding="utf-8") as handle:
return handle.read()

def workflow_text(self):
return self.read_file(WORKFLOW_PATH)

def workflow_input_block(self, input_name):
pattern = rf"\n {re.escape(input_name)}:\n(?P<block>(?: .+\n)+)"
match = re.search(pattern, self.workflow_text())
self.assertIsNotNone(match)
return match.group("block")

def workflow_step_block(self, step_name):
pattern = rf"(?ms)^ - name: {re.escape(step_name)}\n.+?(?=^ - name: |\Z)"
match = re.search(pattern, self.workflow_text())
self.assertIsNotNone(match)
return match.group(0)

def manifest_rows(self):
with open(MANIFEST_PATH, encoding="utf-8") as handle:
return {row["id"]: row for row in json.load(handle)["tools"]}

def script_text(self, filename):
return self.read_file(os.path.join(SCRIPT_DIR, filename))

def test_regal_is_default_on_and_manifested(self):
text = self.workflow_text()
rows = self.manifest_rows()

self.assertIn(" default: true\n", self.workflow_input_block("regal"))
self.assertIn("REGAL_ENABLED: ${{ inputs.regal }}", text)
self.assertIn('REGAL_VERSION: "0.41.1"', text)
self.assertIn("REGAL_LINUX_X86_64_SHA256:", text)
self.assertEqual(rows["regal"], {"id": "regal", "env": "REGAL_ENABLED", "output": "regal.sarif"})

def test_workflow_delegates_policy_iac_tools_to_runner_scripts(self):
expectations = {
"Run zizmor to SARIF": "run_zizmor.sh",
"Run Hadolint to SARIF": "run_hadolint.sh",
"Run Regal to SARIF": "run_regal.sh",
}

for step_name, script_name in expectations.items():
block = self.workflow_step_block(step_name)
self.assertIn(f'bash "$RUNNER_DIR/.github/scripts/{script_name}"', block)

def test_regal_wrapper_uses_high_confidence_sigilix_profile(self):
text = self.script_text("run_regal.sh")
config = self.read_file(REGAL_CONFIG_PATH)

self.assertIn("REGAL_VERSION", text)
self.assertIn("REGAL_LINUX_X86_64_SHA256", text)
self.assertIn("regal_Linux_x86_64", text)
self.assertIn("sha256sum -c --strict", text)
self.assertIn("Regal installed version mismatch", text)
self.assertIn("discover_rego_files", text)
self.assertIn("No Rego files found", text)
self.assertIn('regal_config="$RUNNER_DIR/.github/config/regal-sigilix.yaml"', text)
self.assertIn("--config-file \"$regal_config\"", text)
for category in ("idiomatic", "style", "performance", "testing", "custom"):
self.assertIn(f"--disable-category {category}", text)
self.assertIn("--format sarif", text)
self.assertIn("--output-file \"$raw\"", text)
self.assertIn("sigilix_sarif_contract.py", text)
self.assertIn("use-rego-v1:", config)
self.assertIn("level: ignore", config)
self.assertIn("node_modules", config)
self.assertIn(".terraform", config)

def test_zizmor_and_hadolint_wrappers_preserve_sarif_contracts(self):
zizmor = self.script_text("run_zizmor.sh")
hadolint = self.script_text("run_hadolint.sh")

self.assertIn("ZIZMOR_VERSION", zizmor)
self.assertIn("python3 -m pip install --quiet", zizmor)
self.assertIn("zizmor --format sarif .", zizmor)
self.assertIn("sigilix_sarif_contract.py", zizmor)
self.assertIn("HADOLINT_VERSION", hadolint)
self.assertIn("hadolint-linux-x86_64", hadolint)
self.assertIn("Dockerfile.*", hadolint)
self.assertIn("--no-fail --format sarif", hadolint)
self.assertIn("sigilix_sarif_contract.py", hadolint)


if __name__ == "__main__":
unittest.main()
47 changes: 47 additions & 0 deletions .github/scripts/run_hadolint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail

: "${HADOLINT_VERSION:?}"
: "${RESULT_CAP:?}"
: "${RUNNER_DIR:?}"
: "${RUNNER_TEMP:?}"
: "${SARIF_DIR:?}"
: "${SOURCE_DIR:?}"

SOURCE_DIR="$(cd "$SOURCE_DIR" && pwd -P)"
RUNNER_DIR="$(cd "$RUNNER_DIR" && pwd -P)"

raw="$SARIF_DIR/hadolint.raw.sarif"
out="$SARIF_DIR/hadolint.sarif"
files_list="$RUNNER_TEMP/hadolint-files"

mkdir -p "$SARIF_DIR" "$RUNNER_TEMP"

emit_empty_sarif() {
printf '{"version":"2.1.0","runs":[]}' > "$raw"
}

cd "$SOURCE_DIR"
if ! curl -fsSL -o "$RUNNER_TEMP/hadolint" \
"https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION}/hadolint-linux-x86_64"; then
echo "::warning::hadolint download failed - manifest will record missing output."
elif ! chmod +x "$RUNNER_TEMP/hadolint"; then
echo "::warning::hadolint chmod failed - manifest will record missing output."
elif ! find . -type f \( -name 'Dockerfile' -o -name 'Dockerfile.*' \) \
-not -path './.git/*' -not -path './node_modules/*' -print0 > "$files_list"; then
echo "::warning::hadolint file discovery failed - manifest will record missing output."
else
files=()
while IFS= read -r -d '' file; do
files+=("$file")
done < "$files_list"
if [ "${#files[@]}" -eq 0 ]; then
emit_empty_sarif
else
"$RUNNER_TEMP/hadolint" --no-fail --format sarif "${files[@]}" > "$raw" || true
if [ ! -s "$raw" ]; then emit_empty_sarif; fi
fi
python3 "$RUNNER_DIR/.github/scripts/sigilix_sarif_contract.py" \
hadolint "$raw" "$out" --cap "$RESULT_CAP" --ensure-run \
|| echo "::warning::hadolint SARIF normalization failed - manifest will record missing output."
fi
95 changes: 95 additions & 0 deletions .github/scripts/run_regal.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env bash
set -euo pipefail

: "${REGAL_LINUX_X86_64_SHA256:?}"
: "${REGAL_VERSION:?}"
: "${RESULT_CAP:?}"
: "${RUNNER_DIR:?}"
: "${RUNNER_TEMP:?}"
: "${SARIF_DIR:?}"
: "${SOURCE_DIR:?}"

SOURCE_DIR="$(cd "$SOURCE_DIR" && pwd -P)"
RUNNER_DIR="$(cd "$RUNNER_DIR" && pwd -P)"

raw="$SARIF_DIR/regal.raw.sarif"
out="$SARIF_DIR/regal.sarif"
regal_config="$RUNNER_DIR/.github/config/regal-sigilix.yaml"
regal_bin="$RUNNER_TEMP/regal"
files_list=""

mkdir -p "$SARIF_DIR" "$RUNNER_TEMP"
files_list="$(mktemp "$RUNNER_TEMP/regal-files.XXXXXX")"

cleanup_regal() {
rm -f "$files_list" "$regal_bin"
}
trap cleanup_regal EXIT

emit_empty_sarif() {
printf '{"version":"2.1.0","runs":[]}' > "$raw"
}

discover_rego_files() {
find -P . \
\( -type d \( -name '.git' -o -name 'node_modules' -o -name 'dist' -o -name 'build' \
-o -name 'coverage' -o -name 'vendor' -o -name '.terraform' \) -prune \) -o \
\( -type f -name '*.rego' -print0 \)
}

cd "$SOURCE_DIR"
if [ ! -f "$regal_config" ]; then
echo "::warning::Regal Sigilix config missing at $regal_config - emitting empty Regal SARIF run."
emit_empty_sarif
elif ! discover_rego_files > "$files_list"; then
echo "::warning::Regal file discovery failed - emitting empty Regal SARIF run."
emit_empty_sarif
elif ! grep -qz . "$files_list"; then
echo "::notice::No Rego files found - emitting empty Regal SARIF run."
emit_empty_sarif
elif [[ ! "$REGAL_VERSION" =~ ^[0-9]+[.][0-9]+[.][0-9]+$ ]]; then
echo "::warning::Regal version must be a pinned x.y.z version - emitting empty Regal SARIF run."
emit_empty_sarif
elif [[ ! "$REGAL_LINUX_X86_64_SHA256" =~ ^[0-9a-f]{64}$ ]]; then
echo "::warning::Regal checksum must be a pinned SHA256 value - emitting empty Regal SARIF run."
emit_empty_sarif
elif ! curl -fsSL -o "$regal_bin" \
"https://github.com/open-policy-agent/regal/releases/download/v${REGAL_VERSION}/regal_Linux_x86_64"; then
echo "::warning::Regal download failed - emitting empty Regal SARIF run."
emit_empty_sarif
elif ! printf '%s %s\n' "$REGAL_LINUX_X86_64_SHA256" "$regal_bin" | sha256sum -c --strict -; then
echo "::warning::Regal checksum mismatch - emitting empty Regal SARIF run."
emit_empty_sarif
elif ! chmod +x "$regal_bin"; then
echo "::warning::Regal chmod failed - emitting empty Regal SARIF run."
emit_empty_sarif
elif ! regal_version="$("$regal_bin" version 2>/dev/null)"; then
echo "::warning::Regal version check failed - emitting empty Regal SARIF run."
emit_empty_sarif
elif ! printf '%s\n' "$regal_version" | grep -q "^Version:[[:space:]]*${REGAL_VERSION}$"; then
echo "::warning::Regal installed version mismatch - emitting empty Regal SARIF run."
emit_empty_sarif
else
files=()
while IFS= read -r -d '' file; do
files+=("$file")
done < "$files_list"
"$regal_bin" lint \
--config-file "$regal_config" \
--disable-category idiomatic \
--disable-category style \
--disable-category performance \
--disable-category testing \
--disable-category custom \
--format sarif \
--output-file "$raw" \
-- \
"${files[@]}" || true
if [ ! -s "$raw" ]; then
echo "::warning::Regal scan produced no SARIF output - emitting empty Regal SARIF run."
emit_empty_sarif
fi
fi

python3 "$RUNNER_DIR/.github/scripts/sigilix_sarif_contract.py" \
regal "$raw" "$out" --cap "$RESULT_CAP" --ensure-run
36 changes: 36 additions & 0 deletions .github/scripts/run_zizmor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
set -euo pipefail

: "${RESULT_CAP:?}"
: "${RUNNER_DIR:?}"
: "${SARIF_DIR:?}"
: "${SOURCE_DIR:?}"
: "${ZIZMOR_VERSION:?}"

SOURCE_DIR="$(cd "$SOURCE_DIR" && pwd -P)"
RUNNER_DIR="$(cd "$RUNNER_DIR" && pwd -P)"

raw="$SARIF_DIR/zizmor.raw.sarif"
out="$SARIF_DIR/zizmor.sarif"

mkdir -p "$SARIF_DIR"

emit_empty_sarif() {
printf '{"version":"2.1.0","runs":[]}' > "$raw"
}

cd "$SOURCE_DIR"
if [ ! -d .github/workflows ]; then
emit_empty_sarif
elif ! python3 -m pip install --quiet "zizmor==${ZIZMOR_VERSION}"; then
echo "::warning::zizmor install failed - manifest will record missing output."
else
zizmor --format sarif . > "$raw" || true
if [ ! -s "$raw" ]; then emit_empty_sarif; fi
fi

if [ -s "$raw" ]; then
python3 "$RUNNER_DIR/.github/scripts/sigilix_sarif_contract.py" \
zizmor "$raw" "$out" --cap "$RESULT_CAP" --ensure-run \
|| echo "::warning::zizmor SARIF normalization failed - manifest will record missing output."
fi
2 changes: 2 additions & 0 deletions .github/scripts/sigilix_sarif_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"biome",
"oxlint",
"ast-grep",
"regal",
"htmlhint",
"stylelint",
"yamllint",
Expand Down Expand Up @@ -58,6 +59,7 @@
"biome": "Biome",
"oxlint": "Oxlint",
"ast-grep": "ast-grep",
"regal": "Regal",
"htmlhint": "HTMLHint",
"stylelint": "Stylelint",
"yamllint": "YAMLlint",
Expand Down
7 changes: 7 additions & 0 deletions .github/scripts/sigilix_sarif_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def test_contract_accepts_legacy_and_next_batch_tool_ids(self):
("biome", "Biome"),
("oxlint", "Oxlint"),
("ast-grep", "ast-grep"),
("regal", "Regal"),
):
run = attach_sigilix_metadata({}, tool_id)
driver = run["tool"]["driver"]
Expand Down Expand Up @@ -487,6 +488,10 @@ def test_contract_cli_attaches_metadata_for_new_native_language_tools(self):
"htmlhint": "htmlhint.sarif",
}

POLICY_TOOL_OUTPUTS = {
"regal": "regal.sarif",
}

LANGUAGE_CONVERTER_TOOL_OUTPUTS = {
"flake8": "flake8.sarif",
"stylelint": "stylelint.sarif",
Expand Down Expand Up @@ -516,6 +521,7 @@ def test_contract_cli_attaches_metadata_for_new_native_language_tools(self):
**OPT_IN_SECURITY_TOOL_OUTPUTS,
**TERRAFORM_TOOL_OUTPUTS,
**LANGUAGE_SARIF_TOOL_OUTPUTS,
**POLICY_TOOL_OUTPUTS,
**LANGUAGE_CONVERTER_TOOL_OUTPUTS,
**CONFIG_TOOL_OUTPUTS,
**CI_SECURITY_TOOL_OUTPUTS,
Expand Down Expand Up @@ -664,6 +670,7 @@ def test_catalog_tool_outputs_are_manifested_and_merged(self):
**OPT_IN_SECURITY_TOOL_OUTPUTS,
**TERRAFORM_TOOL_OUTPUTS,
**LANGUAGE_SARIF_TOOL_OUTPUTS,
**POLICY_TOOL_OUTPUTS,
**LANGUAGE_CONVERTER_TOOL_OUTPUTS,
**CONFIG_TOOL_OUTPUTS,
**CI_SECURITY_TOOL_OUTPUTS,
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jobs:
python3 .github/scripts/docs_config_tools_test.py
python3 .github/scripts/oxlint_workflow_test.py
python3 .github/scripts/ast_grep_workflow_test.py
python3 .github/scripts/policy_iac_tools_workflow_test.py
python3 -m py_compile .github/scripts/*.py

lint:
Expand Down
Loading
Loading