diff --git a/.github/scripts/split_sarif_runs.py b/.github/scripts/split_sarif_runs.py new file mode 100644 index 0000000..f8cc75a --- /dev/null +++ b/.github/scripts/split_sarif_runs.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""Split a multi-run SARIF log into one SARIF file per run. + +GitHub code scanning rejects SARIF uploads that contain multiple runs with the +same tool/category. This helper keeps shared metadata and writes each run to an +individual file with a deterministic, unique runAutomationDetails.id. +""" + +from __future__ import annotations + +import argparse +import json +import re +from copy import deepcopy +from pathlib import Path +from typing import Any + + +def slugify(value: str) -> str: + slug = re.sub(r"[^A-Za-z0-9_.-]+", "-", value.strip()).strip("-._") + return slug or "run" + + +def tool_name(run: dict[str, Any], index: int) -> str: + driver = run.get("tool", {}).get("driver", {}) + return str(driver.get("name") or driver.get("semanticVersion") or f"run-{index + 1}") + + +def split_sarif(input_path: Path, output_dir: Path, category_prefix: str) -> list[Path]: + sarif = json.loads(input_path.read_text(encoding="utf-8")) + runs = sarif.get("runs") + if not isinstance(runs, list) or not runs: + raise ValueError(f"{input_path} does not contain any SARIF runs") + + output_dir.mkdir(parents=True, exist_ok=True) + written: list[Path] = [] + + for index, run in enumerate(runs): + run_copy = deepcopy(run) + category = f"{category_prefix}/{index + 1}-{slugify(tool_name(run_copy, index))}" + run_copy["automationDetails"] = {**run_copy.get("automationDetails", {}), "id": category} + + output = {**sarif, "runs": [run_copy]} + destination = output_dir / f"{index + 1:03d}-{slugify(tool_name(run_copy, index))}.sarif" + destination.write_text(json.dumps(output, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + written.append(destination) + + return written + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("input", type=Path, help="SARIF file to split") + parser.add_argument("output_dir", type=Path, help="Directory where split SARIF files are written") + parser.add_argument("--category-prefix", default="codacy", help="Prefix for generated runAutomationDetails.id values") + args = parser.parse_args() + + written = split_sarif(args.input, args.output_dir, args.category_prefix) + for path in written: + print(path) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/codacy.yml b/.github/workflows/codacy.yml index 681c983..e26db67 100644 --- a/.github/workflows/codacy.yml +++ b/.github/workflows/codacy.yml @@ -54,8 +54,12 @@ jobs: # This will handover control about PR rejection to the GitHub side max-allowed-issues: 2147483647 - # Upload the SARIF file generated in the previous step + # Split Codacy's multi-run SARIF output so every upload has a unique category. + - name: Split SARIF runs + run: python .github/scripts/split_sarif_runs.py results.sarif sarif-results --category-prefix codacy + + # Upload the SARIF files generated in the previous step. - name: Upload SARIF results file uses: github/codeql-action/upload-sarif@v4 with: - sarif_file: results.sarif + sarif_file: sarif-results diff --git a/.github/workflows/ossar.yml b/.github/workflows/ossar.yml index 8c105d0..1a80e79 100644 --- a/.github/workflows/ossar.yml +++ b/.github/workflows/ossar.yml @@ -22,34 +22,30 @@ permissions: jobs: OSSAR-Scan: - # OSSAR runs on windows-latest. - # ubuntu-latest and macos-latest support coming soon + # OSSAR currently requires a Windows runner. Pin the image to avoid + # windows-latest migration surprises. permissions: contents: read # for actions/checkout to fetch code security-events: write # for github/codeql-action/upload-sarif to upload SARIF results actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status - runs-on: windows-latest + runs-on: windows-2022 steps: - name: Checkout repository uses: actions/checkout@v6 - # Ensure a compatible version of dotnet is installed. - # The [Microsoft Security Code Analysis CLI](https://aka.ms/mscadocs) is built with dotnet v3.1.201. - # A version greater than or equal to v3.1.201 of dotnet must be installed on the agent in order to run this action. - # GitHub hosted runners already have a compatible version of dotnet installed and this step may be skipped. - # For self-hosted runners, ensure dotnet version 3.1.201 or later is installed by including this action: - # - name: Install .NET - # uses: actions/setup-dotnet@v4 - # with: - # dotnet-version: '3.1.x' - - # Run open source static analysis tools + # Ensure a compatible version of .NET is installed for OSSAR/MSDO. + - name: Install .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '6.0.x' + + # Run open source static analysis tools. - name: Run OSSAR - uses: github/ossar-action@v2 + uses: github/ossar-action@v2.0.0 id: ossar - # Upload results to the Security tab + # Upload results to the Security tab. - name: Upload OSSAR results uses: github/codeql-action/upload-sarif@v4 with: