diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 76f53bd..0e552e4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,8 +4,12 @@ updates: directory: / schedule: interval: weekly + cooldown: + default-days: 7 - package-ecosystem: maven directory: / schedule: interval: weekly + cooldown: + default-days: 7 diff --git a/.github/workflows/build-any-branch.yml b/.github/workflows/build-any-branch.yml index 1dce9df..1bcf1d4 100644 --- a/.github/workflows/build-any-branch.yml +++ b/.github/workflows/build-any-branch.yml @@ -12,6 +12,8 @@ on: - '**/*.adoc' - '**/*.md' +permissions: {} + env: MAVEN_COMMAND: ./mvnw MAVEN_CLI_COMMON: "-e -B" @@ -25,12 +27,28 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 permissions: - contents: write + contents: read steps: - uses: actions/checkout@v7 + with: + persist-credentials: false - uses: ./.github/actions/jdk-setup - name: Build and verify run: ${{ env.MAVEN_COMMAND }} ${{ env.MAVEN_CLI_COMMON }} clean verify + + # Isolated from the build job so its write-scoped token is never present + # while a build of pull-request code runs. Only fires for pushes to main. + submit-dependencies: + needs: build + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: write + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: ./.github/actions/jdk-setup - name: Submit dependencies to GitHub - if: github.actor != 'dependabot[bot]' - uses: advanced-security/maven-dependency-submission-action@v5 + uses: advanced-security/maven-dependency-submission-action@a64327a7329c9939cf675e458452febe1894a70c # v6.0.1 diff --git a/.github/workflows/deploy-snapshot.yml b/.github/workflows/deploy-snapshot.yml index 7d51647..6e1c01d 100644 --- a/.github/workflows/deploy-snapshot.yml +++ b/.github/workflows/deploy-snapshot.yml @@ -1,6 +1,11 @@ name: Deploy Snapshot on: + # zizmor: ignore[dangerous-triggers] + # workflow_run is used safely: the job's `if` requires a successful "Build any + # branch" run from this repository (head_repository guard, so a fork branch + # named `main` cannot reach it) on `main` (branches filter), and it checks out + # exactly that run's commit (head_sha) — never fork-controlled code. workflow_run: workflows: ["Build any branch"] types: [completed] @@ -19,7 +24,9 @@ concurrency: jobs: deploy-snapshot: - if: github.event.workflow_run.conclusion == 'success' + if: >- + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.head_repository.full_name == github.repository runs-on: ubuntu-latest timeout-minutes: 20 steps: @@ -36,7 +43,7 @@ jobs: - name: Get project version id: ver - run: echo "version=$(${{ env.MAVEN_COMMAND }} help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_OUTPUT + run: echo "version=$(${{ env.MAVEN_COMMAND }} help:evaluate -Dexpression=project.version -q -DforceStdout)" >> "$GITHUB_OUTPUT" - name: Deploy snapshot to Maven Central if: endsWith(steps.ver.outputs.version, '-SNAPSHOT') @@ -47,4 +54,6 @@ jobs: - name: Skip (not a snapshot version) if: "!endsWith(steps.ver.outputs.version, '-SNAPSHOT')" - run: echo "Version ${{ steps.ver.outputs.version }} is a release version — skipping snapshot deploy" + env: + VERSION: ${{ steps.ver.outputs.version }} + run: echo "Version ${VERSION} is a release version — skipping snapshot deploy" diff --git a/.github/workflows/lint-workflows.yml b/.github/workflows/lint-workflows.yml new file mode 100644 index 0000000..1feefa2 --- /dev/null +++ b/.github/workflows/lint-workflows.yml @@ -0,0 +1,54 @@ +name: Lint workflows + +on: + pull_request: + paths: + - '.github/**' + push: + branches: [main] + paths: + - '.github/**' + schedule: + - cron: '0 6 * * 1' # Mondays 06:00 UTC — surface newly added lint rules and freshly deprecated actions + workflow_dispatch: + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + actionlint: + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Run actionlint + uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2.2.0 + with: + version: 1.7.12 # pin the tool; the action otherwise resolves 'latest' at run time + + zizmor: + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Run zizmor + env: + GH_TOKEN: ${{ github.token }} + # Pinned tool, run directly (not via zizmor-action) so --strict-collection + # can fail the job on a malformed collected file — dependabot.yml, an + # action.yml — instead of warning and skipping it. pipx is preinstalled + # on ubuntu-latest. + run: >- + pipx run zizmor==1.30.1 --strict-collection --format github + --persona regular --collect all -- .github/ diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 8b4a9b4..c8f9283 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -8,15 +8,17 @@ on: - 'src/site/**' - '**/*.adoc' - '**/*.md' + # zizmor: ignore[dangerous-triggers] + # workflow_run is used safely: for that event the build-site job requires a + # successful "Build any branch" run from this repository (head_repository + # guard) on `main` (branches filter), and checks out the default branch — never + # a triggering ref, so never fork-controlled code. workflow_run: workflows: ["Build any branch"] types: [completed] branches: [main] -permissions: - contents: read - pages: write - id-token: write +permissions: {} concurrency: group: pages @@ -28,16 +30,23 @@ env: jobs: build-site: - if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' + if: >- + github.event_name != 'workflow_run' || + (github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.head_repository.full_name == github.repository) runs-on: ubuntu-latest timeout-minutes: 20 + permissions: + contents: read steps: - uses: actions/checkout@v7 + with: + persist-credentials: false - uses: ./.github/actions/jdk-setup - name: Build project run: ${{ env.MAVEN_COMMAND }} ${{ env.MAVEN_CLI_COMMON }} install -DskipTests - name: Generate Maven site - run: ${{ env.MAVEN_COMMAND }} ${{ env.MAVEN_CLI_COMMON }} site -Dproject.build.outputTimestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ) + run: ${{ env.MAVEN_COMMAND }} ${{ env.MAVEN_CLI_COMMON }} site -Dproject.build.outputTimestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - name: Upload Pages artifact if: >- github.event_name == 'workflow_dispatch' || @@ -55,6 +64,9 @@ jobs: github.event_name == 'workflow_run' runs-on: ubuntu-latest timeout-minutes: 10 + permissions: + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 0000000..18445ff --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,22 @@ +# zizmor configuration — see .github/workflows/lint-workflows.yml. +# https://docs.zizmor.sh/configuration/ +rules: + unpinned-uses: + config: + # Symbolic (tag) refs are acceptable for actions published by GitHub's + # own organizations; anything else must be pinned to a full commit SHA. + policies: + "actions/*": ref-pin + "github/*": ref-pin + "dependabot/*": ref-pin + "*": hash-pin + + self-repository: + ignore: + # The `$/...` self-repository syntax is only a few weeks old (GitHub, + # Jul 2026). The workspace-relative `./...` form is used deliberately + # until that syntax has settled; revisit and switch over later. + - build-any-branch.yml + - deploy-snapshot.yml + - publish-docs.yml + - release.yml