Skip to content

Update release workflow for NPM OIDC#28

Merged
stwiname merged 1 commit into
mainfrom
npm-gh-oidc-solana
Nov 26, 2025
Merged

Update release workflow for NPM OIDC#28
stwiname merged 1 commit into
mainfrom
npm-gh-oidc-solana

Conversation

@stwiname
Copy link
Copy Markdown
Collaborator

@stwiname stwiname commented Nov 25, 2025

Summary by CodeRabbit

  • Chores
    • Updated GitHub Actions tooling to the latest versions for improved stability and security
    • Upgraded Yarn package manager from 3.1.1 to 4.12.0 with updated configuration settings
    • Simplified CI/CD authentication by removing npm token requirements
    • Consolidated and streamlined publishing workflow for more efficient deployments

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 25, 2025

Walkthrough

The pull request upgrades GitHub Actions and yarn versions across CI/CD workflows (v4→v5 for standard actions, v1→v2 for Discord), restructures the publishing pipeline by consolidating separate prerelease and release workflows into a unified publish workflow, removes npm-token inputs from custom actions, and updates yarn configuration to version 4.12.0.

Changes

Cohort / File(s) Change Summary
GitHub Actions Version Bumps (v4→v5)
.github/workflows/benchmark.yml, .github/workflows/gh-release.yml, .github/workflows/node-docker.yml, .github/workflows/pr.yml, .github/workflows/sync-deps.yml
Updated actions/checkout and actions/setup-node from v4 to v5 across multiple workflows; no logic changes.
Third-Party Action Updates
.github/workflows/discord.yml
Upgraded Discord webhook action from rjstone/discord-webhook-notify@v1 to @v2.
Custom Action Input Removal
.github/actions/create-prerelease/action.yml, .github/actions/create-release/action.yml
Removed required npm-token input and associated NPM_TOKEN environment variable; publish commands remain but without token passing.
Workflow Restructuring
.github/workflows/prerelease.yml
Removed entire prerelease workflow that monitored main branch commits and created prerelease PRs conditionally.
Workflow Restructuring
.github/workflows/release.yml
Removed entire release workflow that triggered on [release] commit messages and published to npm.
Workflow Restructuring
.github/workflows/publish.yml
New consolidated workflow replacing prerelease and release logic; features multi-job pipeline (pre-CI, setup & detect changes, conditional release/prerelease publish) with commit message-based branching.
Yarn & Package Configuration
.yarnrc.yml, package.json
Updated yarn from 3.1.1 to 4.12.0; modified .yarnrc.yml with new settings (compressionLevel, enableGlobalCache, nodeLinker, changesetIgnorePatterns) and removed plugins block.

Sequence Diagram(s)

sequenceDiagram
    participant GitHub
    participant Workflow as publish.yml
    participant Setup as Setup & Detect<br/>Changes
    participant Release as Release Publish<br/>Job
    participant Prerelease as Prerelease Publish<br/>Job
    participant NPM as npm Registry
    participant Repo as Repository

    GitHub->>Workflow: Push to main
    Workflow->>Workflow: Pre-CI: Extract commit message
    Workflow->>Setup: Checkout & detect changes<br/>(types, common-solana, node)
    Setup-->>Workflow: Expose changed flags
    
    alt Commit message starts with [release]
        Workflow->>Release: Condition: is release & repo matches
        Release->>Release: Setup Node.js + build
        Release->>NPM: Publish changed packages
        NPM-->>Release: Published
    else Normal commit
        Workflow->>Prerelease: Condition: not release & repo matches
        Prerelease->>Prerelease: Setup Node.js + build
        Prerelease->>NPM: Bump & deploy prerelease
        NPM-->>Prerelease: Deployed
        Prerelease->>Repo: Commit with [SKIP CI] tag
        Repo-->>Prerelease: Committed
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Workflow consolidation logic: The new publish.yml introduces conditional job execution based on commit messages and change detection; requires verification that the consolidated logic correctly replaces both removed workflows without functional gaps.
  • npm-token removal implications: Verify that removing npm-token input from both custom actions does not break downstream npm publishing steps or require authentication adjustments elsewhere.
  • Yarn 4.12.0 upgrade: Ensure .yarnrc.yml configuration changes (new settings like nodeLinker: node-modules and plugin removal) are compatible with the project's build process and dependency resolution.
  • Workflow trigger conditions: Confirm that pre-CI commit message extraction and change detection logic in publish.yml correctly differentiates between release and prerelease execution paths.

Poem

🐰 Workflows dance in grand new ways,
Consolidated through thoughtful maze,
Yarn ascends with version's climb,
Actions hop to v5 sublime,
Tokens fade, yet builds prevail,
Hop and cheers! Let pipes set sail! 🎉

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Update release workflow for NPM OIDC' accurately reflects the main changes: removing npm-token inputs from release/prerelease actions and replacing the separate release/prerelease workflows with a consolidated publish workflow that uses NPM OIDC authentication.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch npm-gh-oidc-solana

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

Coverage report

Caution

Test run failed

St.
Category Percentage Covered / Total
🔴 Statements 55.33% 2360/4265
🟡 Branches 67.54% 231/342
🔴 Functions 49.76% 102/205
🔴 Lines 55.33% 2360/4265

Test suite run failed

Failed tests: 4/54. Failed suites: 1/8.
  ● test solana project.yaml › could get options in template from its deployment

    expect(received).toContain(expected) // indexOf

    Expected substring: "abi: Pool"
    Received string:    "dataSources:
      - kind: solana/Runtime
        mapping:
          file: ./dist/index.js
          handlers:
            - filter: {}
              handler: handlePoolCreated
              kind: solana/LogHandler
        startBlock: 12369621
      - kind: solana/Runtime
        mapping:
          file: ./dist/index.js
          handlers:
            - filter: {}
              handler: handleIncreaseLiquidity
              kind: solana/LogHandler
            - filter: {}
              handler: handleDecreaseLiquidity
              kind: solana/LogHandler
            - filter: {}
              handler: handleCollect
              kind: solana/LogHandler
            - filter: {}
              handler: handleTransfer
              kind: solana/LogHandler
        startBlock: 12369651
    network:
      chainId: '1'
    runner:
      node:
        name: '@subql/node-solana'
        version: '*'
      query:
        name: '@subql/query'
        version: '*'
    schema:
      file: ./schema.graphql
    specVersion: 1.0.0
    templates:
      - kind: solana/Runtime
        mapping:
          file: ./dist/index.js
          handlers:
            - filter: {}
              handler: handleInitialize
              kind: solana/LogHandler
            - filter: {}
              handler: handleSwap
              kind: solana/LogHandler
            - filter: {}
              handler: handleMint
              kind: solana/LogHandler
            - filter: {}
              handler: handleBurn
              kind: solana/LogHandler
            - filter: {}
              handler: handleFlash
              kind: solana/LogHandler
        name: Pool
    "

      41 |     const manifest = loadSolanaProjectManifest(path.join(projectsDir, 'project_1.0.0.yaml'));
      42 |     const deployment = manifest.toDeployment();
    > 43 |     expect(deployment).toContain('abi: Pool');
         |                        ^
      44 |   });
      45 | });
      46 |

      at Object.<anonymous> (packages/common-solana/src/project/project.spec.ts:43:24)

  ● project.yaml › can validate project.yaml

    Not implemented

      48 |   it('can validate project.yaml', () => {
      49 |     // TODO this should catch a specific error, the file doesn't currently exist
    > 50 |     throw new Error('Not implemented');
         |           ^
      51 |     expect(() => loadSolanaProjectManifest(path.join(projectsDir, 'project_falsy.yaml'))).toThrow();
      52 |     expect(() => loadSolanaProjectManifest(path.join(projectsDir, 'project_falsy_array.yaml'))).toThrow();
      53 |   });

      at Object.<anonymous> (packages/common-solana/src/project/project.spec.ts:50:11)

  ● project.yaml › can fail validation if version not supported

    Not implemented

      55 |   it('can fail validation if version not supported', () => {
      56 |     // TODO this should catch a specific error, the file doesn't currently exist
    > 57 |     throw new Error('Not implemented');
         |           ^
      58 |     expect(() => loadSolanaProjectManifest(path.join(projectsDir, 'project_invalid_version.yaml'))).toThrow('');
      59 |   });
      60 |

      at Object.<anonymous> (packages/common-solana/src/project/project.spec.ts:57:11)

  ● project.yaml › get v1.0.0 deployment mapping filter

    expect(received).toContain(expected) // indexOf

    Expected substring: "Transfer (address from, address to, uint256 tokenId)"
    Received string:    "dataSources:
      - kind: solana/Runtime
        mapping:
          file: ./dist/index.js
          handlers:
            - filter: {}
              handler: handlePoolCreated
              kind: solana/LogHandler
        startBlock: 12369621
      - kind: solana/Runtime
        mapping:
          file: ./dist/index.js
          handlers:
            - filter: {}
              handler: handleIncreaseLiquidity
              kind: solana/LogHandler
            - filter: {}
              handler: handleDecreaseLiquidity
              kind: solana/LogHandler
            - filter: {}
              handler: handleCollect
              kind: solana/LogHandler
            - filter: {}
              handler: handleTransfer
              kind: solana/LogHandler
        startBlock: 12369651
    network:
      chainId: '1'
    runner:
      node:
        name: '@subql/node-solana'
        version: '*'
      query:
        name: '@subql/query'
        version: '*'
    schema:
      file: ./schema.graphql
    specVersion: 1.0.0
    templates:
      - kind: solana/Runtime
        mapping:
          file: ./dist/index.js
          handlers:
            - filter: {}
              handler: handleInitialize
              kind: solana/LogHandler
            - filter: {}
              handler: handleSwap
              kind: solana/LogHandler
            - filter: {}
              handler: handleMint
              kind: solana/LogHandler
            - filter: {}
              handler: handleBurn
              kind: solana/LogHandler
            - filter: {}
              handler: handleFlash
              kind: solana/LogHandler
        name: Pool
    "

      70 |     const deploymentString = manifestVersioned.toDeployment();
      71 |     expect(filter).not.toBeNull();
    > 72 |     expect(deploymentString).toContain('Transfer (address from, address to, uint256 tokenId)');
         |                              ^
      73 |   });
      74 |
      75 |   it('can convert genesis hash in v1.0.0 to chainId in deployment', () => {

      at Object.<anonymous> (packages/common-solana/src/project/project.spec.ts:72:30)

Report generated by 🧪jest coverage report action from b200f42

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/gh-release.yml (1)

23-36: Perfect! I've completed the verification. The review comment is correct - the gh-release.yml workflow is missing critical OIDC authentication configuration that is present in the publish.yml workflow.

Key Findings:

  1. publish.yml has OIDC permissions (lines 9-11):

    permissions:
      id-token: write  # Required for OIDC
      contents: read
  2. gh-release.yml is missing these permissions - it has no permissions block at all

  3. Both workflows use the same create-release action that calls yarn npm publish, but only publish.yml can provide OIDC tokens to authenticate with npm

  4. Impact: The gh-release.yml workflow will fail during npm publishing with authentication errors because it lacks the id-token: write permission needed for OIDC


Add OIDC permissions to the gh-release.yml workflow.

The workflow lacks the id-token: write permission required for OIDC authentication with npm. Add the permissions block after the trigger section:

permissions:
  id-token: write  # Required for OIDC
  contents: read

This matches the configuration in publish.yml and enables the create-release action to authenticate with npm via OIDC when executing yarn npm publish.

🧹 Nitpick comments (1)
.github/workflows/benchmark.yml (1)

82-83: Consider updating deprecated GitHub Actions commands.

Lines 82–83 use the deprecated ::set-output and ::add-mask syntax. While not changed in this PR, consider a follow-up refactor to use modern equivalents:

-          echo "::set-output name=network-endpoint::${NETWORK_ENDPOINT}"
-          echo "::add-mask::${NETWORK_ENDPOINT}"
+          echo "network-endpoint=${NETWORK_ENDPOINT}" >> $GITHUB_OUTPUT
+          echo "::add-mask::${NETWORK_ENDPOINT}"

(Note: add-mask does not have a direct replacement yet; this is a GitHub Actions limitation.)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67e420d and b200f42.

⛔ Files ignored due to path filters (7)
  • .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs is excluded by !**/.yarn/**
  • .yarn/plugins/@yarnpkg/plugin-typescript.cjs is excluded by !**/.yarn/**
  • .yarn/plugins/@yarnpkg/plugin-version.cjs is excluded by !**/.yarn/**
  • .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs is excluded by !**/.yarn/**
  • .yarn/releases/yarn-3.1.1.cjs is excluded by !**/.yarn/**
  • .yarn/releases/yarn-4.12.0.cjs is excluded by !**/.yarn/**
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (13)
  • .github/actions/create-prerelease/action.yml (2 hunks)
  • .github/actions/create-release/action.yml (1 hunks)
  • .github/workflows/benchmark.yml (2 hunks)
  • .github/workflows/discord.yml (1 hunks)
  • .github/workflows/gh-release.yml (1 hunks)
  • .github/workflows/node-docker.yml (2 hunks)
  • .github/workflows/pr.yml (2 hunks)
  • .github/workflows/prerelease.yml (0 hunks)
  • .github/workflows/publish.yml (1 hunks)
  • .github/workflows/release.yml (0 hunks)
  • .github/workflows/sync-deps.yml (1 hunks)
  • .yarnrc.yml (2 hunks)
  • package.json (1 hunks)
💤 Files with no reviewable changes (2)
  • .github/workflows/prerelease.yml
  • .github/workflows/release.yml
🧰 Additional context used
🪛 actionlint (1.7.8)
.github/workflows/publish.yml

31-31: "github.event.head_commit.message" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Coverage report
🔇 Additional comments (13)
.github/workflows/benchmark.yml (2)

68-71: Approved: checkout action upgrade to v5.

The upgrade from actions/checkout@v4 to actions/checkout@v5 is in scope with the PR objectives. The configuration (fetch-depth, token) remains compatible and unchanged.


85-90: Approved: environment variable export and formatting.

Line 85 correctly uses the modern $GITHUB_ENV approach for exporting NETWORK_ENDPOINT. The formatting adjustment to line 90 is a minor whitespace normalization with no semantic impact.

.github/workflows/sync-deps.yml (1)

18-20: Standard actions maintenance.

Routine version bumps for checkout@v5 and setup-node@v5 maintain API compatibility. The jump from setup-node@v3→v5 is larger, but v5 is backwards-compatible with the current invocation pattern.

.github/workflows/node-docker.yml (1)

19-19: Standard actions maintenance in Docker build workflow.

The upgrade to actions/checkout@v5 is routine and maintains compatibility with the current parameter usage (fetch-depth and token handling).

Also applies to: 42-42

.github/workflows/gh-release.yml (1)

11-11: Standard actions maintenance.

The upgrade to actions/checkout@v5 and actions/setup-node@v5 maintains API compatibility and is routine maintenance.

Also applies to: 16-16

.github/workflows/pr.yml (1)

15-17: I'll verify the GitHub Actions version upgrades by examining the workflow file and researching the changes in v5.
<function_calls>

GitHub Actions checkout@v5 changes release notes

</function_calls>

<function_calls>

GitHub Actions setup-node@v5 breaking changes

</function_calls>

<function_calls>


#!/bin/bash

Examine the workflow file to confirm the changes

if [ -f .github/workflows/pr.yml ]; then
echo "=== Lines 10-20 (around line 15-17) ==="
sed -n '10,20p' .github/workflows/pr.yml
echo ""
echo "=== Lines 45-55 (around line 49-52) ==="
sed -n '45,55p' .github/workflows/pr.yml
else
echo "Workflow file not found"
fi


</function_calls>

Please wait while I gather information about the GitHub Actions upgrades and examine the workflow file.

.yarnrc.yml (1)

5-22: <function_calls>

Yarn 4.0 release notes changelog version command plugin removal
``` "yarn version" command Yarn 4 how to bump version without plugin ```
package.json (1)

60-60: Yarn 4 upgrade appears properly configured; no breaking change violations detected.

The upgrade from yarn@3.1.1 to yarn@4.12.0 is well-implemented in this repository. Verification confirms:

  • .yarnrc.yml is properly configured with nodeLinker: node-modules (avoiding PnP complexity) and correct yarnPath pointing to yarn-4.12.0.cjs
  • Node version: All workflows use lts/* (Node 20+), meeting Yarn 4's Node 18+ minimum requirement
  • Yarn commands: No deprecated Yarn 3 flags (e.g., --frozen-lockfile) are used in workflows; all commands (yarn install, yarn build, yarn workspaces foreach) are compatible with Yarn 4
  • No custom plugins: No evidence of plugins requiring API updates

The repository's Yarn 4 migration follows recommended practices and no actionable issues remain.

.github/workflows/discord.yml (1)

14-14: API compatibility confirmed — no action required.

The upgrade to rjstone/discord-webhook-notify@v2 is backward-compatible. Version 2 maintains support for all current input parameters (webhookUrl, color, avatarUrl, details, description, footer), so no workflow modifications are needed. The action's maintainers confirm that v2 is stable for existing users, with breaking changes expected only in future major versions (v3+).

.github/actions/create-release/action.yml (1)

16-18: Verify npm OIDC authentication is properly configured end-to-end.

With the removal of the NPM_TOKEN environment variable, this action now relies on npm CLI automatically detecting OIDC environments and using them for authentication. However, successful OIDC authentication requires npm CLI version 11.5.1 or later and that each package has a trusted publisher configured on npmjs.com.

The workflow calling this action (publish.yml) correctly sets the required id-token: write permission, but ensure:

  1. The npm/yarn version in use includes npm CLI >= 11.5.1
  2. Each package (types, common-solana, node) has a trusted publisher configured on npmjs.com matching the workflow name
  3. The workflow filename in npmjs.com settings exactly matches publish.yml (filename is case-sensitive)
.github/workflows/publish.yml (3)

122-173: Verify prerelease action also doesn't require npm credentials.

The prerelease job similarly relies on OIDC authentication via the create-prerelease action (which the AI summary indicates also had npm-token removed). Ensure the same npm >= 11.5.1 requirement is met, and that each package has trusted publisher configuration allowing prerelease publishing from the publish.yml workflow.

The overall structure of consolidating prerelease and release jobs into a single workflow is good, and the use of change detection flags to conditionally publish only modified packages is sound. Pending verification of npm versions and OIDC setup.


9-11: LGTM: OIDC permissions correctly configured.

The workflow properly declares id-token: write permission, which is the critical requirement that allows GitHub Actions to generate OIDC tokens for npm package publishing. The prerelease job correctly uses an explicit secrets.REPO_TOKEN for git operations.


74-120: Based on my verification, I can now provide the rewritten review comment:


The npm version concern is unfounded—the workflow explicitly updates npm to the latest version.

The release job (line 93) runs npm install -g npm@latest, which ensures npm is updated to the latest version after Node.js setup. Node.js LTS v24 bundles npm v11.6.2, and the explicit upgrade to npm@latest guarantees the version will exceed the minimum v11.5.1 required for OIDC trusted publishing. Yarn 4.12.0 does not bundle npm; it uses the system npm, so the OIDC authentication will work correctly with this setup.

Likely an incorrect or invalid review comment.

description: 'token to push to npm registry'
required: true


Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Script executed:

#!/bin/bash
# Find workflows calling create-prerelease or create-release actions
echo "=== Searching for workflows calling create-prerelease or create-release ==="
fd -t f '\.(yml|yaml)$' '.github/workflows' | while read workflow; do
  if grep -l 'create-prerelease\|create-release' "$workflow" 2>/dev/null; then
    echo "Found workflow: $workflow"
  fi
done

echo ""
echo "=== Full content of workflows calling these actions ==="
fd -t f '\.(yml|yaml)$' '.github/workflows' -x grep -l 'create-prerelease\|create-release' {} \; | while read workflow; do
  echo "--- File: $workflow ---"
  cat -n "$workflow"
  echo ""
done

Repository: subquery/subql-solana


<shell_result>
Repository: subquery/subql-solana
Exit code: 0

stdout:

=== Searching for workflows calling create-prerelease or create-release ===
.github/workflows/gh-release.yml
Found workflow: .github/workflows/gh-release.yml
.github/workflows/publish.yml
Found workflow: .github/workflows/publish.yml

=== Full content of workflows calling these actions ===
--- File: .github/workflows/gh-release.yml ---
     1	name: "Github Release"
     2	on:
     3	  workflow_dispatch:
     4	
     5	jobs:
     6	  github-release:
     7	    name: github-release
     8	    runs-on: ubuntu-latest
     9	    steps:
    10	      #Check out
    11	      - uses: actions/checkout@v5
    12	        with:
    13	          fetch-depth: 100
    14	
    15	      - name: Setup Node.js environment
    16	        uses: actions/setup-node@v5
    17	        with:
    18	          node-version: lts/*
    19	
    20	      - run: yarn install
    21	
    22	      #Create github releases
    23	      - uses: ./.github/actions/create-release
    24	        with:
    25	          package-path: packages/common-solana
    26	          repo-token: ${{ secrets.REPO_TOKEN }}
    27	
    28	      - uses: ./.github/actions/create-release
    29	        with:
    30	          package-path: packages/node
    31	          repo-token: ${{ secrets.REPO_TOKEN }}
    32	
    33	      - uses: ./.github/actions/create-release
    34	        with:
    35	          package-path: packages/types
    36	          repo-token: ${{ secrets.REPO_TOKEN }}

--- File: .github/workflows/publish.yml ---
     1	name: "Publish"
     2	on:
     3	  push:
     4	    branches:
     5	      - main
     6	    paths-ignore:
     7	      - ".github/workflows/**"
     8	
     9	permissions:
    10	  id-token: write  # Required for OIDC
    11	  contents: read
    12	
    13	concurrency:
    14	  group: publish
    15	  cancel-in-progress: false
    16	
    17	jobs:
    18	
    19	  pre-ci:
    20	    name: Pre-CI (Extract Commit Message)
    21	    runs-on: ubuntu-latest
    22	    timeout-minutes: 1
    23	    outputs:
    24	      commit-message: ${{ steps.get_commit_message.outputs.commit-message }}
    25	    steps:
    26	      - uses: actions/checkout@v5
    27	        with:
    28	          fetch-depth: 0
    29	
    30	      - id: get_commit_message
    31	        run: |
    32	          COMMIT_MSG_TEMP="${{ github.event.head_commit.message }}"
    33	          if [ -n "$COMMIT_MSG_TEMP" ]
    34	          then
    35	            commit_msg="$COMMIT_MSG_TEMP"
    36	            echo "commit-message=${commit_msg}" | head -n 1 >> "$GITHUB_OUTPUT"
    37	          else
    38	            commit_message=$(git log -1 --pretty=%B | head -n 1)
    39	            echo "commit-message=$commit_message" >> "$GITHUB_OUTPUT"
    40	          fi
    41	
    42	      - name: Debug commit message
    43	        run: |
    44	          echo "Commit message: ${{ steps.get_commit_message.outputs.commit-message }}"
    45	
    46	  setup:
    47	    name: Setup & Detect Changes
    48	    needs: pre-ci
    49	    runs-on: ubuntu-latest
    50	    outputs:
    51	      changed-types: ${{ steps.changed-types.outputs.changed }}
    52	      changed-common-solana: ${{ steps.changed-common-solana.outputs.changed }}
    53	      changed-node: ${{ steps.changed-node.outputs.changed }}
    54	    steps:
    55	      - uses: actions/checkout@v5
    56	        with:
    57	          fetch-depth: 100 # Needed to detect changes by having commit history
    58	
    59	      - uses: marceloprado/has-changed-path@v1
    60	        id: changed-types
    61	        with:
    62	          paths: packages/types
    63	
    64	      - uses: marceloprado/has-changed-path@v1
    65	        id: changed-common-solana
    66	        with:
    67	          paths: packages/common-solana
    68	
    69	      - uses: marceloprado/has-changed-path@v1
    70	        id: changed-node
    71	        with:
    72	          paths: packages/node
    73	
    74	  release:
    75	    name: Release Publish
    76	    needs: [pre-ci, setup]
    77	    if: >
    78	      !startsWith(github.event.head_commit.message, '[SKIP CI]')
    79	      && startsWith(github.event.head_commit.message, '[release]')
    80	      && github.repository == 'subquery/subql-solana'
    81	    runs-on: ubuntu-latest
    82	    steps:
    83	      - uses: actions/checkout@v5
    84	        with:
    85	          fetch-depth: 0
    86	
    87	      - name: Setup Node.js environment
    88	        uses: actions/setup-node@v5
    89	        with:
    90	          node-version: lts/*
    91	
    92	      - name: Update npm
    93	        run: npm install -g npm@latest
    94	
    95	      - run: yarn
    96	
    97	      - name: build
    98	        run: yarn build
    99	
   100	      # Publish to npm and github releases
   101	      - name: Publish Types
   102	        if: needs.setup.outputs.changed-types == 'true'
   103	        uses: ./.github/actions/create-release
   104	        with:
   105	          package-path: packages/types
   106	          repo-token: ${{ secrets.REPO_TOKEN }}
   107	
   108	      - name: Publish Common Solana
   109	        if: needs.setup.outputs.changed-common-solana == 'true'
   110	        uses: ./.github/actions/create-release
   111	        with:
   112	          package-path: packages/common-solana
   113	          repo-token: ${{ secrets.REPO_TOKEN }}
   114	
   115	      - name: Publish Node
   116	        if: needs.setup.outputs.changed-node == 'true'
   117	        uses: ./.github/actions/create-release
   118	        with:
   119	          package-path: packages/node
   120	          repo-token: ${{ secrets.REPO_TOKEN }}
   121	
   122	  prerelease:
   123	    name: Prerelease Publish
   124	    needs: [pre-ci, setup]
   125	    if: >
   126	      !startsWith(needs.pre-ci.outputs.commit-message, '[SKIP CI]')
   127	      && !startsWith(needs.pre-ci.outputs.commit-message, '[release]')
   128	      && github.repository == 'subquery/subql-solana'
   129	    runs-on: ubuntu-latest
   130	    steps:
   131	      - uses: actions/checkout@v5
   132	        with:
   133	          fetch-depth: 0
   134	          token: ${{ secrets.REPO_TOKEN }} # Needed to push changes back to repo
   135	
   136	      - name: Setup Node.js environment
   137	        uses: actions/setup-node@v5
   138	        with:
   139	          node-version: lts/*
   140	
   141	      - name: Update npm
   142	        run: npm install -g npm@latest
   143	
   144	      - run: yarn
   145	
   146	      - name: build
   147	        run: yarn build
   148	
   149	      # Prerelease publish steps
   150	      - name: Bump types & deploy
   151	        if: needs.setup.outputs.changed-types == 'true'
   152	        uses: ./.github/actions/create-prerelease
   153	        with:
   154	          package-path: packages/types
   155	
   156	      - name: Bump common Solana & deploy
   157	        if: needs.setup.outputs.changed-common-solana == 'true'
   158	        uses: ./.github/actions/create-prerelease
   159	        with:
   160	          package-path: packages/common-solana
   161	
   162	      - name: Bump node & deploy
   163	        if: needs.setup.outputs.changed-node == 'true'
   164	        uses: ./.github/actions/create-prerelease
   165	        with:
   166	          package-path: packages/node
   167	
   168	
   169	      - name: Commit changes
   170	        uses: EndBug/add-and-commit@v9
   171	        with:
   172	          message: "[SKIP CI] Prerelease"
   173	          default_author: github_actions

</shell_result>


Script executed:

#!/bin/bash
# First, check the full create-prerelease action.yml file
echo "=== Full create-prerelease action.yml ==="
cat -n .github/actions/create-prerelease/action.yml

echo ""
echo "=== Searching for .npmrc or npmrc configuration files ==="
fd -t f '\.npmrc|npmrc' .

echo ""
echo "=== Checking for OIDC or getIDToken references in workflows ==="
rg -n 'getIDToken|OIDC|npm.*token|OIDC' '.github/workflows/' -i

echo ""
echo "=== Checking for npm configuration steps in publish workflow ==="
rg -n -A5 -B5 'npm.*config|\.npmrc' '.github/workflows/publish.yml'

Repository: subquery/subql-solana


<shell_result>
Repository: subquery/subql-solana
Exit code: 1

stdout:

=== Full create-prerelease action.yml ===
     1	# Composite action needed to access github context
     2	
     3	# This is to compensate for yarn 3 issue https://github.com/yarnpkg/berry/issues/3868
     4	name: 'Remove Stable Versions'
     5	description: 'This will remove stableVersion from packages for prerelease'
     6	inputs:
     7	  package-path:
     8	    description: 'package path to run action e.g. package/common'
     9	    required: true
    10	
    11	runs:
    12	  using: "composite"
    13	  steps:
    14	    - working-directory: ${{ github.workspace }}
    15	      run: node ${{ github.action_path }}/remove-stable-version.js ${{ github.workspace }}/${{ inputs.package-path }}
    16	      shell: bash
    17	
    18	    - working-directory: ${{ inputs.package-path }}
    19	      run: echo "Changes exist in ${{ inputs.package-path }}" && yarn version prerelease && yarn npm publish --access public --tag dev
    20	      shell: bash

=== Searching for .npmrc or npmrc configuration files ===

=== Checking for OIDC or getIDToken references in workflows ===
.github/workflows/publish.yml:10:  id-token: write  # Required for OIDC

=== Checking for npm configuration steps in publish workflow ===

</shell_result>


Script executed:

#!/bin/bash
# Check if there are any other npm/node setup actions or scripts
echo "=== Checking actions/setup-node@v5 for OIDC capabilities ==="
rg -n 'registry-url|scope\|npm-auth' '.github/workflows/publish.yml'

echo ""
echo "=== Checking remove-stable-version.js for any env setup ==="
if [ -f '.github/actions/create-prerelease/remove-stable-version.js' ]; then
  head -50 '.github/actions/create-prerelease/remove-stable-version.js'
fi

echo ""
echo "=== Check full create-release action for comparison ==="
cat -n '.github/actions/create-release/action.yml'

Repository: subquery/subql-solana


<shell_result>
Repository: subquery/subql-solana
Exit code: 0

stdout:

=== Checking actions/setup-node@v5 for OIDC capabilities ===

=== Checking remove-stable-version.js for any env setup ===
const fs = require('fs');
const myArgs = process.argv.slice(2);
const pJson = require(`${myArgs[0]}/package.json`)

if (pJson.stableVersion){
    delete pJson.stableVersion
    fs.writeFileSync(`${myArgs[0]}/package.json`, JSON.stringify(pJson, null, 2))
}

=== Check full create-release action for comparison ===
     1	# Composite action needed to access github context
     2	
     3	name: 'Create Release'
     4	description: 'This will publish to NPM registry and create Github release'
     5	inputs:
     6	  package-path: # id of input
     7	    description: 'package path to run action e.g. package/common'
     8	    required: true
     9	  repo-token:
    10	    description: 'token to create github release'
    11	    required: true
    12	
    13	runs:
    14	  using: "composite"
    15	  steps:
    16	    - working-directory: ${{ inputs.package-path }}
    17	      run: echo "Changes exist in ${{ inputs.package-path }}" && yarn npm publish --access public
    18	      shell: bash
    19	
    20	    - working-directory: ${{ github.workspace }}
    21	      run: node ${{ github.action_path }}/gh-release-script.js ${{ github.workspace }}/${{ inputs.package-path }} ${{ github.sha }}
    22	      env:
    23	        REPO_TOKEN: ${{ inputs.repo-token }}
    24	      shell: bash

</shell_result>


Script executed:

#!/bin/bash
echo "=== Searching for yarn config files ==="
fd -t f '\.yarnrc' .

echo ""
echo "=== Searching for .yarnrc.yml ==="
fd -t f '\.yarnrc\.yml' .

echo ""
echo "=== Full content if found ==="
if [ -f '.yarnrc.yml' ]; then
  cat -n '.yarnrc.yml'
fi

echo ""
echo "=== Checking for any npmrc or yarnrc in .github directory ==="
find .github -type f -name '.*rc*' 2>/dev/null

echo ""
echo "=== Check package.json scripts for npm config ==="
rg -n '"version|"publish' package.json -A2 -B2

Repository: subquery/subql-solana


<shell_result>
Repository: subquery/subql-solana
Exit code: 1

stdout:

=== Searching for yarn config files ===

=== Searching for .yarnrc.yml ===

=== Full content if found ===
     1	changesetBaseRefs:
     2	  - main
     3	  - origin/main
     4	
     5	changesetIgnorePatterns:
     6	  - .yarn/**/*
     7	
     8	compressionLevel: mixed
     9	
    10	enableGlobalCache: false
    11	
    12	enableImmutableInstalls: false
    13	
    14	enableProgressBars: false
    15	
    16	nodeLinker: node-modules
    17	
    18	npmAuthToken: "${NPM_TOKEN:-}"
    19	
    20	npmPublishRegistry: "https://registry.npmjs.org"
    21	
    22	yarnPath: .yarn/releases/yarn-4.12.0.cjs

=== Checking for any npmrc or yarnrc in .github directory ===

=== Check package.json scripts for npm config ===

</shell_result>


Perfect. Now I have all the information needed. The verification reveals a critical issue: The .yarnrc.yml file expects NPM_TOKEN environment variable (line 18), but the workflow never sets it despite having the id-token permission.

Here's the final rewritten review comment:


Verify OIDC token is exported to NPM_TOKEN environment variable before calling create-prerelease.

The npm-token input has been removed, aligning with the PR objective to migrate to NPM OIDC. However, the migration is incomplete: .yarnrc.yml (line 18) requires npmAuthToken: "${NPM_TOKEN:-}" to be set, but the publish.yml workflow never exports the OIDC token.

Current state:

  • publish.yml has permissions: id-token: write (line 10) ✓
  • Missing: No step calls core.getIDToken() to retrieve and export the OIDC token as NPM_TOKEN
  • create-prerelease action receives no authentication mechanism
  • yarn npm publish will fail with 401 Unauthorized when NPM_TOKEN evaluates to empty

Required fix: Add a step in the prerelease job to export the OIDC token:

- name: Get OIDC token
  id: oidc
  run: echo "NPM_TOKEN=$(curl -s -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "${ACTIONS_ID_TOKEN_REQUEST_URL}?audience=npm")" >> $GITHUB_ENV
  env:
    ACTIONS_ID_TOKEN_REQUEST_URL: ${{ env.ACTIONS_ID_TOKEN_REQUEST_URL }}
    ACTIONS_ID_TOKEN_REQUEST_TOKEN: ${{ env.ACTIONS_ID_TOKEN_REQUEST_TOKEN }}

Or use an action like actions/create-github-app-token that exports NPM_TOKEN before calling create-prerelease.

Also applies to: 19-19

🤖 Prompt for AI Agents
.github/actions/create-prerelease/action.yml around lines 10 and 18-19: the
workflow sets id-token permission but never exports the OIDC token into the
NPM_TOKEN env var required by .yarnrc.yml (npmAuthToken: "${NPM_TOKEN:-}"),
causing publish to 401; add a step in the prerelease job before calling
create-prerelease that retrieves the OIDC token and writes it to GITHUB_ENV as
NPM_TOKEN (either by using core.getIDToken() or the documented curl call with
ACTIONS_ID_TOKEN_REQUEST_URL/TOKEN, or a maintained action like
actions/create-github-app-token) so create-prerelease and yarn npm publish
receive a populated NPM_TOKEN.

Comment on lines +30 to +40
- id: get_commit_message
run: |
COMMIT_MSG_TEMP="${{ github.event.head_commit.message }}"
if [ -n "$COMMIT_MSG_TEMP" ]
then
commit_msg="$COMMIT_MSG_TEMP"
echo "commit-message=${commit_msg}" | head -n 1 >> "$GITHUB_OUTPUT"
else
commit_message=$(git log -1 --pretty=%B | head -n 1)
echo "commit-message=$commit_message" >> "$GITHUB_OUTPUT"
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix potential shell injection vulnerability in commit message handling.

Line 32 directly uses github.event.head_commit.message in an inline script without proper escaping, creating a script injection vulnerability. Per GitHub's secure scripting guidelines, untrusted context values should be passed through environment variables instead.

Apply this diff to fix the injection vulnerability:

       - id: get_commit_message
         run: |
-          COMMIT_MSG_TEMP="${{ github.event.head_commit.message }}"
-          if [ -n "$COMMIT_MSG_TEMP" ]
+          if [ -n "$COMMIT_MSG_TEMP" ]
           then
-            commit_msg="$COMMIT_MSG_TEMP"
-            echo "commit-message=${commit_msg}" | head -n 1 >> "$GITHUB_OUTPUT"
+            echo "commit-message=$(echo "$COMMIT_MSG_TEMP" | head -n 1)" >> "$GITHUB_OUTPUT"
           else
             commit_message=$(git log -1 --pretty=%B | head -n 1)
             echo "commit-message=$commit_message" >> "$GITHUB_OUTPUT"
           fi
+        env:
+          COMMIT_MSG_TEMP: ${{ github.event.head_commit.message }}

This approach passes the untrusted value through an environment variable, then uses proper quoting to ensure it cannot break out of the script context.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- id: get_commit_message
run: |
COMMIT_MSG_TEMP="${{ github.event.head_commit.message }}"
if [ -n "$COMMIT_MSG_TEMP" ]
then
commit_msg="$COMMIT_MSG_TEMP"
echo "commit-message=${commit_msg}" | head -n 1 >> "$GITHUB_OUTPUT"
else
commit_message=$(git log -1 --pretty=%B | head -n 1)
echo "commit-message=$commit_message" >> "$GITHUB_OUTPUT"
fi
- id: get_commit_message
run: |
if [ -n "$COMMIT_MSG_TEMP" ]
then
echo "commit-message=$(echo "$COMMIT_MSG_TEMP" | head -n 1)" >> "$GITHUB_OUTPUT"
else
commit_message=$(git log -1 --pretty=%B | head -n 1)
echo "commit-message=$commit_message" >> "$GITHUB_OUTPUT"
fi
env:
COMMIT_MSG_TEMP: ${{ github.event.head_commit.message }}
🧰 Tools
🪛 actionlint (1.7.8)

31-31: "github.event.head_commit.message" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

🤖 Prompt for AI Agents
.github/workflows/publish.yml lines 30-40: the inline script directly
interpolates github.event.head_commit.message into shell (possible shell
injection); instead expose that value via a step env variable and reference it
quoted inside the script. Update the step to set an env key (e.g.,
COMMIT_MSG_ENV: "${{ github.event.head_commit.message }}") and in the run block
read and test "$COMMIT_MSG_ENV" (always quoted), assign to commit_msg if
non-empty, otherwise fall back to git log; ensure no unquoted expansions or eval
are used so the untrusted value cannot break out of the script context.

@stwiname stwiname merged commit ee90747 into main Nov 26, 2025
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant