From b8941e5b9f78bc91fbf4b5f741b74452796292ae Mon Sep 17 00:00:00 2001 From: "Andrei.Ovcharenko" Date: Thu, 27 Aug 2026 15:33:25 +0300 Subject: [PATCH 1/4] ci: bound Actions artifact retention --- .github/workflows/ci.yml | 1 + .github/workflows/scorecard.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 639a742..f986a15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,6 +61,7 @@ jobs: with: name: jacoco-report-${{ github.run_id }} path: build/reports/jacoco/jacocoAllReport/html + retention-days: 7 - name: Upload coverage to Codecov if: matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 983f3c6..83e856a 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -43,3 +43,4 @@ jobs: name: openssf-scorecard path: scorecard-results.sarif if-no-files-found: error + retention-days: 7 From 31eac78aede79b87bce6a83aee79984f160b9e3f Mon Sep 17 00:00:00 2001 From: "Andrei.Ovcharenko" Date: Sun, 30 Aug 2026 13:06:55 +0300 Subject: [PATCH 2/4] ci: probe private GHCR artifact package --- .github/workflows/ghcr-canary.yml | 111 ++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 .github/workflows/ghcr-canary.yml diff --git a/.github/workflows/ghcr-canary.yml b/.github/workflows/ghcr-canary.yml new file mode 100644 index 0000000..d0b9b3a --- /dev/null +++ b/.github/workflows/ghcr-canary.yml @@ -0,0 +1,111 @@ +name: Private GHCR canary + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + probe: + name: Publish inert probe + if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') }} + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + packages: write + steps: + - name: Set up ORAS 1.3.3 + uses: oras-project/setup-oras@1d808f7d7f6995cc68b7bf507bfe5c5446e1dc9d + with: + version: 1.3.3 + + - name: Publish inert probe + id: probe + env: + GHCR_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + + owner="${GITHUB_REPOSITORY_OWNER,,}" + repository="${GITHUB_REPOSITORY#*/}" + package="${repository,,}-ci-artifacts" + tag="probe-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + reference="ghcr.io/${owner}/${package}:${tag}" + probe_dir="$(mktemp -d)" + registry_config="${probe_dir}/registry.json" + cleanup() { + oras logout ghcr.io --registry-config "${registry_config}" >/dev/null 2>&1 || true + rm -rf -- "${probe_dir}" + } + trap cleanup EXIT + + printf 'repository=%s\nrun_id=%s\nrun_attempt=%s\n' \ + "${GITHUB_REPOSITORY}" "${GITHUB_RUN_ID}" "${GITHUB_RUN_ATTEMPT}" \ + > "${probe_dir}/probe.txt" + + printf '%s' "${GHCR_TOKEN}" | oras login ghcr.io \ + --username "${GITHUB_ACTOR}" \ + --password-stdin \ + --registry-config "${registry_config}" + + digest="$( + cd "${probe_dir}" + oras push "${reference}" \ + --registry-config "${registry_config}" \ + --artifact-type application/vnd.krotname.ci-canary.v1 \ + --annotation "org.opencontainers.image.source=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \ + --annotation "org.opencontainers.image.revision=${GITHUB_HEAD_SHA:-${GITHUB_SHA}}" \ + --format go-template \ + --template '{{.digest}}' \ + "probe.txt:text/plain" + )" + + { + echo "package=${package}" + echo "reference=${reference}" + echo "digest=${digest}" + } >> "${GITHUB_OUTPUT}" + + - name: Verify private package state + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b + env: + PACKAGE_NAME: ${{ steps.probe.outputs.package }} + PACKAGE_REFERENCE: ${{ steps.probe.outputs.reference }} + PACKAGE_DIGEST: ${{ steps.probe.outputs.digest }} + with: + github-token: ${{ github.token }} + script: | + const response = await github.request( + 'GET /user/packages/{package_type}/{package_name}', + { + package_type: 'container', + package_name: process.env.PACKAGE_NAME, + headers: { 'X-GitHub-Api-Version': '2022-11-28' }, + }, + ); + const packageInfo = response.data; + const expectedOwner = context.repo.owner.toLowerCase(); + + if ( + packageInfo.name !== process.env.PACKAGE_NAME + || packageInfo.visibility !== 'private' + || packageInfo.owner?.login?.toLowerCase() !== expectedOwner + ) { + throw new Error( + `Unexpected package state: name=${packageInfo.name}, visibility=${packageInfo.visibility}, ` + + `owner=${packageInfo.owner?.login ?? 'unknown'}`, + ); + } + + await core.summary + .addHeading('Private GHCR canary', 3) + .addList([ + `Visibility: ${packageInfo.visibility}`, + `Owner: ${packageInfo.owner?.login}`, + `Reference: ${process.env.PACKAGE_REFERENCE}@${process.env.PACKAGE_DIGEST}`, + ]) + .write(); From 5329cbde5bbbab5264849efd0db84c4b8101b89c Mon Sep 17 00:00:00 2001 From: "Andrei.Ovcharenko" Date: Sun, 30 Aug 2026 13:14:15 +0300 Subject: [PATCH 3/4] ci: remove public canary package fail-closed --- .github/workflows/ghcr-canary.yml | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/.github/workflows/ghcr-canary.yml b/.github/workflows/ghcr-canary.yml index d0b9b3a..33b4dbf 100644 --- a/.github/workflows/ghcr-canary.yml +++ b/.github/workflows/ghcr-canary.yml @@ -109,3 +109,52 @@ jobs: `Reference: ${process.env.PACKAGE_REFERENCE}@${process.env.PACKAGE_DIGEST}`, ]) .write(); + + - name: Remove a public probe-only package + if: ${{ failure() }} + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b + with: + github-token: ${{ github.token }} + script: | + const packageName = `${context.repo.repo.toLowerCase()}-ci-artifacts`; + const packageResponse = await github.request( + 'GET /user/packages/{package_type}/{package_name}', + { + package_type: 'container', + package_name: packageName, + headers: { 'X-GitHub-Api-Version': '2022-11-28' }, + }, + ); + const packageInfo = packageResponse.data; + const versions = await github.paginate( + 'GET /user/packages/{package_type}/{package_name}/versions', + { + package_type: 'container', + package_name: packageName, + per_page: 100, + headers: { 'X-GitHub-Api-Version': '2022-11-28' }, + }, + ); + const probeOnly = versions.length > 0 && versions.every((version) => { + const tags = version.metadata?.container?.tags ?? []; + return tags.length > 0 && tags.every((tag) => /^probe-\d+-\d+$/.test(tag)); + }); + + if ( + packageInfo.name !== packageName + || packageInfo.visibility !== 'public' + || packageInfo.owner?.login?.toLowerCase() !== context.repo.owner.toLowerCase() + || !probeOnly + ) { + throw new Error(`Refusing to delete non-probe package ${packageName}`); + } + + await github.request( + 'DELETE /user/packages/{package_type}/{package_name}', + { + package_type: 'container', + package_name: packageName, + headers: { 'X-GitHub-Api-Version': '2022-11-28' }, + }, + ); + core.notice(`Deleted public probe-only package ${packageName}`); From 783d399be9cffe4776dbfb9ac8bd513a2109ccca Mon Sep 17 00:00:00 2001 From: "Andrei.Ovcharenko" Date: Sun, 30 Aug 2026 13:16:06 +0300 Subject: [PATCH 4/4] ci: stop GHCR rollout for public repository --- .github/workflows/ghcr-canary.yml | 160 ------------------------------ 1 file changed, 160 deletions(-) delete mode 100644 .github/workflows/ghcr-canary.yml diff --git a/.github/workflows/ghcr-canary.yml b/.github/workflows/ghcr-canary.yml deleted file mode 100644 index 33b4dbf..0000000 --- a/.github/workflows/ghcr-canary.yml +++ /dev/null @@ -1,160 +0,0 @@ -name: Private GHCR canary - -on: - pull_request: - workflow_dispatch: - -permissions: - contents: read - -jobs: - probe: - name: Publish inert probe - if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') }} - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: read - packages: write - steps: - - name: Set up ORAS 1.3.3 - uses: oras-project/setup-oras@1d808f7d7f6995cc68b7bf507bfe5c5446e1dc9d - with: - version: 1.3.3 - - - name: Publish inert probe - id: probe - env: - GHCR_TOKEN: ${{ github.token }} - shell: bash - run: | - set -euo pipefail - - owner="${GITHUB_REPOSITORY_OWNER,,}" - repository="${GITHUB_REPOSITORY#*/}" - package="${repository,,}-ci-artifacts" - tag="probe-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" - reference="ghcr.io/${owner}/${package}:${tag}" - probe_dir="$(mktemp -d)" - registry_config="${probe_dir}/registry.json" - cleanup() { - oras logout ghcr.io --registry-config "${registry_config}" >/dev/null 2>&1 || true - rm -rf -- "${probe_dir}" - } - trap cleanup EXIT - - printf 'repository=%s\nrun_id=%s\nrun_attempt=%s\n' \ - "${GITHUB_REPOSITORY}" "${GITHUB_RUN_ID}" "${GITHUB_RUN_ATTEMPT}" \ - > "${probe_dir}/probe.txt" - - printf '%s' "${GHCR_TOKEN}" | oras login ghcr.io \ - --username "${GITHUB_ACTOR}" \ - --password-stdin \ - --registry-config "${registry_config}" - - digest="$( - cd "${probe_dir}" - oras push "${reference}" \ - --registry-config "${registry_config}" \ - --artifact-type application/vnd.krotname.ci-canary.v1 \ - --annotation "org.opencontainers.image.source=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \ - --annotation "org.opencontainers.image.revision=${GITHUB_HEAD_SHA:-${GITHUB_SHA}}" \ - --format go-template \ - --template '{{.digest}}' \ - "probe.txt:text/plain" - )" - - { - echo "package=${package}" - echo "reference=${reference}" - echo "digest=${digest}" - } >> "${GITHUB_OUTPUT}" - - - name: Verify private package state - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b - env: - PACKAGE_NAME: ${{ steps.probe.outputs.package }} - PACKAGE_REFERENCE: ${{ steps.probe.outputs.reference }} - PACKAGE_DIGEST: ${{ steps.probe.outputs.digest }} - with: - github-token: ${{ github.token }} - script: | - const response = await github.request( - 'GET /user/packages/{package_type}/{package_name}', - { - package_type: 'container', - package_name: process.env.PACKAGE_NAME, - headers: { 'X-GitHub-Api-Version': '2022-11-28' }, - }, - ); - const packageInfo = response.data; - const expectedOwner = context.repo.owner.toLowerCase(); - - if ( - packageInfo.name !== process.env.PACKAGE_NAME - || packageInfo.visibility !== 'private' - || packageInfo.owner?.login?.toLowerCase() !== expectedOwner - ) { - throw new Error( - `Unexpected package state: name=${packageInfo.name}, visibility=${packageInfo.visibility}, ` - + `owner=${packageInfo.owner?.login ?? 'unknown'}`, - ); - } - - await core.summary - .addHeading('Private GHCR canary', 3) - .addList([ - `Visibility: ${packageInfo.visibility}`, - `Owner: ${packageInfo.owner?.login}`, - `Reference: ${process.env.PACKAGE_REFERENCE}@${process.env.PACKAGE_DIGEST}`, - ]) - .write(); - - - name: Remove a public probe-only package - if: ${{ failure() }} - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b - with: - github-token: ${{ github.token }} - script: | - const packageName = `${context.repo.repo.toLowerCase()}-ci-artifacts`; - const packageResponse = await github.request( - 'GET /user/packages/{package_type}/{package_name}', - { - package_type: 'container', - package_name: packageName, - headers: { 'X-GitHub-Api-Version': '2022-11-28' }, - }, - ); - const packageInfo = packageResponse.data; - const versions = await github.paginate( - 'GET /user/packages/{package_type}/{package_name}/versions', - { - package_type: 'container', - package_name: packageName, - per_page: 100, - headers: { 'X-GitHub-Api-Version': '2022-11-28' }, - }, - ); - const probeOnly = versions.length > 0 && versions.every((version) => { - const tags = version.metadata?.container?.tags ?? []; - return tags.length > 0 && tags.every((tag) => /^probe-\d+-\d+$/.test(tag)); - }); - - if ( - packageInfo.name !== packageName - || packageInfo.visibility !== 'public' - || packageInfo.owner?.login?.toLowerCase() !== context.repo.owner.toLowerCase() - || !probeOnly - ) { - throw new Error(`Refusing to delete non-probe package ${packageName}`); - } - - await github.request( - 'DELETE /user/packages/{package_type}/{package_name}', - { - package_type: 'container', - package_name: packageName, - headers: { 'X-GitHub-Api-Version': '2022-11-28' }, - }, - ); - core.notice(`Deleted public probe-only package ${packageName}`);