From a5fe88d2a1696b8561e2857b0753537d22574c44 Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Mon, 6 Apr 2026 16:03:29 -0700 Subject: [PATCH] Chore: Resolves some zizmor reported code scan findings (#12516) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- .github/workflows/ci-backend.yml | 7 +++ .github/workflows/ci-docker.yml | 6 ++- .github/workflows/ci-docs.yml | 5 +- .github/workflows/ci-frontend.yml | 14 ++++++ .github/workflows/ci-lint.yml | 2 + .github/workflows/ci-release.yml | 29 ++++++++--- .github/workflows/ci-static-analysis.yml | 10 +++- .github/workflows/cleanup-tags.yml | 1 + .github/workflows/crowdin.yml | 3 ++ .github/workflows/repo-maintenance.yml | 17 +++++-- .github/zizmor.yml | 61 ++++++++++++++++++++++++ 11 files changed, 139 insertions(+), 16 deletions(-) create mode 100644 .github/zizmor.yml diff --git a/.github/workflows/ci-backend.yml b/.github/workflows/ci-backend.yml index cff139e8c..2a52b84f0 100644 --- a/.github/workflows/ci-backend.yml +++ b/.github/workflows/ci-backend.yml @@ -13,10 +13,13 @@ concurrency: env: DEFAULT_UV_VERSION: "0.10.x" NLTK_DATA: "/usr/share/nltk_data" +permissions: {} jobs: changes: name: Detect Backend Changes runs-on: ubuntu-slim + permissions: + contents: read outputs: backend_changed: ${{ steps.force.outputs.run_all == 'true' || steps.filter.outputs.backend == 'true' }} steps: @@ -66,6 +69,8 @@ jobs: if: needs.changes.outputs.backend_changed == 'true' name: "Python ${{ matrix.python-version }}" runs-on: ubuntu-24.04 + permissions: + contents: read strategy: matrix: python-version: ['3.11', '3.12', '3.13', '3.14'] @@ -143,6 +148,8 @@ jobs: if: needs.changes.outputs.backend_changed == 'true' name: Check project typing runs-on: ubuntu-24.04 + permissions: + contents: read env: DEFAULT_PYTHON: "3.12" steps: diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 43b79728d..aa754a258 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -89,7 +89,7 @@ jobs: push_external="true" ;; esac - case "${{ github.ref }}" in + case "${GITHUB_REF}" in refs/tags/v*|*beta.rc*) push_external="true" ;; @@ -230,8 +230,10 @@ jobs: docker buildx imagetools create ${tags} ${digests} - name: Inspect image + env: + FIRST_TAG: ${{ fromJSON(steps.docker-meta.outputs.json).tags[0] }} run: | - docker buildx imagetools inspect ${{ fromJSON(steps.docker-meta.outputs.json).tags[0] }} + docker buildx imagetools inspect "${FIRST_TAG}" - name: Copy to Docker Hub if: needs.build-arch.outputs.push-external == 'true' env: diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml index a598a3c9d..00b6e00d6 100644 --- a/.github/workflows/ci-docs.yml +++ b/.github/workflows/ci-docs.yml @@ -10,8 +10,6 @@ concurrency: cancel-in-progress: true permissions: contents: read - pages: write - id-token: write env: DEFAULT_UV_VERSION: "0.10.x" DEFAULT_PYTHON_VERSION: "3.12" @@ -105,6 +103,9 @@ jobs: needs: [changes, build] if: github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.changes.outputs.docs_changed == 'true' runs-on: ubuntu-24.04 + permissions: + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} diff --git a/.github/workflows/ci-frontend.yml b/.github/workflows/ci-frontend.yml index 9d4e23a1a..8a8ff6574 100644 --- a/.github/workflows/ci-frontend.yml +++ b/.github/workflows/ci-frontend.yml @@ -10,10 +10,13 @@ on: concurrency: group: frontend-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true +permissions: {} jobs: changes: name: Detect Frontend Changes runs-on: ubuntu-slim + permissions: + contents: read outputs: frontend_changed: ${{ steps.force.outputs.run_all == 'true' || steps.filter.outputs.frontend == 'true' }} steps: @@ -21,6 +24,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 + persist-credentials: false - name: Decide run mode id: force run: | @@ -59,6 +63,8 @@ jobs: if: needs.changes.outputs.frontend_changed == 'true' name: Install Dependencies runs-on: ubuntu-24.04 + permissions: + contents: read steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -89,6 +95,8 @@ jobs: needs: [changes, install-dependencies] if: needs.changes.outputs.frontend_changed == 'true' runs-on: ubuntu-24.04 + permissions: + contents: read steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -120,6 +128,8 @@ jobs: needs: [changes, install-dependencies] if: needs.changes.outputs.frontend_changed == 'true' runs-on: ubuntu-24.04 + permissions: + contents: read strategy: fail-fast: false matrix: @@ -169,6 +179,8 @@ jobs: needs: [changes, install-dependencies] if: needs.changes.outputs.frontend_changed == 'true' runs-on: ubuntu-24.04 + permissions: + contents: read container: mcr.microsoft.com/playwright:v1.58.2-noble env: PLAYWRIGHT_BROWSERS_PATH: /ms-playwright @@ -212,6 +224,8 @@ jobs: needs: [changes, unit-tests, e2e-tests] if: needs.changes.outputs.frontend_changed == 'true' runs-on: ubuntu-24.04 + permissions: + contents: read steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml index 314250719..74a1c1ef8 100644 --- a/.github/workflows/ci-lint.yml +++ b/.github/workflows/ci-lint.yml @@ -9,6 +9,8 @@ on: concurrency: group: lint-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true +permissions: + contents: read jobs: lint: name: Linting via prek diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml index 030e3bcad..eb7d7473d 100644 --- a/.github/workflows/ci-release.yml +++ b/.github/workflows/ci-release.yml @@ -10,10 +10,14 @@ concurrency: env: DEFAULT_UV_VERSION: "0.10.x" DEFAULT_PYTHON_VERSION: "3.12" +permissions: {} jobs: wait-for-docker: name: Wait for Docker Build runs-on: ubuntu-24.04 + permissions: + checks: read + statuses: read steps: - name: Wait for Docker build uses: lewagon/wait-on-check-action@74049309dfeff245fe8009a0137eacf28136cb3c # v1.5.0 @@ -26,6 +30,8 @@ jobs: name: Build Release needs: wait-for-docker runs-on: ubuntu-24.04 + permissions: + contents: read steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -40,8 +46,7 @@ jobs: uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 24.x - cache: 'pnpm' - cache-dependency-path: 'src-ui/pnpm-lock.yaml' + package-manager-cache: false - name: Install frontend dependencies run: cd src-ui && pnpm install - name: Build frontend @@ -56,7 +61,7 @@ jobs: uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1 with: version: ${{ env.DEFAULT_UV_VERSION }} - enable-cache: true + enable-cache: false python-version: ${{ steps.setup-python.outputs.python-version }} - name: Install Python dependencies run: | @@ -129,6 +134,9 @@ jobs: name: Publish Release needs: build-release runs-on: ubuntu-24.04 + permissions: + contents: write + pull-requests: write outputs: prerelease: ${{ steps.get-version.outputs.prerelease }} changelog: ${{ steps.create-release.outputs.body }} @@ -141,9 +149,11 @@ jobs: path: ./ - name: Get version info id: get-version + env: + REF_NAME: ${{ github.ref_name }} run: | - echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT - if [[ "${{ github.ref_name }}" == *"-beta.rc"* ]]; then + echo "version=${REF_NAME}" >> $GITHUB_OUTPUT + if [[ "${REF_NAME}" == *"-beta.rc"* ]]; then echo "prerelease=true" >> $GITHUB_OUTPUT else echo "prerelease=false" >> $GITHUB_OUTPUT @@ -176,6 +186,9 @@ jobs: needs: publish-release if: needs.publish-release.outputs.prerelease == 'false' runs-on: ubuntu-24.04 + permissions: + contents: write + pull-requests: write steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -191,15 +204,17 @@ jobs: uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1 with: version: ${{ env.DEFAULT_UV_VERSION }} - enable-cache: true + enable-cache: false python-version: ${{ env.DEFAULT_PYTHON_VERSION }} - name: Update changelog working-directory: docs + env: + CHANGELOG: ${{ needs.publish-release.outputs.changelog }} run: | git branch ${{ needs.publish-release.outputs.version }}-changelog git checkout ${{ needs.publish-release.outputs.version }}-changelog - echo -e "# Changelog\n\n${{ needs.publish-release.outputs.changelog }}\n" > changelog-new.md + printf '# Changelog\n\n%s\n' "${CHANGELOG}" > changelog-new.md echo "Manually linking usernames" sed -i -r 's|@([a-zA-Z0-9_]+) \(\[#|[@\1](https://github.com/\1) ([#|g' changelog-new.md diff --git a/.github/workflows/ci-static-analysis.yml b/.github/workflows/ci-static-analysis.yml index 99388354a..23da803b8 100644 --- a/.github/workflows/ci-static-analysis.yml +++ b/.github/workflows/ci-static-analysis.yml @@ -33,10 +33,18 @@ jobs: container: image: semgrep/semgrep:1.155.0@sha256:cc869c685dcc0fe497c86258da9f205397d8108e56d21a86082ea4886e52784d if: github.actor != 'dependabot[bot]' + permissions: + contents: read + security-events: write steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Run Semgrep - run: semgrep scan --config auto + run: semgrep scan --config auto --sarif-output results.sarif + - name: Upload results to GitHub code scanning + uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 + if: always() + with: + sarif_file: results.sarif diff --git a/.github/workflows/cleanup-tags.yml b/.github/workflows/cleanup-tags.yml index 426554777..24895ffaa 100644 --- a/.github/workflows/cleanup-tags.yml +++ b/.github/workflows/cleanup-tags.yml @@ -12,6 +12,7 @@ on: concurrency: group: registry-tags-cleanup cancel-in-progress: false +permissions: {} jobs: cleanup-images: name: Cleanup Image Tags for ${{ matrix.primary-name }} diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml index 29b4be02f..559e83917 100644 --- a/.github/workflows/crowdin.yml +++ b/.github/workflows/crowdin.yml @@ -6,6 +6,9 @@ on: push: paths: ['src/locale/**', 'src-ui/messages.xlf', 'src-ui/src/locale/**'] branches: [dev] +permissions: + contents: write + pull-requests: write jobs: synchronize-with-crowdin: name: Crowdin Sync diff --git a/.github/workflows/repo-maintenance.yml b/.github/workflows/repo-maintenance.yml index 1d4903193..42c5e66ab 100644 --- a/.github/workflows/repo-maintenance.yml +++ b/.github/workflows/repo-maintenance.yml @@ -3,10 +3,6 @@ on: schedule: - cron: '0 3 * * *' workflow_dispatch: -permissions: - issues: write - pull-requests: write - discussions: write concurrency: group: lock jobs: @@ -14,6 +10,9 @@ jobs: name: 'Stale' if: github.repository_owner == 'paperless-ngx' runs-on: ubuntu-24.04 + permissions: + issues: write + pull-requests: write steps: - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 with: @@ -36,6 +35,10 @@ jobs: name: 'Lock Old Threads' if: github.repository_owner == 'paperless-ngx' runs-on: ubuntu-24.04 + permissions: + issues: write + pull-requests: write + discussions: write steps: - uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7 # v6.0.0 with: @@ -56,6 +59,8 @@ jobs: name: 'Close Answered Discussions' if: github.repository_owner == 'paperless-ngx' runs-on: ubuntu-24.04 + permissions: + discussions: write steps: - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: @@ -113,6 +118,8 @@ jobs: name: 'Close Outdated Discussions' if: github.repository_owner == 'paperless-ngx' runs-on: ubuntu-24.04 + permissions: + discussions: write steps: - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: @@ -205,6 +212,8 @@ jobs: name: 'Close Unsupported Feature Requests' if: github.repository_owner == 'paperless-ngx' runs-on: ubuntu-24.04 + permissions: + discussions: write steps: - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 000000000..f45e6bbd4 --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,61 @@ +rules: + template-injection: + ignore: + # github.event_name is a GitHub-internal constant (push/pull_request/etc.), + # not attacker-controllable. + - ci-backend.yml:35 + - ci-docker.yml:74 + - ci-docs.yml:33 + - ci-frontend.yml:32 + # github.event.repository.default_branch refers to the target repo's setting, + # which only admins can change; not influenced by fork PR authors. + - ci-backend.yml:47 + - ci-docs.yml:45 + - ci-frontend.yml:44 + # steps.setup-python.outputs.python-version is always a semver string (e.g. "3.12.0") + # produced by actions/setup-python from a hardcoded env var input. + - ci-backend.yml:106 + - ci-backend.yml:121 + - ci-backend.yml:169 + - ci-docs.yml:88 + - ci-docs.yml:92 + - ci-release.yml:69 + - ci-release.yml:78 + - ci-release.yml:90 + - ci-release.yml:96 + - ci-release.yml:229 + # needs.*.result is always one of: success/failure/cancelled/skipped. + - ci-backend.yml:211 + - ci-backend.yml:212 + - ci-backend.yml:216 + - ci-docs.yml:131 + - ci-docs.yml:132 + - ci-frontend.yml:259 + - ci-frontend.yml:260 + - ci-frontend.yml:264 + - ci-frontend.yml:269 + - ci-frontend.yml:274 + - ci-frontend.yml:279 + # needs.changes.outputs.* is always "true" or "false". + - ci-backend.yml:206 + - ci-docs.yml:126 + - ci-frontend.yml:254 + # steps.build.outputs.digest is always a SHA256 digest (sha256:[a-f0-9]{64}). + - ci-docker.yml:152 + # needs.publish-release.outputs.version is the git tag name (e.g. v2.14.0); + # only maintainers can push tags upstream, and the tag pattern excludes + # shell metacharacters. Used in git commands and github-script JS, not eval. + - ci-release.yml:215 + - ci-release.yml:216 + - ci-release.yml:231 + - ci-release.yml:237 + - ci-release.yml:245 + - ci-release.yml:248 + dangerous-triggers: + ignore: + # Both workflows use pull_request_target solely to label/comment on fork PRs + # (requires write-back access unavailable to pull_request). Neither workflow + # checks out PR code or executes anything from the fork — only reads PR + # metadata via context/API. Permissions are scoped to pull-requests: write. + - pr-bot.yml:2 + - project-actions.yml:2