From 9bb0b3f774f276e9e78ce3364fd283497a91f7b3 Mon Sep 17 00:00:00 2001 From: "Hel.Isa" Date: Sat, 2 May 2026 17:56:07 -0400 Subject: [PATCH] Bump version to v2.0.1 and update documentation for cross-repository compatibility - Made reusable-security-gate.yml self-contained for external consumers - Updated references in examples and documentation to v2.0.1 - Added changelog entry for patch release --- .github/workflows/reusable-security-gate.yml | 292 +++++++++++++++++-- CHANGELOG.md | 15 +- README.md | 6 +- VERSION | 2 +- docs/architecture.md | 10 +- docs/external-company-rollout.md | 14 +- docs/onboarding.md | 4 +- docs/release-management.md | 16 +- examples/security-gate-audit.yml | 4 +- examples/security-gate-strict.yml | 4 +- 10 files changed, 316 insertions(+), 51 deletions(-) diff --git a/.github/workflows/reusable-security-gate.yml b/.github/workflows/reusable-security-gate.yml index d361eee..59b007c 100644 --- a/.github/workflows/reusable-security-gate.yml +++ b/.github/workflows/reusable-security-gate.yml @@ -77,31 +77,222 @@ jobs: secrets: name: Secrets Detection needs: validate - uses: ./.github/workflows/reusable-secrets.yml - with: - fail_on_findings: ${{ needs.validate.outputs.fail_on_findings == 'true' }} - config_path: ${{ inputs.gitleaks_config_path }} + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Gitleaks + run: | + set -euo pipefail + GITLEAKS_VERSION="8.24.2" + curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" -o gitleaks.tar.gz + tar -xzf gitleaks.tar.gz gitleaks + sudo mv gitleaks /usr/local/bin/gitleaks + gitleaks version + + - name: Ensure Gitleaks config exists + run: | + set -euo pipefail + CONFIG_PATH="${{ inputs.gitleaks_config_path }}" + + if [ ! -f "$CONFIG_PATH" ]; then + echo "Config file not found at $CONFIG_PATH. Creating a default Gitleaks config." + mkdir -p "$(dirname "$CONFIG_PATH")" + + printf "%s\n" \ + "title = \"Default Gitleaks Config\"" \ + "" \ + "[extend]" \ + "useDefault = true" \ + "" \ + "[allowlist]" \ + "description = \"Global allowlist\"" \ + "paths = [" \ + " '''(^|/)node_modules(/|$)'''," \ + " '''(^|/)dist(/|$)'''," \ + " '''(^|/)build(/|$)'''," \ + " '''(^|/)\\.git(/|$)'''," \ + " '''(^|/)reports(/|$)'''" \ + "]" > "$CONFIG_PATH" + else + echo "Using existing Gitleaks config at $CONFIG_PATH" + fi + + - name: Run Gitleaks + id: gitleaks + run: | + set -euo pipefail + mkdir -p reports + + set +e + gitleaks detect \ + --source . \ + --report-format json \ + --report-path reports/gitleaks-report.json \ + --config "${{ inputs.gitleaks_config_path }}" + EXIT_CODE=$? + set -e + + if [ ! -f reports/gitleaks-report.json ]; then + echo '[]' > reports/gitleaks-report.json + fi + + FINDINGS_COUNT=$(jq 'if type=="array" then length else 0 end' reports/gitleaks-report.json 2>/dev/null || echo 0) + echo "findings_count=${FINDINGS_COUNT}" >> "$GITHUB_OUTPUT" + + if [ "${{ needs.validate.outputs.fail_on_findings }}" = "true" ] && [ "$FINDINGS_COUNT" -gt 0 ]; then + echo "Gitleaks found ${FINDINGS_COUNT} potential secrets." + exit 1 + fi + + if [ "$EXIT_CODE" -ne 0 ] && [ "$FINDINGS_COUNT" -eq 0 ]; then + echo "Gitleaks exited with code $EXIT_CODE but no findings were parsed." + exit "$EXIT_CODE" + fi + + - name: Upload Gitleaks report + if: always() + uses: actions/upload-artifact@v4 + with: + name: gitleaks-report + path: reports/gitleaks-report.json + if-no-files-found: error sast: name: SAST Scan needs: validate - uses: ./.github/workflows/reusable-sast.yml - with: - fail_on_findings: ${{ needs.validate.outputs.fail_on_findings == 'true' }} - config: ${{ inputs.semgrep_config }} + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install Semgrep + run: pip install --disable-pip-version-check semgrep + + - name: Run Semgrep + run: | + mkdir -p reports + semgrep scan --config "${{ inputs.semgrep_config }}" --json --output reports/semgrep-report.json . || true + + - name: Evaluate Semgrep findings + run: | + if [ ! -f reports/semgrep-report.json ]; then + echo '{"results": []}' > reports/semgrep-report.json + fi + FINDINGS_COUNT=$(python - <<'PY' + import json + from pathlib import Path + path = Path('reports/semgrep-report.json') + try: + data = json.loads(path.read_text()) + print(len(data.get('results', [])) if isinstance(data, dict) else 0) + except Exception: + print(0) + PY + ) + echo "Semgrep findings: ${FINDINGS_COUNT}" + if [ "${{ needs.validate.outputs.fail_on_findings }}" = "true" ] && [ "$FINDINGS_COUNT" -gt 0 ]; then + exit 1 + fi + + - name: Upload Semgrep report + if: always() + uses: actions/upload-artifact@v4 + with: + name: semgrep-report + path: reports/semgrep-report.json + if-no-files-found: error sca: name: SCA Scan needs: validate - uses: ./.github/workflows/reusable-sca.yml - with: - fail_on_findings: ${{ needs.validate.outputs.fail_on_findings == 'true' }} + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.25.7' + + - name: Install OSV-Scanner + run: | + set -euo pipefail + go install github.com/google/osv-scanner/v2/cmd/osv-scanner@v2.0.2 + echo "$HOME/go/bin" >> "$GITHUB_PATH" + + - name: Check OSV-Scanner + run: osv-scanner --version + + - name: Run OSV-Scanner + run: | + set -euo pipefail + mkdir -p reports + osv-scanner scan source --recursive ./ --format json --output-file reports/osv-report.json || true + + - name: Evaluate OSV findings + run: | + if [ ! -f reports/osv-report.json ]; then + echo '{"results": []}' > reports/osv-report.json + fi + FINDINGS_COUNT=$(python - <<'PY' + import json + from pathlib import Path + path = Path('reports/osv-report.json') + try: + data = json.loads(path.read_text()) + count = 0 + for result in data.get('results', []): + count += len(result.get('packages', []) or []) + count += len(result.get('vulnerabilities', []) or []) + print(count) + except Exception: + print(0) + PY + ) + echo "OSV findings: ${FINDINGS_COUNT}" + if [ "${{ needs.validate.outputs.fail_on_findings }}" = "true" ] && [ "$FINDINGS_COUNT" -gt 0 ]; then + exit 1 + fi + + - name: Upload OSV report + if: always() + uses: actions/upload-artifact@v4 + with: + name: osv-report + path: reports/osv-report.json + if-no-files-found: error sbom: name: SBOM Generation - uses: ./.github/workflows/reusable-sbom.yml - with: - scan_path: ${{ inputs.sbom_scan_path }} + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Syft + run: | + curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin + syft version + + - name: Generate CycloneDX SBOM + run: | + mkdir -p reports + syft "${{ inputs.sbom_scan_path }}" -o cyclonedx-json=reports/sbom-cyclonedx.json + + - name: Upload SBOM artifact + uses: actions/upload-artifact@v4 + with: + name: sbom-report + path: reports/sbom-cyclonedx.json + if-no-files-found: error dashboard: name: Security Dashboard @@ -111,9 +302,70 @@ jobs: - sca - sbom if: always() - uses: ./.github/workflows/reusable-dashboard.yml - with: - repo_name: ${{ inputs.repo_name }} - deploy_pages: ${{ inputs.deploy_pages }} - gate_repository: ${{ inputs.gate_repository }} - gate_ref: ${{ inputs.gate_ref }} + runs-on: ubuntu-latest + steps: + - name: Checkout Security Gate assets + uses: actions/checkout@v4 + with: + repository: ${{ inputs.gate_repository }} + ref: ${{ inputs.gate_ref }} + path: security-gate + + - name: Download security artifacts + uses: actions/download-artifact@v4 + with: + path: reports/raw + pattern: '*-report' + merge-multiple: true + + - name: Aggregate findings + run: | + set -euo pipefail + mkdir -p reports/normalized + python security-gate/scripts/aggregate_results.py \ + --input-dir reports/raw \ + --output-file reports/normalized/dashboard-data.json \ + --repo-name "${{ inputs.repo_name || github.repository }}" + + - name: Build static dashboard package + run: | + set -euo pipefail + python security-gate/scripts/generate_dashboard.py \ + --data-file reports/normalized/dashboard-data.json \ + --dashboard-dir security-gate/dashboard \ + --output-dir reports/dashboard + + - name: Verify dashboard output + run: | + set -euo pipefail + test -f reports/dashboard/index.html + ls -R reports/dashboard + + - name: Upload dashboard artifact + uses: actions/upload-artifact@v4 + with: + name: security-dashboard + path: reports/dashboard + if-no-files-found: error + + - name: Upload Pages artifact + if: inputs.deploy_pages + uses: actions/upload-pages-artifact@v3 + with: + path: reports/dashboard + + deploy-pages: + name: Deploy Security Dashboard + needs: dashboard + if: inputs.deploy_pages + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Configure Pages + uses: actions/configure-pages@v5 + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index bff337d..4019b98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## v2.0.1 - Cross-Repository Reusable Workflow Fix + +Patch release for approved repositories that call Security Gate from another repository or organization. + +### Fixed + +- Made `.github/workflows/reusable-security-gate.yml` self-contained so external consumers do not need local copies of lower-level reusable workflows. +- Prevented GitHub from resolving nested `./.github/workflows/reusable-*.yml` references against the caller repository. + +### Release Notes + +Approved consumers should pin both the reusable workflow reference and `gate_ref` to `v2.0.1`. + ## v2.0.0 - Initial Product Release Stable release for approved repositories that need a reusable Security Gate workflow. @@ -14,4 +27,4 @@ Stable release for approved repositories that need a reusable Security Gate work ### Release Notes -Approved consumers should pin both the reusable workflow reference and `gate_ref` to `v2.0.0`. +`v2.0.0` should be replaced with `v2.0.1` for external consumers because the product workflow in `v2.0.0` used nested relative reusable workflows. diff --git a/README.md b/README.md index d1a969b..e297e79 100644 --- a/README.md +++ b/README.md @@ -39,12 +39,12 @@ Start in audit mode: jobs: security-gate: name: Security Gate - uses: hel-isa/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.0 + uses: hel-isa/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.1 with: mode: audit semgrep_config: auto repo_name: ${{ github.repository }} - gate_ref: v2.0.0 + gate_ref: v2.0.1 ``` Switch to strict mode when the team is ready to block on findings: @@ -97,7 +97,7 @@ Advanced teams can call individual reusable workflows directly when they need cu ## Stable Releases -Approved consumers should pin to immutable release tags such as `v2.0.0`. Keep the workflow reference and `gate_ref` aligned so the reusable workflow and dashboard assets come from the same release. +Approved consumers should pin to immutable release tags such as `v2.0.1`. Keep the workflow reference and `gate_ref` aligned so the reusable workflow and dashboard assets come from the same release. ## Local Usage (where applicable) - Aggregation script: diff --git a/VERSION b/VERSION index 227cea2..38f77a6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.0 +2.0.1 diff --git a/docs/architecture.md b/docs/architecture.md index f25cf69..4c20c1e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -12,7 +12,7 @@ Version 2 is built around a **product-level reusable GitHub Actions workflow** b - `reusable-sbom.yml`: Syft SBOM generation in CycloneDX JSON. - `reusable-dashboard.yml`: report aggregation and static dashboard creation. -Most repositories should call `reusable-security-gate.yml`. Each lower-level control is still callable through `workflow_call`, enabling advanced repositories to consume any subset of controls. +Most repositories should call `reusable-security-gate.yml`. The product entry workflow is self-contained so cross-repository consumers do not need local copies of the lower-level workflows. Each lower-level control is still callable through `workflow_call`, enabling advanced repositories to consume any subset of controls when the workflow files are available to the caller. ## Product Presets @@ -31,14 +31,14 @@ This keeps the default path language-agnostic while preserving escape hatches fo - `semgrep-report` - `osv-report` - `sbom-report` -3. Dashboard workflow downloads `*-report` artifacts. -4. Dashboard workflow checks out the Security Gate repository assets through `gate_repository` and `gate_ref`. -5. Dashboard workflow normalizes findings with Python and packages a static dashboard artifact. +3. The dashboard job downloads `*-report` artifacts. +4. The dashboard job checks out the Security Gate repository assets through `gate_repository` and `gate_ref`. +5. The dashboard job normalizes findings with Python and packages a static dashboard artifact. ## Approved Repository Flow 1. A repository owner copies an example workflow into `.github/workflows/security-gate.yml`. -2. The workflow calls `hel-isa/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.0`. +2. The workflow calls `hel-isa/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.1`. 3. The first run should use audit mode to establish a baseline. 4. The repository can switch to strict mode when findings are understood and enforcement is accepted. diff --git a/docs/external-company-rollout.md b/docs/external-company-rollout.md index 2743303..d3b2064 100644 --- a/docs/external-company-rollout.md +++ b/docs/external-company-rollout.md @@ -16,7 +16,7 @@ For another company, the most practical private model is a fork or mirror inside ## Prerequisites -1. Create and push a stable release tag in the Security Gate repo, for example `v2.0.0`. +1. Create and push a stable release tag in the Security Gate repo, for example `v2.0.1`. 2. Confirm the target repository allows GitHub Actions and reusable workflows. 3. Start with `deploy_pages: false` to avoid GitHub Pages environment protection issues. 4. Start with `mode: audit` so the first run reports findings without blocking the team. @@ -45,13 +45,13 @@ permissions: jobs: security-gate: name: Security Gate - uses: hel-isa/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.0 + uses: hel-isa/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.1 with: mode: audit semgrep_config: auto repo_name: ${{ github.repository }} gate_repository: hel-isa/security-gate - gate_ref: v2.0.0 + gate_ref: v2.0.1 deploy_pages: false ``` @@ -60,7 +60,7 @@ jobs: This is the recommended private cross-company setup. 1. Fork or mirror this repository into the company's GitHub organization. -2. Create the same stable release tag in that fork or mirror, for example `v2.0.0`. +2. Create the same stable release tag in that fork or mirror, for example `v2.0.1`. 3. Use the company-owned repository in both `uses` and `gate_repository`. ```yaml @@ -81,13 +81,13 @@ permissions: jobs: security-gate: name: Security Gate - uses: COMPANY-ORG/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.0 + uses: COMPANY-ORG/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.1 with: mode: audit semgrep_config: auto repo_name: ${{ github.repository }} gate_repository: COMPANY-ORG/security-gate - gate_ref: v2.0.0 + gate_ref: v2.0.1 deploy_pages: false ``` @@ -107,7 +107,7 @@ jobs: Check that: - the Security Gate repo is public or accessible to the caller -- the tag exists, for example `v2.0.0` +- the tag exists, for example `v2.0.1` - the workflow path is exactly `.github/workflows/reusable-security-gate.yml` ### Dashboard asset checkout fails diff --git a/docs/onboarding.md b/docs/onboarding.md index 23a4cc0..77f3bc1 100644 --- a/docs/onboarding.md +++ b/docs/onboarding.md @@ -44,12 +44,12 @@ permissions: jobs: security-gate: name: Security Gate - uses: hel-isa/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.0 + uses: hel-isa/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.1 with: mode: audit semgrep_config: auto repo_name: ${{ github.repository }} - gate_ref: v2.0.0 + gate_ref: v2.0.1 deploy_pages: false ``` diff --git a/docs/release-management.md b/docs/release-management.md index 211e24a..ed1157b 100644 --- a/docs/release-management.md +++ b/docs/release-management.md @@ -6,8 +6,8 @@ Security Gate consumers should pin to stable release tags. This keeps approved r Use semantic version tags for immutable releases: -- `v2.0.0`: exact release, recommended for approved consumers -- `v2.0.1`: patch release for compatible fixes +- `v2.0.1`: exact release, recommended for approved consumers +- `v2.0.2`: patch release for compatible fixes - `v2.1.0`: minor release for compatible features or new optional inputs - `v3.0.0`: breaking release @@ -21,18 +21,18 @@ Approved repositories should pin the reusable workflow and dashboard assets to t jobs: security-gate: name: Security Gate - uses: hel-isa/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.0 + uses: hel-isa/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.1 with: mode: audit semgrep_config: auto repo_name: ${{ github.repository }} - gate_ref: v2.0.0 + gate_ref: v2.0.1 ``` Keep these aligned: -- workflow reference: `@v2.0.0` -- dashboard/script reference: `gate_ref: v2.0.0` +- workflow reference: `@v2.0.1` +- dashboard/script reference: `gate_ref: v2.0.1` ## Release Checklist @@ -49,14 +49,14 @@ Keep these aligned: 5. Create an annotated tag: ```bash - git tag -a v2.0.0 -m "Release v2.0.0" + git tag -a v2.0.1 -m "Release v2.0.1" ``` 6. Push the branch and tag: ```bash git push origin v2 - git push origin v2.0.0 + git push origin v2.0.1 ``` 7. Ask one approved repository to run the audit template before announcing the release more broadly. diff --git a/examples/security-gate-audit.yml b/examples/security-gate-audit.yml index 729bf09..babb21d 100644 --- a/examples/security-gate-audit.yml +++ b/examples/security-gate-audit.yml @@ -15,10 +15,10 @@ permissions: jobs: security-gate: name: Security Gate - uses: hel-isa/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.0 + uses: hel-isa/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.1 with: mode: audit semgrep_config: auto repo_name: ${{ github.repository }} - gate_ref: v2.0.0 + gate_ref: v2.0.1 deploy_pages: false diff --git a/examples/security-gate-strict.yml b/examples/security-gate-strict.yml index 70a3f3e..2610a0d 100644 --- a/examples/security-gate-strict.yml +++ b/examples/security-gate-strict.yml @@ -15,10 +15,10 @@ permissions: jobs: security-gate: name: Security Gate - uses: hel-isa/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.0 + uses: hel-isa/security-gate/.github/workflows/reusable-security-gate.yml@v2.0.1 with: mode: strict semgrep_config: auto repo_name: ${{ github.repository }} - gate_ref: v2.0.0 + gate_ref: v2.0.1 deploy_pages: false