From caa622b4ea93dab4b416092ca73ada17bf9aa188 Mon Sep 17 00:00:00 2001 From: Eric Fennis Date: Thu, 6 Aug 2026 09:57:00 +0200 Subject: [PATCH] ci(security): improve security with adding permissions (#4619) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci(security): add scoped GITHUB_TOKEN permissions to all workflows Every workflow now declares its token permissions explicitly instead of inheriting the repository default, which satisfies OpenSSF Scorecard's token-permissions check. Multi-job and write-scoped workflows use `permissions: {}` as a deny-by-default floor at the top level, with the actual grants declared per job. Consolidating those grants at the workflow level would leak scopes into jobs that don't need them — a `permissions` block replaces rather than merges, so the empty top-level block also makes a forgotten job block fail closed rather than inherit the default. Single-job read-only package workflows keep a plain top-level `contents: read`; there is no write scope to leak, so a second block per file would only add noise. Scopes were matched to what each job's steps actually do: - linting-icons: `contents: read` per job. No `pull-requests: read` — tj-actions/changed-files only needs that with `use_rest_api: true`, and here it reads the local .git. - lint-pr-title: `pull-requests: read`, which action-semantic-pull-request needs to read the PR title. - close-stale-prs: `pull-requests: write` only; the job never checks out. - comment-icon-preview: `actions: read` for the cross-run artifact download, plus `pull-requests: write` to post the comment. - release: `contents: read` for the checkouts, `id-token: write` only on the publishing jobs that use npm provenance, and `contents: write` only on post-release, which uploads the release assets. The prepare job neither checks out nor calls the API, so its empty scope is explicit. - ci: `contents: write` only on create-release; the reusable-workflow calls pass `id-token: write` and `contents: write` down to release.yml, since a called workflow can only reduce what the caller grants. Verified with actionlint (no new findings) and zizmor (no excessive-permissions findings). Co-Authored-By: Claude Opus 5 (1M context) * fix(ci): correct pull_request path filter in the lucide-vue workflow The Vue package lives in packages/vue, not packages/lucide-vue, so the filter never matched and the workflow did not run for changes to the package itself. Co-Authored-By: Claude Opus 5 (1M context) * Remove empty scope in prepare * Patch WeaknessCWE-506 --------- Co-authored-by: Claude Opus 5 (1M context) --- .github/workflows/angular.yml | 3 +++ .github/workflows/ci.yml | 14 +++++++++++--- .../workflows/close-issue-with-banned-phrases.yml | 9 ++++++--- .github/workflows/close-stale-prs.yml | 2 ++ .github/workflows/comment-icon-preview.yml | 6 ++++++ .github/workflows/icons.yml | 3 +++ .github/workflows/labeler.yml | 2 ++ .github/workflows/lint-code.yml | 3 +++ .github/workflows/lint-pr-title.yml | 4 ++++ .github/workflows/linting-icons.yml | 4 +++- .github/workflows/lucide-astro.yml | 3 +++ .github/workflows/lucide-font.yml | 3 +++ .github/workflows/lucide-preact.yml | 3 +++ .github/workflows/lucide-react-native.yml | 3 +++ .github/workflows/lucide-react.yml | 3 +++ .github/workflows/lucide-shared.yml | 3 +++ .github/workflows/lucide-solid.yml | 3 +++ .github/workflows/lucide-static.yml | 3 +++ .github/workflows/lucide-svelte-5.yml | 3 +++ .github/workflows/lucide-vue.yml | 5 ++++- .github/workflows/lucide.yml | 3 +++ .github/workflows/pull-request-icon-preview.yml | 2 ++ .../pull-request-metadata-suggestions.yml | 2 ++ .github/workflows/release.yml | 10 ++++------ .github/workflows/repo-journal.yml | 2 ++ .github/workflows/request-review.yml | 2 ++ 26 files changed, 89 insertions(+), 14 deletions(-) diff --git a/.github/workflows/angular.yml b/.github/workflows/angular.yml index 64ec0c903..afecdf311 100644 --- a/.github/workflows/angular.yml +++ b/.github/workflows/angular.yml @@ -7,6 +7,9 @@ on: - tools/build-icons/** - pnpm-lock.yaml +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5abb506c7..8221d766a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,9 +23,7 @@ on: - beta - alpha -permissions: - id-token: write # Required for OIDC - contents: write +permissions: {} jobs: check-dispatch-gate: @@ -33,6 +31,8 @@ jobs: startsWith(github.event.head_commit.message, 'feat(icons)') && github.event_name != 'workflow_dispatch' runs-on: ubuntu-latest + permissions: + contents: read outputs: RELEASE_ENVIRONMENT: ${{ steps.environment.outputs.RELEASE_ENVIRONMENT }} steps: @@ -60,6 +60,8 @@ jobs: needs: check-dispatch-gate environment: ${{ needs.check-dispatch-gate.outputs.RELEASE_ENVIRONMENT }} runs-on: ubuntu-latest + permissions: + contents: write # Required to create the release and tag outputs: VERSION: ${{ steps.new-version.outputs.NEW_VERSION }} steps: @@ -111,6 +113,9 @@ jobs: needs: create-release uses: './.github/workflows/release.yml' secrets: inherit + permissions: + id-token: write # Required for OIDC (npm provenance) + contents: write # Required to upload release assets with: version: ${{ needs.create-release.outputs.VERSION }} @@ -118,6 +123,9 @@ jobs: if: github.event_name == 'workflow_dispatch' uses: './.github/workflows/release.yml' secrets: inherit + permissions: + id-token: write # Required for OIDC (npm provenance) + contents: write # Required to upload release assets with: version: ${{ inputs.version }} tag: ${{ inputs.tag }} diff --git a/.github/workflows/close-issue-with-banned-phrases.yml b/.github/workflows/close-issue-with-banned-phrases.yml index e6958de12..11e9c5158 100644 --- a/.github/workflows/close-issue-with-banned-phrases.yml +++ b/.github/workflows/close-issue-with-banned-phrases.yml @@ -4,10 +4,16 @@ on: issues: types: [opened] +permissions: {} + +env: + GH_TOKEN: ${{ github.token }} + jobs: block_phrases: runs-on: ubuntu-latest permissions: + contents: read issues: write steps: - name: Checkout repository @@ -43,6 +49,3 @@ jobs: exit 0 fi done - -env: - GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/close-stale-prs.yml b/.github/workflows/close-stale-prs.yml index 0b47315f4..d3a0d5fce 100644 --- a/.github/workflows/close-stale-prs.yml +++ b/.github/workflows/close-stale-prs.yml @@ -3,6 +3,8 @@ on: schedule: - cron: '45 1 * * *' +permissions: {} + jobs: stale: runs-on: ubuntu-latest diff --git a/.github/workflows/comment-icon-preview.yml b/.github/workflows/comment-icon-preview.yml index 1ba1aa89e..c002edb05 100644 --- a/.github/workflows/comment-icon-preview.yml +++ b/.github/workflows/comment-icon-preview.yml @@ -6,9 +6,15 @@ on: types: - completed +permissions: {} + jobs: upload: runs-on: ubuntu-latest + permissions: + contents: read + actions: read # Required to download the artifact from the triggering run + pull-requests: write if: > github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' diff --git a/.github/workflows/icons.yml b/.github/workflows/icons.yml index 42865b6b8..47e5b4493 100644 --- a/.github/workflows/icons.yml +++ b/.github/workflows/icons.yml @@ -9,6 +9,9 @@ on: - tools/rollup-plugins/** - pnpm-lock.yaml +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 7dc5c2879..97fe2cb48 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -2,6 +2,8 @@ name: 'Pull Request Labeler' on: - pull_request_target +permissions: {} + jobs: triage: permissions: diff --git a/.github/workflows/lint-code.yml b/.github/workflows/lint-code.yml index 8fb9a83d5..72a54d980 100644 --- a/.github/workflows/lint-code.yml +++ b/.github/workflows/lint-code.yml @@ -3,6 +3,9 @@ name: Linting PR code on: pull_request: +permissions: + contents: read + jobs: lint-code: runs-on: ubuntu-latest diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index a336a39a5..ad4c61e23 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -7,6 +7,10 @@ on: - edited - reopened +permissions: + contents: read + pull-requests: read # Required to read the PR title + jobs: lint-pr-title: name: PR Title Lint diff --git a/.github/workflows/linting-icons.yml b/.github/workflows/linting-icons.yml index c02613b53..6175bf219 100644 --- a/.github/workflows/linting-icons.yml +++ b/.github/workflows/linting-icons.yml @@ -9,6 +9,8 @@ on: - 'icons/*' - 'lab/*' +permissions: {} + jobs: lint-filenames: name: Lint Filenames @@ -29,7 +31,7 @@ jobs: - name: Get changed files id: changed-files - uses: tj-actions/changed-files@v46 + uses: tj-actions/changed-files@v46.0.1 with: files: | icons/* diff --git a/.github/workflows/lucide-astro.yml b/.github/workflows/lucide-astro.yml index 09bcb19b8..d1d2d1ed9 100644 --- a/.github/workflows/lucide-astro.yml +++ b/.github/workflows/lucide-astro.yml @@ -11,6 +11,9 @@ on: - tools/build-icons/** - pnpm-lock.yaml +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/lucide-font.yml b/.github/workflows/lucide-font.yml index 6842a01ce..1b495dc9e 100644 --- a/.github/workflows/lucide-font.yml +++ b/.github/workflows/lucide-font.yml @@ -9,6 +9,9 @@ on: - tools/build-font/** - pnpm-lock.yaml +permissions: + contents: read + jobs: lucide-font: runs-on: ubuntu-latest diff --git a/.github/workflows/lucide-preact.yml b/.github/workflows/lucide-preact.yml index fc7b1c824..ceb84f091 100644 --- a/.github/workflows/lucide-preact.yml +++ b/.github/workflows/lucide-preact.yml @@ -12,6 +12,9 @@ on: - tools/rollup-plugins/** - pnpm-lock.yaml +permissions: + contents: read + jobs: lucide-preact: runs-on: ubuntu-latest diff --git a/.github/workflows/lucide-react-native.yml b/.github/workflows/lucide-react-native.yml index 99436b33e..67bba34ac 100644 --- a/.github/workflows/lucide-react-native.yml +++ b/.github/workflows/lucide-react-native.yml @@ -12,6 +12,9 @@ on: - tools/rollup-plugins/** - pnpm-lock.yaml +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/lucide-react.yml b/.github/workflows/lucide-react.yml index 77f85f7d0..07b4cc79c 100644 --- a/.github/workflows/lucide-react.yml +++ b/.github/workflows/lucide-react.yml @@ -13,6 +13,9 @@ on: - scripts/generateNextJSAliases.mjs - pnpm-lock.yaml +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/lucide-shared.yml b/.github/workflows/lucide-shared.yml index 56c4b1931..f59868cc7 100644 --- a/.github/workflows/lucide-shared.yml +++ b/.github/workflows/lucide-shared.yml @@ -9,6 +9,9 @@ on: - packages/shared/** - pnpm-lock.yaml +permissions: + contents: read + jobs: test: runs-on: ubuntu-latest diff --git a/.github/workflows/lucide-solid.yml b/.github/workflows/lucide-solid.yml index a8515c761..3272a6aec 100644 --- a/.github/workflows/lucide-solid.yml +++ b/.github/workflows/lucide-solid.yml @@ -12,6 +12,9 @@ on: - tools/rollup-plugins/** - pnpm-lock.yaml +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/lucide-static.yml b/.github/workflows/lucide-static.yml index aa604eaa5..d31955f3b 100644 --- a/.github/workflows/lucide-static.yml +++ b/.github/workflows/lucide-static.yml @@ -7,6 +7,9 @@ on: - tools/build-icons/** - pnpm-lock.yaml +permissions: + contents: read + jobs: lucide-static: runs-on: ubuntu-latest diff --git a/.github/workflows/lucide-svelte-5.yml b/.github/workflows/lucide-svelte-5.yml index 84f3a415f..ca43bafae 100644 --- a/.github/workflows/lucide-svelte-5.yml +++ b/.github/workflows/lucide-svelte-5.yml @@ -12,6 +12,9 @@ on: - tools/rollup-plugins/** - pnpm-lock.yaml +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/lucide-vue.yml b/.github/workflows/lucide-vue.yml index c653429bb..19f782f9f 100644 --- a/.github/workflows/lucide-vue.yml +++ b/.github/workflows/lucide-vue.yml @@ -6,12 +6,15 @@ on: - next pull_request: paths: - - packages/lucide-vue/** + - packages/vue/** - packages/shared/** - tools/build-icons/** - tools/rollup-plugins/** - pnpm-lock.yaml +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/lucide.yml b/.github/workflows/lucide.yml index 578b71f36..bc49dc3c1 100644 --- a/.github/workflows/lucide.yml +++ b/.github/workflows/lucide.yml @@ -11,6 +11,9 @@ on: - tools/rollup-plugins/** - pnpm-lock.yaml +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/pull-request-icon-preview.yml b/.github/workflows/pull-request-icon-preview.yml index 814673683..f2f87ee58 100644 --- a/.github/workflows/pull-request-icon-preview.yml +++ b/.github/workflows/pull-request-icon-preview.yml @@ -5,6 +5,8 @@ on: paths: - 'icons/*.svg' +permissions: {} + jobs: generate-changed-icons-comment: name: Generate Changed Icons Comment diff --git a/.github/workflows/pull-request-metadata-suggestions.yml b/.github/workflows/pull-request-metadata-suggestions.yml index a6b2acfd2..4ccbddf42 100644 --- a/.github/workflows/pull-request-metadata-suggestions.yml +++ b/.github/workflows/pull-request-metadata-suggestions.yml @@ -5,6 +5,8 @@ on: paths: - 'icons/*.json' +permissions: {} + jobs: pull-request-metadata-suggestions: name: Pull Request Metadata Suggestions diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e72e488db..fb5422a1a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,9 +16,7 @@ on: default: 'latest' type: string -permissions: - id-token: write # Required for OIDC - contents: write +permissions: {} jobs: prepare: @@ -26,7 +24,6 @@ jobs: runs-on: ubuntu-latest outputs: VERSION: ${{ steps.get_version.outputs.VERSION }} - steps: - name: Get the version id: get_version @@ -122,6 +119,8 @@ jobs: if: github.repository == 'lucide-icons/lucide' runs-on: ubuntu-latest needs: prepare + permissions: + contents: read steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 @@ -152,8 +151,7 @@ jobs: runs-on: ubuntu-latest needs: [prepare, lucide-font] permissions: - id-token: write # Required for OIDC - contents: write + contents: write # Required to upload the release assets steps: - uses: actions/checkout@v6 - uses: actions/download-artifact@v4 diff --git a/.github/workflows/repo-journal.yml b/.github/workflows/repo-journal.yml index 09638bac5..a1072a3a9 100644 --- a/.github/workflows/repo-journal.yml +++ b/.github/workflows/repo-journal.yml @@ -4,8 +4,10 @@ on: # Run this workflow every Monday at 07:00 UTC schedule: - cron: '0 7 * * 1' + permissions: contents: read + jobs: weekly-summary: runs-on: ubuntu-latest diff --git a/.github/workflows/request-review.yml b/.github/workflows/request-review.yml index 9998d6f83..40bcb8766 100644 --- a/.github/workflows/request-review.yml +++ b/.github/workflows/request-review.yml @@ -5,6 +5,8 @@ on: paths: - icons/*.svg +permissions: {} + jobs: request-review: if: github.event.pull_request.draft == false