From 803b22663703538580793b5fe53a9d7c67694e07 Mon Sep 17 00:00:00 2001 From: schapron Date: Wed, 7 Oct 2026 18:51:05 +0200 Subject: [PATCH 1/5] ci(secu): run security scans on Ubuntu 26.04 Move the reusable security workflows to the Ubuntu 26.04 runners: private centreon repositories use centreon-ubuntu-26.04-small (secret scan) and centreon-ubuntu-26.04-slim (dependency scan), public ones ubuntu-26.04. The quanta-computing runner (quanta-security) is kept. Refs: SECU-1249 Co-Authored-By: Claude Opus 5.5 (1M context) Assisted-by: Claude Code (claude-opus-5-5) --- .github/workflows/dependency-analysis.yml | 2 +- .github/workflows/gitleaks-analysis.yml | 2 +- CLAUDE.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dependency-analysis.yml b/.github/workflows/dependency-analysis.yml index a7c20ef2..91c270b2 100644 --- a/.github/workflows/dependency-analysis.yml +++ b/.github/workflows/dependency-analysis.yml @@ -11,7 +11,7 @@ permissions: jobs: dependency-scan: name: Run dependencies analysis - runs-on: ${{ github.repository_visibility != 'public' && (github.repository_owner == 'centreon' && 'centreon-security' || github.repository_owner == 'quanta-computing' && 'quanta-security') || 'ubuntu-24.04' }} + runs-on: ${{ github.repository_visibility != 'public' && (github.repository_owner == 'centreon' && 'centreon-ubuntu-26.04-slim' || github.repository_owner == 'quanta-computing' && 'quanta-security') || 'ubuntu-26.04' }} steps: - name: Checkout Repository diff --git a/.github/workflows/gitleaks-analysis.yml b/.github/workflows/gitleaks-analysis.yml index e4b7261a..560bf22f 100644 --- a/.github/workflows/gitleaks-analysis.yml +++ b/.github/workflows/gitleaks-analysis.yml @@ -13,7 +13,7 @@ env: jobs: secret-scan: name: Run Gitleaks - runs-on: ${{ github.repository_visibility != 'public' && (github.repository_owner == 'centreon' && 'centreon-security' || github.repository_owner == 'quanta-computing' && 'quanta-security') || 'ubuntu-24.04' }} + runs-on: ${{ github.repository_visibility != 'public' && (github.repository_owner == 'centreon' && 'centreon-ubuntu-26.04-small' || github.repository_owner == 'quanta-computing' && 'quanta-security') || 'ubuntu-26.04' }} permissions: contents: read diff --git a/CLAUDE.md b/CLAUDE.md index b06cb56a..fa6b1a93 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,7 +28,7 @@ Because consumers pin to `@main`, changes to workflows and the blocklist take ef Findings accumulate in `error_log.txt`, which is posted back to the PR as a sticky comment via `marocchino/sticky-pull-request-comment`. The job only fails if `fail_the_build=true` (set when enforcement is active and findings exist). -**Self-hosted runner selection**: jobs use a conditional `runs-on` that picks a per-org self-hosted runner only for **private** repos — `centreon-security` for the `centreon` org, `quanta-security` for the `quanta-computing` org — and falls back to `ubuntu-24.04` for public repos or any other owner. The same expression is used in both `dependency-analysis.yml` and `gitleaks-analysis.yml`; keep them in sync when editing. +**Self-hosted runner selection**: jobs use a conditional `runs-on` that picks a per-org self-hosted runner only for **private** repos — for the `centreon` org, `centreon-ubuntu-26.04-small` in `gitleaks-analysis.yml` and `centreon-ubuntu-26.04-slim` in `dependency-analysis.yml`; `quanta-security` for the `quanta-computing` org — and falls back to `ubuntu-26.04` for public repos or any other owner. Apart from the `centreon` runner, the expression is the same in both workflows; keep them in sync when editing. ### The blocklist (`blacklist/compromised-packages.txt`) From 00a0c63af9657decd04459aa6b3a423dcec6ee4c Mon Sep 17 00:00:00 2001 From: schapron Date: Thu, 8 Oct 2026 10:56:37 +0200 Subject: [PATCH 2/5] ci(secu): reuse the runner's gitleaks when pinned Some runners (centreon-ubuntu-26.04-small) already ship gitleaks. Keep it when `gitleaks version` reports the pinned GITLEAKS_VERSION, and install the pinned release, verified against GITLEAKS_SHA256, in every other case: gitleaks missing (e.g. the public ubuntu-26.04 runner) or any other version. Refs: SECU-1249 Co-Authored-By: Claude Opus 5.5 (1M context) Assisted-by: Claude Code (claude-opus-5-5) --- .github/workflows/gitleaks-analysis.yml | 10 ++++++++++ CLAUDE.md | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gitleaks-analysis.yml b/.github/workflows/gitleaks-analysis.yml index 560bf22f..3776e4b7 100644 --- a/.github/workflows/gitleaks-analysis.yml +++ b/.github/workflows/gitleaks-analysis.yml @@ -43,6 +43,16 @@ jobs: - name: Install Gitleaks run: | + # Some runners (e.g. centreon-ubuntu-26.04-small) ship gitleaks: keep + # it only when it is the pinned version, otherwise (missing, or any + # other version) install the pinned release. + CURRENT="$(gitleaks version 2>/dev/null || true)" + if [ "${CURRENT#v}" = "${GITLEAKS_VERSION}" ]; then + echo "gitleaks ${GITLEAKS_VERSION} is already installed: $(command -v gitleaks)." + exit 0 + fi + echo "Found gitleaks version '${CURRENT:-none}', expected ${GITLEAKS_VERSION}: installing it." + ASSET="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" BASE_URL="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}" diff --git a/CLAUDE.md b/CLAUDE.md index fa6b1a93..d48ad543 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,7 +20,7 @@ Because consumers pin to `@main`, changes to workflows and the blocklist take ef - `dependency-analysis.yml` — `workflow_call` + runs on PR. Two responsibilities: 1. **Lockfile compliance** — enforces PNPM only (rejects `yarn.lock` / `package-lock.json`), requires a lockfile, and enforces a minimum `lockfileVersion` (`8.9.9`). 2. **Blocklist scan** — downloads `compromised-packages.txt` from `main` and greps every `pnpm-lock.yaml` for `name@version` matches. -- `gitleaks-analysis.yml` — `workflow_call`, supporting `pull_request` and `workflow_dispatch` only (org repos call it on both; `workflow_dispatch` is mostly for debugging the pipeline). Installs the gitleaks release pinned by `GITLEAKS_VERSION` + `GITLEAKS_SHA256` (bump both together; Dependabot/Renovate don't). On a PR it scans `base..head`: every non-merge commit not already on the destination branch (whatever it is called, and whether or not the commits are on other branches), so it never fails on secrets already on the base and fails on a secret in one of the PR's commits. On `workflow_dispatch` it scans the ref's whole history. Pull requests are checked out shallow (`fetch-depth` = PR commits + 2): the scan deepens the base side until the range holds no parentless commit (a shallow boundary would be read as a root and its whole tree scanned). Event SHAs missing from the clone are re-fetched with the job token. Fails closed: leaks, gitleaks errors, unresolvable ranges and unsupported events (push, schedule, …) all fail the job. `pull_request_target` always fails (not compliant with the security posture), from a first step that runs before checkout. +- `gitleaks-analysis.yml` — `workflow_call`, supporting `pull_request` and `workflow_dispatch` only (org repos call it on both; `workflow_dispatch` is mostly for debugging the pipeline). Keeps the runner's own gitleaks when `gitleaks version` matches `GITLEAKS_VERSION` (e.g. on `centreon-ubuntu-26.04-small`), and otherwise installs that release, verified against `GITLEAKS_SHA256` (bump both together; Dependabot/Renovate don't). On a PR it scans `base..head`: every non-merge commit not already on the destination branch (whatever it is called, and whether or not the commits are on other branches), so it never fails on secrets already on the base and fails on a secret in one of the PR's commits. On `workflow_dispatch` it scans the ref's whole history. Pull requests are checked out shallow (`fetch-depth` = PR commits + 2): the scan deepens the base side until the range holds no parentless commit (a shallow boundary would be read as a root and its whole tree scanned). Event SHAs missing from the clone are re-fetched with the job token. Fails closed: leaks, gitleaks errors, unresolvable ranges and unsupported events (push, schedule, …) all fail the job. `pull_request_target` always fails (not compliant with the security posture), from a first step that runs before checkout. **Warn-then-enforce gating** (in `dependency-analysis.yml`): findings are reported as `[WARNING]` until a deadline, after which they become `[ERROR]` and fail the build. Controlled by two repository-level GitHub variables: - `OVERRIDE_DEPENDENCY_ENFORCEMENT_DATE` — the date enforcement begins. From 5a7cbe3699fc02079d03436b2d8d213e4720c4a2 Mon Sep 17 00:00:00 2001 From: schapron Date: Thu, 8 Oct 2026 11:03:01 +0200 Subject: [PATCH 3/5] ci(secu): resolve gitleaks version like the runners Use the rule of the centreon-ubuntu-26.04 runner images (centreon-images, runners/base-ubuntu-26.04/Dockerfile), so the private runner's preinstalled gitleaks is the expected one and gets reused: GITLEAKS_VERSION "latest" is the newest stable release published at least 4 days before the run, and a version can still be pinned. When the runner's gitleaks differs, the release for the runner's architecture is installed and verified against its checksums file. The GitHub API is called with the job token, passed through curl's config on stdin, to avoid the anonymous rate limit shared behind an egress IP. Refs: SECU-1249 Co-Authored-By: Claude Opus 5.5 (1M context) Assisted-by: Claude Code (claude-opus-5-5) --- .github/workflows/gitleaks-analysis.yml | 65 +++++++++++++++++-------- CLAUDE.md | 2 +- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/.github/workflows/gitleaks-analysis.yml b/.github/workflows/gitleaks-analysis.yml index 3776e4b7..41bbd0b7 100644 --- a/.github/workflows/gitleaks-analysis.yml +++ b/.github/workflows/gitleaks-analysis.yml @@ -4,11 +4,11 @@ on: workflow_call: env: - # Pinned gitleaks release. Bump both together: GITLEAKS_SHA256 is the sha256 - # of gitleaks__linux_x64.tar.gz, checked against the release's - # checksums file when bumping. - GITLEAKS_VERSION: "8.30.1" - GITLEAKS_SHA256: "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb" + # Same rule as the centreon-ubuntu-26.04 runner images (centreon-images, + # runners/base-ubuntu-26.04/Dockerfile): "latest" = newest stable release + # published at least 4 days before the run. Set a version (e.g. "8.30.1") + # to pin it. + GITLEAKS_VERSION: "latest" jobs: secret-scan: @@ -42,31 +42,58 @@ jobs: persist-credentials: false - name: Install Gitleaks + env: + API_TOKEN: ${{ github.token }} run: | - # Some runners (e.g. centreon-ubuntu-26.04-small) ship gitleaks: keep - # it only when it is the pinned version, otherwise (missing, or any - # other version) install the pinned release. + set -o pipefail + + case "${RUNNER_ARCH}" in + X64) ARCH=x64 ;; + ARM64) ARCH=arm64 ;; + *) echo "::error::Unsupported runner architecture '${RUNNER_ARCH}'."; exit 1 ;; + esac + + VERSION="${GITLEAKS_VERSION}" + if [ "${VERSION}" = "latest" ]; then + # The job token lifts the anonymous API rate limit, shared by every + # job behind the same egress IP. It goes through curl's config on + # stdin, never on its command line. + cutoff="$(date -u -d '-4 days' +%Y-%m-%dT%H:%M:%SZ)" + VERSION="$(printf 'header = "Authorization: Bearer %s"\n' "${API_TOKEN}" \ + | curl -fsSL --retry 3 -K - "https://api.github.com/repos/gitleaks/gitleaks/releases?per_page=30" \ + | jq -r --arg cutoff "${cutoff}" '.[] + | select((.draft or .prerelease | not) and .published_at <= $cutoff and (.tag_name | test("^v[0-9]+\\.[0-9]+\\.[0-9]+$"))) + | .tag_name | ltrimstr("v")' \ + | sort -V | tail -1)" || VERSION="" + if [ -z "${VERSION}" ]; then + echo "::error::Could not resolve the latest gitleaks release from the GitHub API." + exit 1 + fi + fi + unset API_TOKEN + + # Some runners (e.g. centreon-ubuntu-26.04-small) ship gitleaks, built + # with the same rule: keep it when it is the expected version, + # otherwise (missing, or any other version) install that release. CURRENT="$(gitleaks version 2>/dev/null || true)" - if [ "${CURRENT#v}" = "${GITLEAKS_VERSION}" ]; then - echo "gitleaks ${GITLEAKS_VERSION} is already installed: $(command -v gitleaks)." + if [ "${CURRENT#v}" = "${VERSION}" ]; then + echo "gitleaks ${VERSION} is already installed: $(command -v gitleaks)." exit 0 fi - echo "Found gitleaks version '${CURRENT:-none}', expected ${GITLEAKS_VERSION}: installing it." + echo "Found gitleaks version '${CURRENT:-none}', expected ${VERSION}: installing it." - ASSET="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" - BASE_URL="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}" + TARBALL="gitleaks_${VERSION}_linux_${ARCH}.tar.gz" + BASE_URL="https://github.com/gitleaks/gitleaks/releases/download/v${VERSION}" workdir="$(mktemp -d)" cd "$workdir" - curl -sSfL -o "${ASSET}" "${BASE_URL}/${ASSET}" - - # Verify against the pinned digest, not a checksums file served - # from the same release as the binary. - echo "${GITLEAKS_SHA256} ${ASSET}" | sha256sum -c - + curl -fsSL --retry 3 -o "${TARBALL}" "${BASE_URL}/${TARBALL}" + curl -fsSL --retry 3 "${BASE_URL}/gitleaks_${VERSION}_checksums.txt" \ + | grep " ${TARBALL}\$" | sha256sum -c - mkdir -p "$HOME/.local/bin" - tar -xzf "${ASSET}" -C "$HOME/.local/bin" gitleaks + tar -xzf "${TARBALL}" -C "$HOME/.local/bin" gitleaks echo "$HOME/.local/bin" >> "$GITHUB_PATH" "$HOME/.local/bin/gitleaks" version diff --git a/CLAUDE.md b/CLAUDE.md index d48ad543..4a264850 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,7 +20,7 @@ Because consumers pin to `@main`, changes to workflows and the blocklist take ef - `dependency-analysis.yml` — `workflow_call` + runs on PR. Two responsibilities: 1. **Lockfile compliance** — enforces PNPM only (rejects `yarn.lock` / `package-lock.json`), requires a lockfile, and enforces a minimum `lockfileVersion` (`8.9.9`). 2. **Blocklist scan** — downloads `compromised-packages.txt` from `main` and greps every `pnpm-lock.yaml` for `name@version` matches. -- `gitleaks-analysis.yml` — `workflow_call`, supporting `pull_request` and `workflow_dispatch` only (org repos call it on both; `workflow_dispatch` is mostly for debugging the pipeline). Keeps the runner's own gitleaks when `gitleaks version` matches `GITLEAKS_VERSION` (e.g. on `centreon-ubuntu-26.04-small`), and otherwise installs that release, verified against `GITLEAKS_SHA256` (bump both together; Dependabot/Renovate don't). On a PR it scans `base..head`: every non-merge commit not already on the destination branch (whatever it is called, and whether or not the commits are on other branches), so it never fails on secrets already on the base and fails on a secret in one of the PR's commits. On `workflow_dispatch` it scans the ref's whole history. Pull requests are checked out shallow (`fetch-depth` = PR commits + 2): the scan deepens the base side until the range holds no parentless commit (a shallow boundary would be read as a root and its whole tree scanned). Event SHAs missing from the clone are re-fetched with the job token. Fails closed: leaks, gitleaks errors, unresolvable ranges and unsupported events (push, schedule, …) all fail the job. `pull_request_target` always fails (not compliant with the security posture), from a first step that runs before checkout. +- `gitleaks-analysis.yml` — `workflow_call`, supporting `pull_request` and `workflow_dispatch` only (org repos call it on both; `workflow_dispatch` is mostly for debugging the pipeline). Resolves the gitleaks version like the `centreon-ubuntu-26.04` runner images (`centreon-images`, `runners/base-ubuntu-26.04/Dockerfile`): `GITLEAKS_VERSION: latest` is the newest stable release published at least 4 days before the run (GitHub API, called with the job token); set a version to pin it. Keeps the runner's own gitleaks when `gitleaks version` matches (e.g. on `centreon-ubuntu-26.04-small`), and otherwise installs that release for the runner's architecture, verified against the release's checksums file. On a PR it scans `base..head`: every non-merge commit not already on the destination branch (whatever it is called, and whether or not the commits are on other branches), so it never fails on secrets already on the base and fails on a secret in one of the PR's commits. On `workflow_dispatch` it scans the ref's whole history. Pull requests are checked out shallow (`fetch-depth` = PR commits + 2): the scan deepens the base side until the range holds no parentless commit (a shallow boundary would be read as a root and its whole tree scanned). Event SHAs missing from the clone are re-fetched with the job token. Fails closed: leaks, gitleaks errors, unresolvable ranges and unsupported events (push, schedule, …) all fail the job. `pull_request_target` always fails (not compliant with the security posture), from a first step that runs before checkout. **Warn-then-enforce gating** (in `dependency-analysis.yml`): findings are reported as `[WARNING]` until a deadline, after which they become `[ERROR]` and fail the build. Controlled by two repository-level GitHub variables: - `OVERRIDE_DEPENDENCY_ENFORCEMENT_DATE` — the date enforcement begins. From b81482c3c648090c40ab2710a8cf0c6f20dcd850 Mon Sep 17 00:00:00 2001 From: schapron Date: Thu, 8 Oct 2026 11:33:24 +0200 Subject: [PATCH 4/5] ci(secu): resolve gitleaks version without a token The latest-release lookup sent the job token to the GitHub API only to lift the anonymous rate limit. Read the public releases feed instead: it needs no token and no API quota, and its date is at or after the publication date, so the 4-day delay still holds. Drafts are not in the feed and only vX.Y.Z tags are kept. jq is no longer needed. Refs: SECU-1249 Co-Authored-By: Claude Opus 5.5 (1M context) Assisted-by: Claude Code (claude-opus-5-5) --- .github/workflows/gitleaks-analysis.yml | 26 +++++++++++++------------ CLAUDE.md | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.github/workflows/gitleaks-analysis.yml b/.github/workflows/gitleaks-analysis.yml index 41bbd0b7..8e940684 100644 --- a/.github/workflows/gitleaks-analysis.yml +++ b/.github/workflows/gitleaks-analysis.yml @@ -42,8 +42,6 @@ jobs: persist-credentials: false - name: Install Gitleaks - env: - API_TOKEN: ${{ github.token }} run: | set -o pipefail @@ -55,22 +53,26 @@ jobs: VERSION="${GITLEAKS_VERSION}" if [ "${VERSION}" = "latest" ]; then - # The job token lifts the anonymous API rate limit, shared by every - # job behind the same egress IP. It goes through curl's config on - # stdin, never on its command line. + # Read the public releases feed: no token, no API rate limit. Its + # date is at or after the publication date, so the + # 4-day delay is never shortened. Drafts are not in the feed, and + # only vX.Y.Z tags are kept, which leaves pre-releases out. cutoff="$(date -u -d '-4 days' +%Y-%m-%dT%H:%M:%SZ)" - VERSION="$(printf 'header = "Authorization: Bearer %s"\n' "${API_TOKEN}" \ - | curl -fsSL --retry 3 -K - "https://api.github.com/repos/gitleaks/gitleaks/releases?per_page=30" \ - | jq -r --arg cutoff "${cutoff}" '.[] - | select((.draft or .prerelease | not) and .published_at <= $cutoff and (.tag_name | test("^v[0-9]+\\.[0-9]+\\.[0-9]+$"))) - | .tag_name | ltrimstr("v")' \ + VERSION="$(curl -fsSL --retry 3 "https://github.com/gitleaks/gitleaks/releases.atom" \ + | python3 -I -c ' + import re, sys, xml.etree.ElementTree as ET + ns = {"a": "http://www.w3.org/2005/Atom"} + for entry in ET.parse(sys.stdin).getroot().findall("a:entry", ns): + tag = entry.findtext("a:id", "", ns).rsplit("/", 1)[-1] + if re.fullmatch(r"v[0-9]+\.[0-9]+\.[0-9]+", tag) and entry.findtext("a:updated", "", ns) <= sys.argv[1]: + print(tag[1:]) + ' "${cutoff}" \ | sort -V | tail -1)" || VERSION="" if [ -z "${VERSION}" ]; then - echo "::error::Could not resolve the latest gitleaks release from the GitHub API." + echo "::error::Could not resolve the latest gitleaks release from its releases feed." exit 1 fi fi - unset API_TOKEN # Some runners (e.g. centreon-ubuntu-26.04-small) ship gitleaks, built # with the same rule: keep it when it is the expected version, diff --git a/CLAUDE.md b/CLAUDE.md index 4a264850..c2d5f536 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,7 +20,7 @@ Because consumers pin to `@main`, changes to workflows and the blocklist take ef - `dependency-analysis.yml` — `workflow_call` + runs on PR. Two responsibilities: 1. **Lockfile compliance** — enforces PNPM only (rejects `yarn.lock` / `package-lock.json`), requires a lockfile, and enforces a minimum `lockfileVersion` (`8.9.9`). 2. **Blocklist scan** — downloads `compromised-packages.txt` from `main` and greps every `pnpm-lock.yaml` for `name@version` matches. -- `gitleaks-analysis.yml` — `workflow_call`, supporting `pull_request` and `workflow_dispatch` only (org repos call it on both; `workflow_dispatch` is mostly for debugging the pipeline). Resolves the gitleaks version like the `centreon-ubuntu-26.04` runner images (`centreon-images`, `runners/base-ubuntu-26.04/Dockerfile`): `GITLEAKS_VERSION: latest` is the newest stable release published at least 4 days before the run (GitHub API, called with the job token); set a version to pin it. Keeps the runner's own gitleaks when `gitleaks version` matches (e.g. on `centreon-ubuntu-26.04-small`), and otherwise installs that release for the runner's architecture, verified against the release's checksums file. On a PR it scans `base..head`: every non-merge commit not already on the destination branch (whatever it is called, and whether or not the commits are on other branches), so it never fails on secrets already on the base and fails on a secret in one of the PR's commits. On `workflow_dispatch` it scans the ref's whole history. Pull requests are checked out shallow (`fetch-depth` = PR commits + 2): the scan deepens the base side until the range holds no parentless commit (a shallow boundary would be read as a root and its whole tree scanned). Event SHAs missing from the clone are re-fetched with the job token. Fails closed: leaks, gitleaks errors, unresolvable ranges and unsupported events (push, schedule, …) all fail the job. `pull_request_target` always fails (not compliant with the security posture), from a first step that runs before checkout. +- `gitleaks-analysis.yml` — `workflow_call`, supporting `pull_request` and `workflow_dispatch` only (org repos call it on both; `workflow_dispatch` is mostly for debugging the pipeline). Resolves the gitleaks version like the `centreon-ubuntu-26.04` runner images (`centreon-images`, `runners/base-ubuntu-26.04/Dockerfile`): `GITLEAKS_VERSION: latest` is the newest stable release published at least 4 days before the run (read from the public releases feed, `releases.atom`, without any token); set a version to pin it. Keeps the runner's own gitleaks when `gitleaks version` matches (e.g. on `centreon-ubuntu-26.04-small`), and otherwise installs that release for the runner's architecture, verified against the release's checksums file. On a PR it scans `base..head`: every non-merge commit not already on the destination branch (whatever it is called, and whether or not the commits are on other branches), so it never fails on secrets already on the base and fails on a secret in one of the PR's commits. On `workflow_dispatch` it scans the ref's whole history. Pull requests are checked out shallow (`fetch-depth` = PR commits + 2): the scan deepens the base side until the range holds no parentless commit (a shallow boundary would be read as a root and its whole tree scanned). Event SHAs missing from the clone are re-fetched with the job token. Fails closed: leaks, gitleaks errors, unresolvable ranges and unsupported events (push, schedule, …) all fail the job. `pull_request_target` always fails (not compliant with the security posture), from a first step that runs before checkout. **Warn-then-enforce gating** (in `dependency-analysis.yml`): findings are reported as `[WARNING]` until a deadline, after which they become `[ERROR]` and fail the build. Controlled by two repository-level GitHub variables: - `OVERRIDE_DEPENDENCY_ENFORCEMENT_DATE` — the date enforcement begins. From 649ae07b36e265f9a4522ad936a0f88ca0c2e61f Mon Sep 17 00:00:00 2001 From: schapron Date: Thu, 8 Oct 2026 11:37:16 +0200 Subject: [PATCH 5/5] ci(secu): fall back to gitleaks 8.30.1 if needed An unreachable releases feed failed the job, blocking every pull request of the org. Install GITLEAKS_FALLBACK_VERSION (8.30.1) instead, still verified against its checksums file, and emit a gitleaks-version-fallback warning so the issue can be found and investigated. Refs: SECU-1249 Co-Authored-By: Claude Opus 5.5 (1M context) Assisted-by: Claude Code (claude-opus-5-5) --- .github/workflows/gitleaks-analysis.yml | 8 ++++++-- CLAUDE.md | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gitleaks-analysis.yml b/.github/workflows/gitleaks-analysis.yml index 8e940684..2b173352 100644 --- a/.github/workflows/gitleaks-analysis.yml +++ b/.github/workflows/gitleaks-analysis.yml @@ -9,6 +9,8 @@ env: # published at least 4 days before the run. Set a version (e.g. "8.30.1") # to pin it. GITLEAKS_VERSION: "latest" + # Installed when "latest" cannot be resolved, so the scan still runs. + GITLEAKS_FALLBACK_VERSION: "8.30.1" jobs: secret-scan: @@ -69,8 +71,10 @@ jobs: ' "${cutoff}" \ | sort -V | tail -1)" || VERSION="" if [ -z "${VERSION}" ]; then - echo "::error::Could not resolve the latest gitleaks release from its releases feed." - exit 1 + # Do not block the run: fall back to a known release, still + # verified against its checksums file below. + VERSION="${GITLEAKS_FALLBACK_VERSION}" + echo "::warning::gitleaks-version-fallback: could not resolve the latest gitleaks release from its releases feed; using ${VERSION}." fi fi diff --git a/CLAUDE.md b/CLAUDE.md index c2d5f536..b502afbe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,7 +20,7 @@ Because consumers pin to `@main`, changes to workflows and the blocklist take ef - `dependency-analysis.yml` — `workflow_call` + runs on PR. Two responsibilities: 1. **Lockfile compliance** — enforces PNPM only (rejects `yarn.lock` / `package-lock.json`), requires a lockfile, and enforces a minimum `lockfileVersion` (`8.9.9`). 2. **Blocklist scan** — downloads `compromised-packages.txt` from `main` and greps every `pnpm-lock.yaml` for `name@version` matches. -- `gitleaks-analysis.yml` — `workflow_call`, supporting `pull_request` and `workflow_dispatch` only (org repos call it on both; `workflow_dispatch` is mostly for debugging the pipeline). Resolves the gitleaks version like the `centreon-ubuntu-26.04` runner images (`centreon-images`, `runners/base-ubuntu-26.04/Dockerfile`): `GITLEAKS_VERSION: latest` is the newest stable release published at least 4 days before the run (read from the public releases feed, `releases.atom`, without any token); set a version to pin it. Keeps the runner's own gitleaks when `gitleaks version` matches (e.g. on `centreon-ubuntu-26.04-small`), and otherwise installs that release for the runner's architecture, verified against the release's checksums file. On a PR it scans `base..head`: every non-merge commit not already on the destination branch (whatever it is called, and whether or not the commits are on other branches), so it never fails on secrets already on the base and fails on a secret in one of the PR's commits. On `workflow_dispatch` it scans the ref's whole history. Pull requests are checked out shallow (`fetch-depth` = PR commits + 2): the scan deepens the base side until the range holds no parentless commit (a shallow boundary would be read as a root and its whole tree scanned). Event SHAs missing from the clone are re-fetched with the job token. Fails closed: leaks, gitleaks errors, unresolvable ranges and unsupported events (push, schedule, …) all fail the job. `pull_request_target` always fails (not compliant with the security posture), from a first step that runs before checkout. +- `gitleaks-analysis.yml` — `workflow_call`, supporting `pull_request` and `workflow_dispatch` only (org repos call it on both; `workflow_dispatch` is mostly for debugging the pipeline). Resolves the gitleaks version like the `centreon-ubuntu-26.04` runner images (`centreon-images`, `runners/base-ubuntu-26.04/Dockerfile`): `GITLEAKS_VERSION: latest` is the newest stable release published at least 4 days before the run (read from the public releases feed, `releases.atom`, without any token), falling back to `GITLEAKS_FALLBACK_VERSION` with a `gitleaks-version-fallback` warning when the feed cannot be resolved; set a version to pin it. Keeps the runner's own gitleaks when `gitleaks version` matches (e.g. on `centreon-ubuntu-26.04-small`), and otherwise installs that release for the runner's architecture, verified against the release's checksums file. On a PR it scans `base..head`: every non-merge commit not already on the destination branch (whatever it is called, and whether or not the commits are on other branches), so it never fails on secrets already on the base and fails on a secret in one of the PR's commits. On `workflow_dispatch` it scans the ref's whole history. Pull requests are checked out shallow (`fetch-depth` = PR commits + 2): the scan deepens the base side until the range holds no parentless commit (a shallow boundary would be read as a root and its whole tree scanned). Event SHAs missing from the clone are re-fetched with the job token. Fails closed: leaks, gitleaks errors, unresolvable ranges and unsupported events (push, schedule, …) all fail the job. `pull_request_target` always fails (not compliant with the security posture), from a first step that runs before checkout. **Warn-then-enforce gating** (in `dependency-analysis.yml`): findings are reported as `[WARNING]` until a deadline, after which they become `[ERROR]` and fail the build. Controlled by two repository-level GitHub variables: - `OVERRIDE_DEPENDENCY_ENFORCEMENT_DATE` — the date enforcement begins.