From cf3ea751fe102cbcc2c32dc654924371a8691f0d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 15:00:58 +0000 Subject: [PATCH 1/2] docs: release pre-flight reflects build-time apt upgrade and built-image scanning The pre-release step still asked for a manual apt-cache check against the base image (naming a package renamed in Noble). Both the process document and the /release skill now point at the automated gates that replaced it. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01TbLEyxJhFzfRL6cexuGmgp --- .claude/skills/release/SKILL.md | 2 +- engineering/RELEASE_PROCESS.md | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 01e8d4404..412e30f13 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -38,7 +38,7 @@ Before starting, verify: 4. **The `[Unreleased]` section of `CHANGELOG.md` has entries**. If it is empty, warn the user — a release with no changelog entries is unusual. -5. **Review pinned Docker dependencies**: Check that base image digests and apt package versions are current. If Dependabot PRs for Docker digests have been merged since the last release, flag this so the user can verify pinned apt versions still match. +5. **Review pinned Docker dependencies**: Check for open Dependabot base-image digest PRs and open `apt-pin-check` PRs, and flag them so the user can merge them before tagging. No manual verification of apt versions is needed: the production stages run `apt-get upgrade` before the pinned installs (a stale pin fails the build), and the `scan-images` CI job builds and scans every production image on each push to `main`. ## Documentation Review and Update diff --git a/engineering/RELEASE_PROCESS.md b/engineering/RELEASE_PROCESS.md index d89e46de3..668fed98a 100644 --- a/engineering/RELEASE_PROCESS.md +++ b/engineering/RELEASE_PROCESS.md @@ -62,11 +62,9 @@ git push origin main --tags ### Steps -1. **Review pinned Docker dependencies**: Check that base image digests and apt package versions are up to date. If Dependabot PRs for Docker digests have been merged since the last release, verify the pinned apt versions still match. If not, update them: +1. **Review pinned Docker dependencies**: merge any open Dependabot base-image digest PR and any open `apt-pin-check` PR before tagging, so the release ships the newest base image and current apt pins. Nothing needs checking by hand beyond that: every production stage runs `apt-get upgrade` before its pinned installs, so a stale pin fails the image build rather than shipping, and the `scan-images` CI job builds and scans every production image on each push to `main`, failing on any fixable HIGH/CRITICAL CVE. Confirm the latest `main` run is green. To inspect the archive's current versions for a pin by hand (the `apt-pin-check` workflow does this daily): ```bash - # Check available versions in the current base image - docker run --rm mcr.microsoft.com/dotnet/aspnet:10.0@sha256: bash -c \ - "apt-get update -qq && apt-cache policy libldap-common libldap-2.5-0 cifs-utils" + pwsh -File ./.github/scripts/check-apt-pins.ps1 ``` 2. **Update the changelog**: Move items from `[Unreleased]` to a new version section in `CHANGELOG.md` From cc025743ec19d33fc7b82ea0ef64dd35b05cd641 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 6 Sep 2026 16:47:07 +0000 Subject: [PATCH 2/2] ci: remove the retired-category uploads now the configurations are gone The three trivy-base-image-* configurations were deleted from code scanning via the API, so the empty uploads that kept the Trivy merge check satisfied during the transition have nothing left to satisfy. The Developer Guide's retirement procedure now records the API-driven deletion, its per-set semantics and the rate-limit cost, for the next category that goes. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01TbLEyxJhFzfRL6cexuGmgp --- .github/workflows/ci.yml | 56 +--------------------------------- engineering/DEVELOPER_GUIDE.md | 18 ++++++++++- 2 files changed, 18 insertions(+), 56 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20e859c68..b03759a9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -433,56 +433,6 @@ jobs: # never uploaded again. See discover-base-images.ps1. category: trivy-image-${{ matrix.image_name }} - retire-base-image-scan-categories: - # MIGRATION STEP: remove this job once the three configurations below have been - # deleted from Security > Code scanning > Tool status (Trivy). - # - # Code scanning remembers every category a workflow has ever uploaded for the - # default branch as a "configuration", and the Trivy merge check compares a PR - # against all of them. When scan-images replaced the base-image scan, the three - # trivy-base-image-* categories stopped being uploaded, the check reported - # "3 configurations present on main were not found", and the ruleset's - # code-scanning requirement blocked the PR (#1637) with every job green. An - # analysis with no results, uploaded under each retired category, satisfies the - # comparison, and once it lands on main it closes every alert those categories - # still hold as fixed; the base image findings they carried are now reported, - # where they still apply, by the built-image scan. Deleting the configurations - # is API/UI-only and cannot be done from a workflow, which is why this is a - # step to remove by hand rather than one that retires itself. - runs-on: ${{ github.event_name == 'push' && 'self-hosted' || 'ubuntu-latest' }} - permissions: - contents: read - security-events: write # required to upload SARIF to code scanning - - strategy: - fail-fast: false - matrix: - base_image: [aspnet, runtime, sdk] - - steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - - name: Write an empty Trivy analysis - run: | - cat > retired-category.sarif <<'EOF' - { - "version": "2.1.0", - "$schema": "https://json.schemastore.org/sarif-2.1.0.json", - "runs": [ - { - "tool": { "driver": { "name": "Trivy", "informationUri": "https://github.com/aquasecurity/trivy" } }, - "results": [] - } - ] - } - EOF - - - name: Upload the empty analysis under the retired category - uses: github/codeql-action/upload-sarif@6f5948dfacef28e207b48d0905cf90c03365536d # v3.37.9 - with: - sarif_file: retired-category.sarif - category: trivy-base-image-mcr-microsoft-com-dotnet-${{ matrix.base_image }} - scan-base-images-summary: # Stable-name gate for the dynamic scan-images matrix, providing the single # check name the branch protection ruleset requires. The name predates the @@ -490,7 +440,7 @@ jobs: # kept because the ruleset names it; renaming it here without changing the # ruleset would leave every PR waiting on a check that never reports. if: always() - needs: [discover-base-images, scan-images, retire-base-image-scan-categories] + needs: [discover-base-images, scan-images] runs-on: ${{ github.event_name == 'push' && 'self-hosted' || 'ubuntu-latest' }} steps: - name: Check matrix results @@ -503,8 +453,4 @@ jobs: echo "One or more scan-images matrix legs failed" exit 1 fi - if [ "${{ needs.retire-base-image-scan-categories.result }}" != "success" ]; then - echo "One or more retired-category uploads failed" - exit 1 - fi echo "All image scans passed" diff --git a/engineering/DEVELOPER_GUIDE.md b/engineering/DEVELOPER_GUIDE.md index c6867288f..a9c467196 100644 --- a/engineering/DEVELOPER_GUIDE.md +++ b/engineering/DEVELOPER_GUIDE.md @@ -1109,7 +1109,23 @@ When adding a new production Dockerfile: Vulnerability scanning (`scan-images`) builds every production JIM image on every push and PR and scans the result: the built image is what customers run, and it alone carries the apt pins and the build-time upgrade. Findings are surfaced in the GitHub Security tab via SARIF upload (one category per JIM image) in addition to the Actions log, so they are visible to reviewers and auditable after the fact. -**Retiring a code scanning category.** Code scanning remembers every SARIF category a workflow has uploaded for `main` as a "configuration", and the per-tool merge check compares each PR against all of them: a PR whose workflow stops uploading a category is reported as "configurations present on main were not found" and the ruleset's code-scanning requirement blocks the merge with every job green (seen on #1637 when the base-image scan became the built-image scan). Two steps retire one cleanly: keep uploading an empty analysis under the old category until the change has landed (the `retire-base-image-scan-categories` job does this; on `main` it also closes the category's remaining alerts as fixed), then delete the configuration from *Security > Code scanning > Tool status* and remove the job. Deletion is UI/API-only, so it cannot be folded into the workflow. +**Retiring a code scanning category.** Code scanning remembers every SARIF category a workflow has uploaded for `main` as a "configuration", and the per-tool merge check compares each PR against all of them: a PR whose workflow stops uploading a category is reported as "configurations present on main were not found" and the ruleset's code-scanning requirement blocks the merge with every job green (seen on #1637 when the base-image scan became the built-image scan). Two steps retire one cleanly: keep uploading an empty analysis under the old category until the change has landed (a small matrix job writing a SARIF file with no results and passing it to `upload-sarif` with the old `category`; on `main` that also closes the category's remaining alerts as fixed), then delete every analysis in the category and remove the job. Deletion is API-only (the Tool status page lists configurations but cannot delete them), and the API deletes one *set* at a time (one set per category per git ref), so it has to be driven to exhaustion: + +```bash +repo=repos/TetronIO/JIM +while :; do + urls=$(gh api "$repo/code-scanning/analyses?tool_name=Trivy&per_page=100" --paginate \ + --jq '.[] | select((.category|startswith("")) and .deletable==true) | .url') + [ -z "$urls" ] && break + for url in $urls; do + while [ -n "$url" ]; do + url=$(gh api -X DELETE "$url?confirm_delete" --jq '.next_analysis_url // empty' 2>/dev/null) || break + done + done +done +``` + +Budget for it: retiring the three base-image categories on #1637 meant about 8,000 deletions against the 5,000-per-hour API limit, so run it detached (`nohup`) with a retry on rate-limit refusals, and expect it to take a couple of hours. **Why this matters**: `System.DirectoryServices.Protocols` (the .NET LDAP client) P/Invokes into the native `libldap` shared library at runtime. An incompatible libldap version could cause silent behavioural differences or crashes during LDAP/AD operations.