From da7feb5eb14f727b8025edd568c05dd5a01800ab Mon Sep 17 00:00:00 2001 From: "v.razuvaev" Date: Mon, 3 Aug 2026 18:56:23 +0300 Subject: [PATCH 1/2] ci: add path-aware job gating and zizmor security workflow - build.yml / static-analysis.yml: add a 'changes' gate job; heavy jobs now carry needs/if on it so docs-only changes skip them while required checks still resolve (skipped, not pending). - zizmor.yml: new job, SHA-pinned zizmorcore/zizmor-action v0.6.2, persona auditor, advanced-security disabled, PR/push-triggered on .github/**/*.yml changes. - release.yml: move the contents:write permission comment inline so zizmor's undocumented-permissions audit recognizes it. - dependabot.yml: add a 7-day cooldown per ecosystem (zizmor's dependabot-cooldown auto-fix) so bumps aren't applied same-day. --- .github/dependabot.yml | 4 +++ .github/workflows/build.yml | 47 +++++++++++++++++++++++++++ .github/workflows/release.yml | 3 +- .github/workflows/static-analysis.yml | 41 +++++++++++++++++++++++ .github/workflows/zizmor.yml | 38 ++++++++++++++++++++++ 5 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/zizmor.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3d983e6..f28cabb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,6 +11,8 @@ updates: open-pull-requests-limit: 5 labels: - dependencies + cooldown: + default-days: 7 - package-ecosystem: github-actions directory: / @@ -22,3 +24,5 @@ updates: open-pull-requests-limit: 5 labels: - dependencies + cooldown: + default-days: 7 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00a03d7..4fe5a3b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,8 +14,49 @@ concurrency: cancel-in-progress: true jobs: + changes: + name: Relevant changes + runs-on: ubuntu-latest + + outputs: + run: ${{ steps.filter.outputs.run }} + + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Detect relevant changes + id: filter + env: + EVENT_NAME: ${{ github.event_name }} + BEFORE_SHA: ${{ github.event.before }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: | + if [ "$EVENT_NAME" = "pull_request" ]; then + BASE="$BASE_SHA" + elif [ "$BEFORE_SHA" = "0000000000000000000000000000000000000000" ]; then + echo "run=true" >> "$GITHUB_OUTPUT" + exit 0 + else + BASE="$BEFORE_SHA" + fi + + if git diff --quiet "$BASE" "$GITHUB_SHA" -- \ + src tests config examples composer.json testo.php psalm.xml \ + rector.php .php-cs-fixer.php infection.json5 \ + .github/workflows/build.yml; then + echo "run=false" >> "$GITHUB_OUTPUT" + else + echo "run=true" >> "$GITHUB_OUTPUT" + fi + build: name: PHP ${{ matrix.php }} + needs: changes + if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.run == 'true') }} runs-on: ubuntu-latest strategy: @@ -55,6 +96,8 @@ jobs: prefer-lowest: name: Prefer lowest + needs: changes + if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.run == 'true') }} runs-on: ubuntu-latest steps: @@ -86,6 +129,8 @@ jobs: coverage: name: Coverage & Mutation + needs: changes + if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.run == 'true') }} runs-on: ubuntu-latest steps: @@ -120,6 +165,8 @@ jobs: compatibility: name: Backward compatibility + needs: changes + if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.run == 'true') }} runs-on: ubuntu-latest steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a920837..b889dbf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,8 +21,7 @@ jobs: name: Publish GitHub release runs-on: ubuntu-latest permissions: - # creating a Release object is a write to the repository - contents: write + contents: write # Needed to create the GitHub release steps: - name: Checkout diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index b5bc137..da30fad 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -14,8 +14,49 @@ concurrency: cancel-in-progress: true jobs: + changes: + name: Relevant changes + runs-on: ubuntu-latest + + outputs: + run: ${{ steps.filter.outputs.run }} + + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Detect relevant changes + id: filter + env: + EVENT_NAME: ${{ github.event_name }} + BEFORE_SHA: ${{ github.event.before }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: | + if [ "$EVENT_NAME" = "pull_request" ]; then + BASE="$BASE_SHA" + elif [ "$BEFORE_SHA" = "0000000000000000000000000000000000000000" ]; then + echo "run=true" >> "$GITHUB_OUTPUT" + exit 0 + else + BASE="$BEFORE_SHA" + fi + + if git diff --quiet "$BASE" "$GITHUB_SHA" -- \ + src tests config examples composer.json testo.php psalm.xml \ + rector.php .php-cs-fixer.php infection.json5 \ + .github/workflows/static-analysis.yml; then + echo "run=false" >> "$GITHUB_OUTPUT" + else + echo "run=true" >> "$GITHUB_OUTPUT" + fi + psalm: name: Psalm + needs: changes + if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.run == 'true') }} runs-on: ubuntu-latest steps: diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 0000000..f2bfb70 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,38 @@ +name: GitHub Actions security + +on: + pull_request: + paths: &paths + - '.github/**/*.yml' + - '.github/**/*.yaml' + push: + branches: + - master + paths: *paths + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + zizmor: + name: zizmor + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Audit GitHub Actions + uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 + with: + advanced-security: false + annotations: true + online-audits: false + persona: auditor + version: 1.25.2 From 4660cc6d54917b9f094ef8414d8d9a735f5a12a8 Mon Sep 17 00:00:00 2001 From: "v.razuvaev" Date: Mon, 3 Aug 2026 19:04:22 +0300 Subject: [PATCH 2/2] ci: stop gating the matrix build job on 'changes' A skipped matrix job posts one check run under its unexpanded name template ("PHP ${{ matrix.php }}"), not per-version names, so the required PHP 8.3/8.4/8.5 checks never resolve and the PR stays BLOCKED forever. Confirmed live on bulkhead (same shape PR). --- .github/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4fe5a3b..7562f0c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,8 +55,6 @@ jobs: build: name: PHP ${{ matrix.php }} - needs: changes - if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.run == 'true') }} runs-on: ubuntu-latest strategy: