From 635ff99c1a16c1eb781b732788456f4789db9689 Mon Sep 17 00:00:00 2001 From: Christopher Bartz Date: Fri, 2 Oct 2026 11:54:40 +0000 Subject: [PATCH 1/9] ci: add secscan vulnerability scanning Scan runner tarballs with Canonical secscan before each release and weekly (SEC0025, ISD-6336). --- .github/secscan-exclusions/linux-arm.txt | 3 + .github/secscan-exclusions/linux-arm64.txt | 3 + .github/secscan-exclusions/linux-ppc64le.txt | 3 + .github/secscan-exclusions/linux-s390x.txt | 3 + .github/secscan-exclusions/linux-x64.txt | 3 + .github/workflows/release.yml | 10 +- .github/workflows/secscan.yaml | 125 +++++++++++++++++++ SECURITY.md | 14 +++ 8 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 .github/secscan-exclusions/linux-arm.txt create mode 100644 .github/secscan-exclusions/linux-arm64.txt create mode 100644 .github/secscan-exclusions/linux-ppc64le.txt create mode 100644 .github/secscan-exclusions/linux-s390x.txt create mode 100644 .github/secscan-exclusions/linux-x64.txt create mode 100644 .github/workflows/secscan.yaml diff --git a/.github/secscan-exclusions/linux-arm.txt b/.github/secscan-exclusions/linux-arm.txt new file mode 100644 index 00000000000..4386d831989 --- /dev/null +++ b/.github/secscan-exclusions/linux-arm.txt @@ -0,0 +1,3 @@ +# CVEs excluded from the secscan gate for this artifact, one per line. +# Every entry needs a justification comment and a link to its draft GHSA. +# High/critical exclusions need GRC/SecOps approval recorded in the GHSA (SEC0025). diff --git a/.github/secscan-exclusions/linux-arm64.txt b/.github/secscan-exclusions/linux-arm64.txt new file mode 100644 index 00000000000..4386d831989 --- /dev/null +++ b/.github/secscan-exclusions/linux-arm64.txt @@ -0,0 +1,3 @@ +# CVEs excluded from the secscan gate for this artifact, one per line. +# Every entry needs a justification comment and a link to its draft GHSA. +# High/critical exclusions need GRC/SecOps approval recorded in the GHSA (SEC0025). diff --git a/.github/secscan-exclusions/linux-ppc64le.txt b/.github/secscan-exclusions/linux-ppc64le.txt new file mode 100644 index 00000000000..4386d831989 --- /dev/null +++ b/.github/secscan-exclusions/linux-ppc64le.txt @@ -0,0 +1,3 @@ +# CVEs excluded from the secscan gate for this artifact, one per line. +# Every entry needs a justification comment and a link to its draft GHSA. +# High/critical exclusions need GRC/SecOps approval recorded in the GHSA (SEC0025). diff --git a/.github/secscan-exclusions/linux-s390x.txt b/.github/secscan-exclusions/linux-s390x.txt new file mode 100644 index 00000000000..4386d831989 --- /dev/null +++ b/.github/secscan-exclusions/linux-s390x.txt @@ -0,0 +1,3 @@ +# CVEs excluded from the secscan gate for this artifact, one per line. +# Every entry needs a justification comment and a link to its draft GHSA. +# High/critical exclusions need GRC/SecOps approval recorded in the GHSA (SEC0025). diff --git a/.github/secscan-exclusions/linux-x64.txt b/.github/secscan-exclusions/linux-x64.txt new file mode 100644 index 00000000000..4386d831989 --- /dev/null +++ b/.github/secscan-exclusions/linux-x64.txt @@ -0,0 +1,3 @@ +# CVEs excluded from the secscan gate for this artifact, one per line. +# Every entry needs a justification comment and a link to its draft GHSA. +# High/critical exclusions need GRC/SecOps approval recorded in the GHSA (SEC0025). diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c4f4fedb007..fa7403a7947 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -162,8 +162,16 @@ jobs: path: | _package - release: + secscan: needs: build + permissions: + contents: read + uses: ./.github/workflows/secscan.yaml + with: + source: artifacts + + release: + needs: [build, secscan] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/secscan.yaml b/.github/workflows/secscan.yaml new file mode 100644 index 00000000000..d5b4f18b3f1 --- /dev/null +++ b/.github/workflows/secscan.yaml @@ -0,0 +1,125 @@ +# Scans the Linux runner release tarballs with Canonical secscan to meet SEC0025. +# Called by release.yml on the freshly built packages (blocking the release), and run +# weekly against the latest published release. +name: Secscan + +on: + schedule: + - cron: "29 4 * * 1" + workflow_dispatch: + workflow_call: + inputs: + source: + description: "'artifacts' scans this run's runner-packages-* artifacts; otherwise the latest release." + type: string + default: release + pull_request: + paths: + - .github/workflows/secscan.yaml + - .github/secscan-exclusions/** + +permissions: {} + +env: + SSDLC_CYCLE: "26.10" + +jobs: + secscan: + name: Secscan ${{ matrix.runtime }} + # Fork PRs must never reach private-endpoint runners. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + runs-on: [self-hosted-linux-amd64-noble-private-endpoint-medium] + permissions: + contents: read + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + runtime: [linux-x64, linux-arm64, linux-ppc64le, linux-s390x, linux-arm] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install secscan client + run: | + sudo snap install canonical-secscan-client + sudo snap connect canonical-secscan-client:home system:home + mkdir -p "$HOME/snap/canonical-secscan-client/common" + echo "SECSCAN_DIR=$HOME/snap/canonical-secscan-client/common" >> "$GITHUB_ENV" + echo "NO_PROXY=${NO_PROXY:+$NO_PROXY,}.internal" >> "$GITHUB_ENV" + echo "no_proxy=${no_proxy:+$no_proxy,}.internal" >> "$GITHUB_ENV" + - name: Download built package + if: inputs.source == 'artifacts' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: runner-packages-${{ matrix.runtime }} + path: packages + - name: Download latest release package + if: inputs.source != 'artifacts' + env: + RUNTIME: ${{ matrix.runtime }} + run: | + mkdir -p packages + url="$(curl -fsSL "https://api.github.com/repos/$GITHUB_REPOSITORY/releases/latest" \ + | python3 -c 'import json,sys,os; r=os.environ["RUNTIME"]; print(next(a["browser_download_url"] for a in json.load(sys.stdin)["assets"] if a["name"].startswith(f"actions-runner-{r}-") and a["name"].endswith(".tar.gz")))')" + curl -fsSL -o "packages/${url##*/}" "$url" + - name: Stage package + id: stage + env: + RUNTIME: ${{ matrix.runtime }} + run: | + shopt -s nullglob + files=(packages/actions-runner-"$RUNTIME"-*.tar.gz) + [[ ${#files[@]} -eq 1 ]] || { echo "::error::expected one $RUNTIME tarball, found ${#files[@]}"; exit 1; } + name="${files[0]##*/}" + version="${name#actions-runner-"$RUNTIME"-}"; version="${version%.tar.gz}" + cp "${files[0]}" "$SECSCAN_DIR/$name" + echo "target=$SECSCAN_DIR/$name" >> "$GITHUB_OUTPUT" + echo "version=$version" >> "$GITHUB_OUTPUT" + - name: Scan + env: + ARTIFACT: ${{ matrix.runtime }} + TARGET: ${{ steps.stage.outputs.target }} + SSDLC_VERSION: ${{ steps.stage.outputs.version }} + SSDLC_CHANNEL: stable + run: | + select_exclusions() { # $1 artifact name, $2 staging dir; prints staged path or nothing + local src=".github/secscan-exclusions/$1.txt" + if [[ -f "$src" ]] && grep -qvE '^[[:space:]]*(#|$)' "$src"; then + cp "$src" "$2/exclusions-$1.txt"; echo "$2/exclusions-$1.txt" + fi + } + classify() { # $1 secscan-client --batch exit code; echoes annotation, returns it + case "$1" in + 0) echo "No CVEs found." ;; + 101) echo "::error::Excluded CVEs are no longer reported; remove them from .github/secscan-exclusions/." ;; + 1[0-9][0-9]) echo "::error::CVEs found; download the secscan report artifact and triage per SECURITY.md." ;; + *) echo "::error::secscan-client failed (exit $1); re-run the job." ;; + esac + return "$1" + } + mkdir -p reports + exclusions=() + excl_file="$(select_exclusions "$ARTIFACT" "$SECSCAN_DIR")" + [[ -n "$excl_file" ]] && exclusions=(--exclusions-filename "$excl_file") + set +e + secscan-client --batch submit \ + --scanner trivy --type package --format archive \ + --ssdlc-product-name github-runner \ + --ssdlc-product-version "$SSDLC_VERSION" \ + --ssdlc-product-channel "$SSDLC_CHANNEL" \ + --ssdlc-cycle "$SSDLC_CYCLE" \ + --token "$SECSCAN_DIR/$ARTIFACT.token" \ + "${exclusions[@]}" \ + --wait-and-print "$TARGET" | tee "reports/$ARTIFACT.txt" + rc=${PIPESTATUS[0]} + secscan-client --batch report --token "$SECSCAN_DIR/$ARTIFACT.token" > "reports/$ARTIFACT.native.txt" 2>&1 + set -e + classify "$rc" + - name: Upload report + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: secscan-report-${{ matrix.runtime }} + path: reports/ + if-no-files-found: warn diff --git a/SECURITY.md b/SECURITY.md index fe6468e8d0a..ea5e1fe3685 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -21,3 +21,17 @@ assigned and coordinating the release of the fix. The [Ubuntu Security disclosure and embargo policy](https://ubuntu.com/security/disclosure-policy) contains more information about what you can expect when you contact us, and what we expect from you. + +## Vulnerability scanning + +The `Secscan` workflow scans the Linux runner tarballs with Canonical's secscan service +(Trivy). `Runner CD` runs it on the freshly built packages and does not create the +release if it fails; it also runs weekly against the latest release and on demand. + +When it fails, maintainers download the `secscan-report-*` artifacts and either fix the +issue (usually a .NET or bundled Node.js update) or open a draft +[GitHub security advisory](../../security/advisories/new) recording the CVE, its severity +and the remediation plan. A CVE accepted as a false positive or acceptable risk is added +to `.github/secscan-exclusions/.txt` with a justification comment and a link to +its advisory. High or critical exclusions need security team approval. The SSDLC cycle is +set by `SSDLC_CYCLE` in the workflow and must be bumped each cycle. From 1e1f5a4b1688de7b4934e07ff6dd2dc9ad20b2a1 Mon Sep 17 00:00:00 2001 From: Christopher Bartz Date: Fri, 2 Oct 2026 12:50:21 +0000 Subject: [PATCH 2/9] ci(secscan): authenticate release lookup and separate report html --- .github/workflows/release.yml | 2 +- .github/workflows/secscan.yaml | 12 ++++++++---- SECURITY.md | 4 ++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fa7403a7947..5273ff1a924 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -166,7 +166,7 @@ jobs: needs: build permissions: contents: read - uses: ./.github/workflows/secscan.yaml + uses: ./.github/workflows/secscan.yaml # zizmor: ignore[self-repository] with: source: artifacts diff --git a/.github/workflows/secscan.yaml b/.github/workflows/secscan.yaml index d5b4f18b3f1..27532e5e4df 100644 --- a/.github/workflows/secscan.yaml +++ b/.github/workflows/secscan.yaml @@ -58,11 +58,12 @@ jobs: if: inputs.source != 'artifacts' env: RUNTIME: ${{ matrix.runtime }} + GH_TOKEN: ${{ github.token }} run: | mkdir -p packages - url="$(curl -fsSL "https://api.github.com/repos/$GITHUB_REPOSITORY/releases/latest" \ + url="$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" "https://api.github.com/repos/$GITHUB_REPOSITORY/releases/latest" \ | python3 -c 'import json,sys,os; r=os.environ["RUNTIME"]; print(next(a["browser_download_url"] for a in json.load(sys.stdin)["assets"] if a["name"].startswith(f"actions-runner-{r}-") and a["name"].endswith(".tar.gz")))')" - curl -fsSL -o "packages/${url##*/}" "$url" + curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -o "packages/${url##*/}" "$url" - name: Stage package id: stage env: @@ -93,7 +94,7 @@ jobs: case "$1" in 0) echo "No CVEs found." ;; 101) echo "::error::Excluded CVEs are no longer reported; remove them from .github/secscan-exclusions/." ;; - 1[0-9][0-9]) echo "::error::CVEs found; download the secscan report artifact and triage per SECURITY.md." ;; + 1[0-9][0-9]) echo "::error::CVEs found; the secscan-report artifact's $ARTIFACT.txt lists them; triage per SECURITY.md." ;; *) echo "::error::secscan-client failed (exit $1); re-run the job." ;; esac return "$1" @@ -113,7 +114,10 @@ jobs: "${exclusions[@]}" \ --wait-and-print "$TARGET" | tee "reports/$ARTIFACT.txt" rc=${PIPESTATUS[0]} - secscan-client --batch report --token "$SECSCAN_DIR/$ARTIFACT.token" > "reports/$ARTIFACT.native.txt" 2>&1 + if (( rc == 0 || rc >= 100 )); then + secscan-client --batch report --token "$SECSCAN_DIR/$ARTIFACT.token" \ + > "reports/$ARTIFACT.report.html" 2> "reports/$ARTIFACT.report.err" || true + fi set -e classify "$rc" - name: Upload report diff --git a/SECURITY.md b/SECURITY.md index ea5e1fe3685..e543647a048 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -28,6 +28,10 @@ The `Secscan` workflow scans the Linux runner tarballs with Canonical's secscan (Trivy). `Runner CD` runs it on the freshly built packages and does not create the release if it fails; it also runs weekly against the latest release and on demand. +Each `secscan-report-` artifact contains `.txt`, the authoritative +verdict plus the CVE IDs secscan reported, and `.report.html`, Trivy's detailed +report with package and severity. + When it fails, maintainers download the `secscan-report-*` artifacts and either fix the issue (usually a .NET or bundled Node.js update) or open a draft [GitHub security advisory](../../security/advisories/new) recording the CVE, its severity From 15b92df25c64d481a9caf224bede6bc717e918a9 Mon Sep 17 00:00:00 2001 From: Christopher Bartz Date: Fri, 2 Oct 2026 13:34:30 +0000 Subject: [PATCH 3/9] docs(security): explain re-dispatching runner cd after an exclusion --- SECURITY.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index e543647a048..7392d7136d2 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -30,12 +30,14 @@ release if it fails; it also runs weekly against the latest release and on deman Each `secscan-report-` artifact contains `.txt`, the authoritative verdict plus the CVE IDs secscan reported, and `.report.html`, Trivy's detailed -report with package and severity. +report, which may not list every CVE secscan reports for archive scans. When it fails, maintainers download the `secscan-report-*` artifacts and either fix the issue (usually a .NET or bundled Node.js update) or open a draft [GitHub security advisory](../../security/advisories/new) recording the CVE, its severity and the remediation plan. A CVE accepted as a false positive or acceptable risk is added to `.github/secscan-exclusions/.txt` with a justification comment and a link to -its advisory. High or critical exclusions need security team approval. The SSDLC cycle is -set by `SSDLC_CYCLE` in the workflow and must be bumped each cycle. +its advisory. Committing an exclusion does not re-trigger `Runner CD`; dispatch it again on +the release branch to retry the release. High or critical exclusions need security team +approval. The SSDLC cycle is set by `SSDLC_CYCLE` in the workflow and must be bumped each +cycle. From 5cc08dcc2c9e97d767214b4dea6e840b69f373b7 Mon Sep 17 00:00:00 2001 From: Christopher Bartz Date: Fri, 2 Oct 2026 14:17:05 +0000 Subject: [PATCH 4/9] ci(secscan): report release scan results without blocking the release --- .github/workflows/release.yml | 2 +- SECURITY.md | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5273ff1a924..ca7cb33f8d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -171,7 +171,7 @@ jobs: source: artifacts release: - needs: [build, secscan] + needs: build runs-on: ubuntu-latest steps: diff --git a/SECURITY.md b/SECURITY.md index 7392d7136d2..1fb4be76466 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -25,8 +25,9 @@ expect from you. ## Vulnerability scanning The `Secscan` workflow scans the Linux runner tarballs with Canonical's secscan service -(Trivy). `Runner CD` runs it on the freshly built packages and does not create the -release if it fails; it also runs weekly against the latest release and on demand. +(Trivy). `Runner CD` runs it on the freshly built packages alongside the release; a +failing scan marks the run as failed but does not block the release. It also runs weekly +against the latest release and on demand. Each `secscan-report-` artifact contains `.txt`, the authoritative verdict plus the CVE IDs secscan reported, and `.report.html`, Trivy's detailed @@ -37,7 +38,5 @@ issue (usually a .NET or bundled Node.js update) or open a draft [GitHub security advisory](../../security/advisories/new) recording the CVE, its severity and the remediation plan. A CVE accepted as a false positive or acceptable risk is added to `.github/secscan-exclusions/.txt` with a justification comment and a link to -its advisory. Committing an exclusion does not re-trigger `Runner CD`; dispatch it again on -the release branch to retry the release. High or critical exclusions need security team -approval. The SSDLC cycle is set by `SSDLC_CYCLE` in the workflow and must be bumped each -cycle. +its advisory. High or critical exclusions need security team approval. The SSDLC cycle is +set by `SSDLC_CYCLE` in the workflow and must be bumped each cycle. From f9580593c8c9b7d3393174d8a0e2ef0d4d06b7d7 Mon Sep 17 00:00:00 2001 From: Christopher Bartz Date: Fri, 2 Oct 2026 14:26:58 +0000 Subject: [PATCH 5/9] ci(secscan): make snap install idempotent and fix release-scan docs --- .github/workflows/secscan.yaml | 4 ++-- SECURITY.md | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/secscan.yaml b/.github/workflows/secscan.yaml index 27532e5e4df..8ecee56bdf4 100644 --- a/.github/workflows/secscan.yaml +++ b/.github/workflows/secscan.yaml @@ -1,5 +1,5 @@ # Scans the Linux runner release tarballs with Canonical secscan to meet SEC0025. -# Called by release.yml on the freshly built packages (blocking the release), and run +# Called by release.yml on the freshly built packages (report-only; it does not block the release), and run # weekly against the latest published release. name: Secscan @@ -42,7 +42,7 @@ jobs: persist-credentials: false - name: Install secscan client run: | - sudo snap install canonical-secscan-client + snap list canonical-secscan-client >/dev/null 2>&1 || sudo snap install canonical-secscan-client sudo snap connect canonical-secscan-client:home system:home mkdir -p "$HOME/snap/canonical-secscan-client/common" echo "SECSCAN_DIR=$HOME/snap/canonical-secscan-client/common" >> "$GITHUB_ENV" diff --git a/SECURITY.md b/SECURITY.md index 1fb4be76466..6c80f72edc9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -31,7 +31,8 @@ against the latest release and on demand. Each `secscan-report-` artifact contains `.txt`, the authoritative verdict plus the CVE IDs secscan reported, and `.report.html`, Trivy's detailed -report, which may not list every CVE secscan reports for archive scans. +report, which may not list every CVE secscan reports for archive scans. The HTML report is +best-effort: it is absent when the scan fails before completing. When it fails, maintainers download the `secscan-report-*` artifacts and either fix the issue (usually a .NET or bundled Node.js update) or open a draft From a4d8561c71ce85ab0e8a038ae1bf12cd5a0f5053 Mon Sep 17 00:00:00 2001 From: Christopher Bartz Date: Fri, 2 Oct 2026 14:31:22 +0000 Subject: [PATCH 6/9] ci(secscan): remove staged packages and tokens after each scan --- .github/workflows/secscan.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/secscan.yaml b/.github/workflows/secscan.yaml index 8ecee56bdf4..3faad93e157 100644 --- a/.github/workflows/secscan.yaml +++ b/.github/workflows/secscan.yaml @@ -127,3 +127,6 @@ jobs: name: secscan-report-${{ matrix.runtime }} path: reports/ if-no-files-found: warn + - name: Clean up secscan staging + if: always() + run: rm -rf "${SECSCAN_DIR:?}"/* From bdf52eef3fd2b1b9d75841e6e5fc0c9261cb7a3b Mon Sep 17 00:00:00 2001 From: Christopher Bartz Date: Fri, 2 Oct 2026 14:37:47 +0000 Subject: [PATCH 7/9] ci(secscan): isolate staging per job --- .github/workflows/secscan.yaml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/secscan.yaml b/.github/workflows/secscan.yaml index 3faad93e157..8ae477cce30 100644 --- a/.github/workflows/secscan.yaml +++ b/.github/workflows/secscan.yaml @@ -41,11 +41,15 @@ jobs: with: persist-credentials: false - name: Install secscan client + env: + RUNTIME: ${{ matrix.runtime }} run: | snap list canonical-secscan-client >/dev/null 2>&1 || sudo snap install canonical-secscan-client sudo snap connect canonical-secscan-client:home system:home - mkdir -p "$HOME/snap/canonical-secscan-client/common" - echo "SECSCAN_DIR=$HOME/snap/canonical-secscan-client/common" >> "$GITHUB_ENV" + # Per-job dir so concurrent jobs on one host cannot collide on tokens or cleanup. + secscan_dir="$HOME/snap/canonical-secscan-client/common/$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$RUNTIME" + mkdir -p "$secscan_dir" + echo "SECSCAN_DIR=$secscan_dir" >> "$GITHUB_ENV" echo "NO_PROXY=${NO_PROXY:+$NO_PROXY,}.internal" >> "$GITHUB_ENV" echo "no_proxy=${no_proxy:+$no_proxy,}.internal" >> "$GITHUB_ENV" - name: Download built package @@ -129,4 +133,4 @@ jobs: if-no-files-found: warn - name: Clean up secscan staging if: always() - run: rm -rf "${SECSCAN_DIR:?}"/* + run: rm -rf "${SECSCAN_DIR:?}" From d161c182656fb83d006da573f6dfdfa92d832d10 Mon Sep 17 00:00:00 2001 From: Christopher Bartz Date: Fri, 2 Oct 2026 14:41:15 +0000 Subject: [PATCH 8/9] ci(secscan): tolerate concurrent snap install --- .github/workflows/secscan.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/secscan.yaml b/.github/workflows/secscan.yaml index 8ae477cce30..fcaf40b8545 100644 --- a/.github/workflows/secscan.yaml +++ b/.github/workflows/secscan.yaml @@ -44,7 +44,10 @@ jobs: env: RUNTIME: ${{ matrix.runtime }} run: | - snap list canonical-secscan-client >/dev/null 2>&1 || sudo snap install canonical-secscan-client + # A concurrent job on the same host may win the install race. + snap list canonical-secscan-client >/dev/null 2>&1 || + sudo snap install canonical-secscan-client || + snap list canonical-secscan-client >/dev/null 2>&1 sudo snap connect canonical-secscan-client:home system:home # Per-job dir so concurrent jobs on one host cannot collide on tokens or cleanup. secscan_dir="$HOME/snap/canonical-secscan-client/common/$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$RUNTIME" From 0b1f743b0264472f64bfb4b6096627f71d868f95 Mon Sep 17 00:00:00 2001 From: Christopher Bartz Date: Mon, 5 Oct 2026 06:09:24 +0000 Subject: [PATCH 9/9] ci(secscan): drop fork-PR guard covered by external contributor check --- .github/workflows/secscan.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/secscan.yaml b/.github/workflows/secscan.yaml index fcaf40b8545..0f4087be4e3 100644 --- a/.github/workflows/secscan.yaml +++ b/.github/workflows/secscan.yaml @@ -26,8 +26,6 @@ env: jobs: secscan: name: Secscan ${{ matrix.runtime }} - # Fork PRs must never reach private-endpoint runners. - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository runs-on: [self-hosted-linux-amd64-noble-private-endpoint-medium] permissions: contents: read