diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 4aac949..28ccf09 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -10,9 +10,9 @@ inputs: runs: using: composite steps: - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ inputs.node-version }} cache: pnpm diff --git a/.github/workflows/cf-preview-url.yml b/.github/workflows/cf-preview-url.yml index bf0f651..78be4e7 100644 --- a/.github/workflows/cf-preview-url.yml +++ b/.github/workflows/cf-preview-url.yml @@ -31,7 +31,7 @@ jobs: issues: write steps: - - uses: actions/github-script@v9 + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const run = context.payload.check_run; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51cc500..8daeef4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - uses: ./.github/actions/setup - run: pnpm run lint @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - uses: ./.github/actions/setup - run: pnpm run typecheck @@ -45,7 +45,7 @@ jobs: matrix: node-version: [20, 22] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - uses: ./.github/actions/setup with: node-version: ${{ matrix.node-version }} @@ -55,7 +55,7 @@ jobs: run: pnpm run ${{ matrix.node-version == 22 && 'test:coverage' || 'test' }} - name: Upload coverage to Codecov if: matrix.node-version == 22 - uses: codecov/codecov-action@v7 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: files: ./coverage/lcov.info fail_ci_if_error: false @@ -65,7 +65,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - uses: ./.github/actions/setup - run: pnpm run build:clean @@ -74,7 +74,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - uses: ./.github/actions/setup - run: pnpm run demo:build @@ -83,7 +83,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - uses: ./.github/actions/setup - run: pnpm run build-storybook @@ -92,7 +92,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - uses: ./.github/actions/setup - name: Audit root run: pnpm audit --audit-level=high diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5c970ea..febe510 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -23,11 +23,11 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v6 - - uses: github/codeql-action/init@v4 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + - uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 with: languages: javascript-typescript queries: security-and-quality - - uses: github/codeql-action/analyze@v4 + - uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 with: category: '/language:javascript-typescript' diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index 9f77835..258d245 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Fetch Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v3 + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 0047a03..a94b6f3 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: googleapis/release-please-action@v5 + - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 id: release with: config-file: release-please-config.json @@ -26,7 +26,7 @@ jobs: # already ran in CI against the same commit before release-please # merged it to main, and re-installing Chromium on the npm-token- # bearing job widens the supply-chain surface for no security gain. - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 if: ${{ steps.release.outputs.releases_created == 'true' }} - uses: ./.github/actions/setup @@ -43,17 +43,25 @@ jobs: # Trusted Publisher (OIDC) needs npm CLI >= 11.5.1; Node 22 ships # with npm 10.x. Run the modern CLI via `npx` from its separate - # cache instead of `npm install -g npm@latest`, which races against - # the running npm mid-install ("Cannot find module 'promise-retry'"). - # npm 11+ also requires an explicit --tag for prereleases. + # cache instead of `npm install -g`, which races against the + # running npm mid-install ("Cannot find module 'promise-retry'"). + # + # The CLI version is pinned exactly, never `latest`: this step runs + # with the OIDC publish credential live, so whatever the registry + # serves here can publish as us. Dependabot does not track this + # line; bump NPM_VERSION deliberately, the same way an action SHA + # gets bumped. npm 11+ also requires an explicit --tag for + # prereleases. - name: Publish to npm if: ${{ steps.release.outputs.releases_created == 'true' }} + env: + NPM_VERSION: 12.0.2 run: | version=$(node -p "require('./package.json').version") if [[ "$version" == *-* ]]; then tag="${version#*-}" tag="${tag%%.*}" - npx -y npm@latest publish --provenance --access public --tag "$tag" + npx -y "npm@$NPM_VERSION" publish --provenance --access public --tag "$tag" else - npx -y npm@latest publish --provenance --access public + npx -y "npm@$NPM_VERSION" publish --provenance --access public fi diff --git a/SECURITY-REVIEW.md b/SECURITY-REVIEW.md index 0abb94e..e27c7a5 100644 --- a/SECURITY-REVIEW.md +++ b/SECURITY-REVIEW.md @@ -198,7 +198,7 @@ These were inspected, deemed safe as-shipped, and noted here so future reviewers - **Random IDs**: `nanoid@5.x` (CSPRNG-backed). Not used for security tokens; appropriate. - **Toolbar docs link**: [toolbar.tsx:158-166](src/components/toolbar.tsx:158) is a hardcoded `docs.slack.dev` URL with `rel="noreferrer noopener"`. Safe. - **`.gitignore`**: `.env*` excluded. No `.env*` tracked. -- **Action pinning**: `actions/checkout@v6`, `dependabot/fetch-metadata@v3`, `googleapis/release-please-action@v5`. Major-tag pins, GitHub's recommended practice for trusted publishers. SHA-pinning is the highest-rigor option (see Info-002). +- **Action pinning**: `actions/checkout@v6`, `dependabot/fetch-metadata@v3`, `googleapis/release-please-action@v5`. Major-tag pins, GitHub's recommended practice for trusted publishers. SHA-pinning is the highest-rigor option (see Info-002). _Superseded 2026-09-03: every third-party action is now pinned to a full commit SHA with the release version in a trailing comment (Dependabot keeps both current), and the publish step pins the npm CLI to an exact version instead of `npm@latest`._ - **CI trigger**: uses `pull_request` (NOT the footgun `pull_request_target`). - **`npm publish` provenance**: enabled via `--provenance` in the publish workflow. - **`prepublishOnly`**: runs `build:clean && test` — local `pnpm publish` is gated. @@ -207,7 +207,7 @@ These were inspected, deemed safe as-shipped, and noted here so future reviewers ## Informational notes - **Info-001 — Validator scope.** `@tightknitai/slack-block-kit-validator@0.1.0-alpha.0` is a structural validator. Its `format: "uri"` rule accepts any RFC-3986 URI and is **not** a URI-scheme allowlist. Do not rely on it for sanitizing user URLs at any layer. -- **Info-002 — Action SHA pinning.** Consider SHA-pinning all third-party actions in `.github/workflows/` (`dependabot/fetch-metadata`, `googleapis/release-please-action`) for the highest supply-chain rigor. Major-tag pinning is the current GitHub recommendation and is acceptable. +- **Info-002 — Action SHA pinning.** Consider SHA-pinning all third-party actions in `.github/workflows/` (`dependabot/fetch-metadata`, `googleapis/release-please-action`) for the highest supply-chain rigor. Major-tag pinning is the current GitHub recommendation and is acceptable. _Done 2026-09-03, after an external report (CWE-829) pointed out that `release-please-action@v5` and `npx npm@latest` were both mutable refs executing inside the one job that holds `id-token: write`. Every `uses:` outside `./.github/actions/setup` is now a commit SHA, and the npm CLI is an exact version._ - **Info-003 — CSP guidance for consumers.** This is a UI library; we cannot set HTTP response headers ourselves. Consumers should set a strict CSP (`script-src 'self'`, `style-src 'self' 'unsafe-inline'` to permit our scoped brand `