Skip to content

ci: publish to npm via OIDC trusted publishing - #4

Merged
sommio merged 1 commit into
developfrom
ci/trusted-publishing
Jul 29, 2026
Merged

ci: publish to npm via OIDC trusted publishing#4
sommio merged 1 commit into
developfrom
ci/trusted-publishing

Conversation

@sommio

@sommio sommio commented Jul 29, 2026

Copy link
Copy Markdown
Owner

What

Tag-triggered release.yml now publishes to npm automatically, alongside the existing binary cross-compile + GitHub Release.

Why

npm removed legacy tokens (Nov 2025); granular tokens need 90-day rotation and carry leak risk. Trusted Publishing (OIDC) issues short-lived tokens bound to this specific workflow — no stored secret, no rotation, auto provenance badge. Directly addresses the upcoming ban on non-2FA keys (no key exists to ban).

Changes

.github/workflows/release.yml

  • permissions.id-token: write (OIDC required)
  • environment: release — matches npm trusted publisher binding, enables a manual approval gate before publish
  • Node 20 → 22 (trusted publishing requires ≥22.14)
  • npm install -g npm@latest before publish (Node 22 ships npm 10; OIDC requires ≥11.5.1)
  • npm publish --provenance --access public inserted after build, before binary cross-compile
  • No NODE_AUTH_TOKEN — npm CLI auto-detects OIDC

package.json

  • publishConfig: { access: public, provenance: true }
  • files: drop AGENTS.md (internal agent rules, not useful to npx users)

npm-side prerequisites (already done)

Trusted Publisher configured on npmjs.com for qwen-omni-mcp:

  • GitHub Actions, sommio/qwen-omni-mcp, workflow release.yml, environment release
  • Publishing access: Require 2FA and disallow tokens (most restrictive; compatible with OIDC)

Verification

  • Local gates green: typecheck / lint / format / 65 tests / build
  • npm pack --dry-run: .env not included ✓, AGENTS.md removed ✓, 12.1 kB / 15 files

Publish flow (after merge)

npm version patch && git push --follow-tags

→ quality gates → npm publish (provenance) → binary cross-compile → GitHub Release

First release should be an rc prerelease to validate the OIDC handshake:

npm version prerelease --preid=rc && git push --follow-tags

Rollback

  • npm: npm unpublish within 72h, else npm deprecate
  • GitHub Release / tag deletable
  • No long-lived credentials to rotate

Not in scope

  • No new runtime dependency, no new MCP tool names/schemas
  • No release-please / changesets (single maintainer; manual npm version is enough)
  • No change to CI test matrix (Node 20/22)

Legacy npm tokens removed Nov 2025; granular tokens need 90-day
rotation and leak risk. Trusted publishing issues short-lived
OIDC tokens bound to this workflow -- no stored secret, auto
provenance.

- release.yml: id-token write, environment release, Node 22,
  npm upgrade, npm publish --provenance before binary cross-compile
- package.json: publishConfig, drop AGENTS.md from shipped files
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Chores
    • Updated the release process to use secure, provenance-enabled publishing through GitHub Actions.
    • Upgraded release builds to Node.js 22.
    • Added a manual approval gate before publishing.
    • Limited the npm package contents to the distribution files and README.
    • Configured the package for public access with provenance metadata.

Walkthrough

The release configuration now uses GitHub Actions OIDC trusted publishing with Node 22 and npm provenance. Package metadata limits published files to dist and README.md and enables public provenance publishing.

Changes

npm publishing

Layer / File(s) Summary
Package publication metadata
./package.json
The published file list excludes AGENTS.md, while publishConfig enables public access and provenance.
OIDC release workflow
.github/workflows/release.yml
The release job uses the release environment, Node 22, writable OIDC identity tokens, an npm upgrade step, and provenance-enabled npm publishing.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions
  participant ReleaseEnvironment
  participant NPMRegistry
  GitHubActions->>ReleaseEnvironment: Enter release environment
  ReleaseEnvironment-->>GitHubActions: Approve release job
  GitHubActions->>NPMRegistry: Publish package with OIDC provenance
Loading

Poem

A rabbit hops through release night,
With OIDC tokens glowing bright.
Node twenty-two begins the run,
Provenance tags the package spun.
dist and README lead the way—
To npm’s shelf at break of day.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: publishing to npm via OIDC trusted publishing.
Description check ✅ Passed The description is directly related to the workflow and package metadata changes in the pull request.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/trusted-publishing
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch ci/trusted-publishing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 41-44: Move the “Publish to npm” step from its current position to
the final release step or job, after cross-compilation completes and
action-gh-release successfully uploads all artifacts. Preserve the existing npm
publish command and OIDC/provenance configuration, ensuring publishing occurs
only after artifact generation and upload succeed.
- Line 17: Update the release workflow action references for actions/checkout,
actions/setup-node, oven-sh/setup-bun, and softprops/action-gh-release to
immutable full commit SHAs, retaining comments that identify each pinned
version. Do not leave any of these actions referenced by mutable version tags.
- Line 17: Update the actions/checkout@v4 step in the release workflow to
disable credential persistence by setting persist-credentials to false, since no
later release step requires checkout-provided credentials.
- Line 22: Remove the cache: npm setting from the trusted-publishing release job
in the workflow, while leaving its OIDC permissions and install, test, build,
and publish steps unchanged.
- Line 10: Move id-token: write from workflow-level permissions into
jobs.release.permissions, limiting OIDC access to the release publishing job.
Keep contents permission scoped appropriately and ensure other jobs do not
inherit id-token access.
- Line 39: Update the npm installation command in the release workflow to pin an
exact npm CLI version compatible with Node 22 and at least 11.5.1, replacing the
mutable latest tag. Keep future version changes explicit and deliberate for
trusted publishing.
- Line 21: Update the release workflow’s node-version setting to an explicitly
tested Node.js 22.14.0-or-newer version so trusted publishing always runs above
the required floor; keep the existing release setup unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f9578e1e-e7bd-452f-8b8e-f25c2a5c825a

📥 Commits

Reviewing files that changed from the base of the PR and between a8647b1 and d2f9945.

📒 Files selected for processing (2)
  • .github/workflows/release.yml
  • package.json
📜 Review details
🧰 Additional context used
🪛 zizmor (1.28.0)
.github/workflows/release.yml

[warning] 17-17: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 10-10: overly broad permissions (excessive-permissions): id-token: write is overly broad at the workflow level

(excessive-permissions)


[error] 17-17: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 19-19: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 19-19: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): this step

(cache-poisoning)


[info] 13-13: workflow or action definition without a name (anonymous-definition): this job

(anonymous-definition)


[warning] 39-39: ad-hoc installation of packages (adhoc-packages): installs a package outside of a lockfile

(adhoc-packages)

🔇 Additional comments (3)
package.json (1)

15-20: LGTM!

.github/workflows/release.yml (2)

15-15: 🔒 Security & Privacy

Verify that the release environment is actually protected.

environment: release associates the job with an environment but does not itself create a required-reviewer gate. Verify that the environment has required reviewers, self-review restrictions, and tag/branch policies before relying on this as a manual approval barrier. (docs.github.com)


41-44: 🎯 Functional Correctness

No change needed. Current release metadata uses a regular 0.2.1 version, not a prerelease tag, and no planned rc tag is present in the release workflow.

			> Likely an incorrect or invalid review comment.


permissions:
contents: write
id-token: write # Required for npm OIDC trusted publishing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== release.yml context =="
if [ -f .github/workflows/release.yml ]; then
  nl -ba .github/workflows/release.yml | sed -n '1,140p'
else
  echo "missing .github/workflows/release.yml"
fi

echo
echo "== all workflow permissions snippets =="
python3 - <<'PY'
from pathlib import Path
import re
for p in sorted(Path('.github/workflows').glob('*')):
    if p.is_file() and p.suffix in {'.yml','.yaml'}:
        text=p.read_text()
        lines=text.splitlines()
        for i,l in enumerate(lines,1):
            if 'id-token:' in l or 'permissions:' in l:
                start=max(1,i-3); end=min(len(lines),i+8)
                print(f"-- {p}:{start}-{end} --")
                for n in range(start,end+1):
                    print(f"{n:4}: {lines[n-1]}")
PY

Repository: sommio/qwen-omni-mcp

Length of output: 224


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== release.yml context =="
if [ -f .github/workflows/release.yml ]; then
  nl -ba .github/workflows/release.yml | sed -n '1,140p'
else
  echo "missing .github/workflows/release.yml"
fi

echo
 echo "== all workflow permissions snippets =="
python3 - <<'PY'
from pathlib import Path
import re
for p in sorted(Path('.github/workflows').glob('*')):
    if p.is_file() and p.suffix in {'.yml','.yaml'}:
        text=p.read_text()
        lines=text.splitlines()
        for i,l in enumerate(lines,1):
            if 'id-token:' in l or 'permissions:' in l:
                start=max(1,i-3); end=min(len(lines),i+8)
                print(f"-- {p}:{start}-{end} --")
                for n in range(start,end+1):
                    print(f"{n:4}: {lines[n-1]}")
PY

Repository: sommio/qwen-omni-mcp

Length of output: 224


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== release.yml context =="
if [ -f .github/workflows/release.yml ]; then
  awk '{printf "%5d\t%s\n", NR, $0}' .github/workflows/release.yml | sed -n '1,160p'
else
  echo "missing .github/workflows/release.yml"
fi

echo
echo "== workflow permission snippets =="
python3 - <<'PY'
from pathlib import Path
for p in sorted(Path('.github/workflows').glob('*')):
    if not p.is_file() or p.suffix not in {'.yml','.yaml'}:
        continue
    text = p.read_text()
    lines = text.splitlines()
    hits = [i for i,l in enumerate(lines,1) if 'id-token:' in l or 'permissions:' in l]
    if not hits:
        continue
    print(f"-- {p} --")
    for n in hits:
        start=max(1,n-3); end=min(len(lines),n+8)
        for num in range(start,end+1):
            print(f"{num:4}: {lines[num-1]}")
PY

echo
echo "== structural parse with Python-safe yml-ish scan =="
python3 - <<'PY'
from pathlib import Path
import re
p = Path('.github/workflows/release.yml')
if p.exists():
    text = p.read_text()
    print("contains workflow-id-token:", bool(re.search(r'^\s*id-token:\s*write\s*(#.*)?$', text, re.M)))
    print("contains release job block:", bool(re.search(r'^\s*release:\s*$', text, re.M)))
    m = re.search(r'^\s*id-token:\s*write\s*(#.*)?$', text, re.M)
    if m:
        line=m.group(0).splitlines()[0]
        pos=line.find('id-token')
        indent=' ' * (len(m.group(0))-len(m.group(0).lstrip(' ')))
        print(f"top-level id-token indent count: {len(indent)}")
PY

Repository: sommio/qwen-omni-mcp

Length of output: 5464


Scope the OIDC permission to the release job.

id-token: write is currently workflow-scoped, so it applies to every job in this workflow. Move it under jobs.release.permissions and give the publishing job only the permissions it requires; the rest of the workflow can keep contents scoped appropriately.

🧰 Tools
🪛 zizmor (1.28.0)

[error] 10-10: overly broad permissions (excessive-permissions): id-token: write is overly broad at the workflow level

(excessive-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 10, Move id-token: write from
workflow-level permissions into jobs.release.permissions, limiting OIDC access
to the release publishing job. Keep contents permission scoped appropriately and
ensure other jobs do not inherit id-token access.

Source: Linters/SAST tools

runs-on: ubuntu-latest
environment: release # Matches the npm trusted publisher binding; enables a manual approval gate.
steps:
- uses: actions/checkout@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
sed -n '1,80p' .github/workflows/release.yml 2>/dev/null || true

echo
echo "== action refs in workflow =="
grep -nE 'uses:\s*[^#]+@[A-Za-z0-9_.-]+' .github/workflows/release.yml || true

echo
echo "== npm publish related lines in workflow =="
grep -nE 'npm|publish|setup-node|checkout' .github/workflows/release.yml || true

Repository: sommio/qwen-omni-mcp

Length of output: 4290


🌐 Web query:

GitHub Actions security hardening use immutable full length SHA pins instead of mutable tags for third-party actions

💡 Result:

Pinning GitHub Actions to an immutable full-length commit SHA (Secure Hash Algorithm) is a critical security best practice for hardening CI/CD pipelines [1][2][3]. Unlike tags (e.g., @v4) or branch references (e.g., @main), which are mutable and can be force-updated by maintainers or attackers to point to malicious code, a commit SHA provides a cryptographically verifiable, content-addressed reference to a specific version of an action [4][5][6][7]. Why SHA pinning is required for security: 1. Mitigation of Mutable Ref Attacks: Tags and branches are merely pointers that can be moved. If an action's repository is compromised, an attacker can re-point a popular tag (like @v4) to a malicious commit [4][6][7]. Because GitHub Actions resolve tags at runtime, any workflow using that tag will automatically execute the malicious code, potentially exfiltrating secrets, tokens, or modifying repository code [1][2][8]. SHA pinning renders this attack impossible because the workflow will continue to execute the exact, reviewed commit hash you specified [6][7]. 2. Immutable Reproducibility: Pinning to a SHA ensures that your workflow behavior remains consistent and reproducible over time, regardless of changes made to the upstream repository [9][5][8]. Best Practices for Implementation: * Use Full-Length SHAs: Always use the full 40-character commit SHA, not a short SHA, to ensure unique identification of the Git object [1][5][3]. * Keep Human-Readable Labels: Maintain the version number as a trailing comment (e.g., uses: actions/checkout@b4ffde6 # v4.1.1) [4][5][2]. This allows dependency management tools like Dependabot or Renovate to track updates and open pull requests for new releases while maintaining the security of an immutable pin [4][5][10]. * Enforce via Policy: GitHub allows organization and repository administrators to enforce SHA pinning through "Allowed actions" policies [1][9]. When enabled, workflows attempting to use a tag or branch will fail, ensuring compliance across the organization [9][5]. * Complement with Other Controls: SHA pinning should be part of a broader security strategy, including: * Applying the principle of least privilege to the GITHUB_TOKEN [2][11]. * Using dependency update tools (Dependabot/Renovate) to manage and review updates to pinned actions [4][5][11]. * Adopting "cooldown" periods before updating to new action versions to allow community detection of potential supply chain compromises [12]. In summary, treating tags as mutable discovery labels rather than trust anchors is essential [8]. By pinning to immutable commit SHAs, you ensure that your CI pipeline runs only the specific, audited code you intend, significantly reducing the blast radius of upstream supply chain compromises [2][6][8].

Citations:


Pin release workflow actions to immutable commit SHAs.

This workflow has contents: write and id-token: write before running npm publish. actions/checkout@v4, actions/setup-node@v4, oven-sh/setup-bun@v2, and softprops/action-gh-release@v2 are tag references; use full commit SHAs with version comments so publish-time action code cannot move unexpectedly.

🧰 Tools
🪛 zizmor (1.28.0)

[warning] 17-17: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 17-17: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 17, Update the release workflow action
references for actions/checkout, actions/setup-node, oven-sh/setup-bun, and
softprops/action-gh-release to immutable full commit SHAs, retaining comments
that identify each pinned version. Do not leave any of these actions referenced
by mutable version tags.

Source: Linters/SAST tools


🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow files =="
git ls-files '.github/workflows/*'

echo
echo "== release workflow =="
if [ -f .github/workflows/release.yml ]; then
  nl -ba .github/workflows/release.yml | sed -n '1,220p'
fi

echo
echo "== package scripts references =="
if [ -f package.json ]; then
  node -e 'const p=require("./package.json"); console.log(JSON.stringify({scripts:p.scripts, repository:p.repository}, null, 2))'
fi

echo
echo "== repository scripts/runnable files mentions in workflow =="
if [ -f .github/workflows/release.yml ]; then
  rg -n "npm|npx|run|shell:|permissions:|pull_request|push|release" .github/workflows/release.yml
fi

Repository: sommio/qwen-omni-mcp

Length of output: 367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== release workflow lines using awk =="
if [ -f .github/workflows/release.yml ]; then
  awk '{printf "%7d: %s\n", NR, $0}' .github/workflows/release.yml | sed -n '1,220p'
fi

echo
echo "== package scripts if present =="
if [ -f package.json ]; then
  awk 'BEGIN{s=0} {if(/"scripts"/) s=1; if(s) print} END{}' package.json | sed -n '1,220p'
fi

echo
echo "== all workflow mentions of npm/npx/scripts/repository files =="
grep -RInE 'npm|npx|scripts|shell:|pull_request|push|release|permissions:' .github/workflows || true

Repository: sommio/qwen-omni-mcp

Length of output: 7412


Disable checkout credential persistence.

actions/checkout persists the GitHub token by default, and this release job later executes repository scripts via npm ci (which runs the configured prepare script), lint/test/build commands, and publishes to npm. Set persist-credentials: false unless checkout-provided credentials are explicitly needed by a later step.

Proposed fix
       - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
📝 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
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.28.0)

[warning] 17-17: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 17-17: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 17, Update the actions/checkout@v4
step in the release workflow to disable credential persistence by setting
persist-credentials to false, since no later release step requires
checkout-provided credentials.

Source: Linters/SAST tools

- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22 # Trusted publishing requires Node >=22.14.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow snippets =="
sed -n '1,80p' .github/workflows/release.yml 2>/dev/null || true

echo
echo "== related node setup lines =="
rg -n "node-version|trusted publishing|setup-node|npm" .github/workflows release -S 2>/dev/null || true

echo
echo "== package metadata if present =="
for f in package.json .nvmrc .node-version; do
  [ -f "$f" ] && { echo "--- $f"; cat "$f"; }
done

Repository: sommio/qwen-omni-mcp

Length of output: 6776


🌐 Web query:

npm trusted publishing Node version 22.14.0 npm 11.5.1 documentation

💡 Result:

Trusted publishing for npm packages is a security feature that allows you to publish packages directly from CI/CD workflows using OpenID Connect (OIDC) authentication [1]. This mechanism eliminates the need for long-lived npm tokens, significantly reducing the security risks associated with storing, rotating, or accidentally exposing such tokens [1][2][3]. Requirements: To use trusted publishing, you must use npm CLI version 11.5.1 or later and Node.js version 22.14.0 or higher [1][3]. Key Benefits and Functionality: - Automated Authentication: The npm CLI automatically detects OIDC environments and uses them to authenticate during the publish or stage operations, without requiring an NPM_TOKEN [1][3]. - Provenance Attestations: When utilizing trusted publishing, npm automatically generates and publishes provenance attestations for your package, providing a verifiable link to the source code and build instructions [2][4]. The --provenance flag is no longer required when using this method [2][4]. - Supported Providers: Trusted publishing currently supports cloud-hosted runners on platforms including GitHub Actions, GitLab CI/CD, and CircleCI [1][4]. Configuration: - Management: You can configure trusted publishing via the npm website in your package settings or by using the npm trust command in the CLI [1][5]. - Setup: When configuring a trusted publisher, you establish a trust relationship by specifying the required OIDC claims for your CI/CD provider (e.g., repository, workflow file, environment) [1][2]. The npm trust command provides a way to manage these relationships and specify permissions, such as --allow-publish or --allow-stage-publish [5]. - Limitations: This feature is intended for cloud-hosted runners; support for self-hosted runners is not currently available [1]. Each package can have only one configured trusted publisher at a time [1].

Citations:


Pin the Node.js floor used for trusted publishing.

node-version: 22 allows latest Node 22 releases and the comment does not make the required 22.14.0+ floor explicit. Set this step to a tested 22.14.0+ version or add an explicit runtime check for the npm/trusted-publishing minimum.

Proposed fix
-          node-version: 22
+          node-version: '22.14.0'
📝 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
node-version: 22 # Trusted publishing requires Node >=22.14.
node-version: '22.14.0' # Trusted publishing requires Node >=22.14.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 21, Update the release workflow’s
node-version setting to an explicitly tested Node.js 22.14.0-or-newer version so
trusted publishing always runs above the required floor; keep the existing
release setup unchanged.

with:
node-version: 20
node-version: 22 # Trusted publishing requires Node >=22.14.
cache: npm

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== release workflow =="
cat -n .github/workflows/release.yml

echo
echo "== relevant setup-node/npm usage =="
rg -n "setup-node|setup-javascript|npm|cache:|permissions:|id-token|npm publish|trusted-publish|oidc" .github/workflows/release.yml .github/workflows 2>/dev/null || true

echo
echo "== git status/diff summary =="
git diff --stat || true
git diff .github/workflows/release.yml --unified=200 || true

Repository: sommio/qwen-omni-mcp

Length of output: 7407


🌐 Web query:

github actions setup-node cache npm trusted publishing npm publish disable cache trusted-publishing

💡 Result:

When using GitHub Actions to publish packages to npm via Trusted Publishing (OIDC), it is recommended to disable automatic dependency caching in the setup-node action to enhance security and prevent potential cache poisoning [1][2][3]. To disable caching, set the package-manager-cache input to false in your workflow file [1][3]: - uses: actions/setup-node@v7 with: node-version: '24' registry-url: 'https://registry.npmjs.org' package-manager-cache: false # Disable automatic npm dependency caching The primary reasons for disabling caching in this context are: 1. Security: Automatic caching for npm is enabled by default if your package.json defines a packageManager field [4][5][3]. In publishing workflows, a poisoned cache can potentially expose sensitive information or OIDC tokens to attacker-controlled code [1]. 2. OIDC Compatibility: Trusted Publishing (OIDC) does not use the NODE_AUTH_TOKEN secret [4][2]. If registry-url is specified, setup-node may attempt to configure authentication in a way that conflicts with OIDC flows if not configured correctly, leading to E404 or ENEEDAUTH errors [6][7]. Explicitly disabling cache and ensuring your environment does not accidentally rely on legacy auth tokens helps maintain a clean OIDC publishing flow [1][2]. Requirements for Trusted Publishing: - npm version 11.5.1 or newer is required [1][8]. - Your workflow must include the id-token: write permission [1][2][8]. - You must configure a Trusted Publisher in npm for your package/scope that precisely matches your repository, workflow file, and optional environment [1][2]. If you are using Node.js 24 or newer, it will generally include the required npm version by default [1][8]. Always verify that your workflow filename exactly matches the one registered in your npm Trusted Publisher settings, as mismatches can cause publishing failures [1][2].

Citations:


🌐 Web query:

npm docs trusted publishing package cache setup-node cache: npm disable

💡 Result:

When using trusted publishing (OpenID Connect) for npm packages within GitHub Actions, you should explicitly disable automatic npm caching in the setup-node action to mitigate security risks such as cache poisoning [1][2][3]. To disable automatic caching, set the package-manager-cache input to false in your workflow file [1][4][5]: - uses: actions/setup-node@v7 with: node-version: '24' package-manager-cache: false The setup-node action enables npm caching by default if your package.json contains a packageManager or devEngines.packageManager field set to npm [4][5][6]. Disabling this is recommended for workflows with elevated privileges, such as publishing, because a poisoned cache could expose sensitive information or credentials—including OIDC tokens—to attacker-controlled code [1][2][5]. While trusted publishing itself does not use long-lived npm tokens (like NODE_AUTH_TOKEN) [4][7], disabling the cache remains a security best practice for any high-permission release workflow to ensure a clean environment [2][3].

Citations:


Disable npm caching in the trusted-publishing release job.

This workflow has id-token: write, install/test/build scripts, and publishes to npm via OIDC. Remove cache: npm so poisoned package-manager cache input cannot affect the privileged release path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 22, Remove the cache: npm setting from
the trusted-publishing release job in the workflow, while leaving its OIDC
permissions and install, test, build, and publish steps unchanged.

Source: Linters/SAST tools


# Trusted publishing (OIDC) requires npm CLI >=11.5.1; Node 22 ships npm 10.
- name: Upgrade npm
run: npm install -g npm@latest

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
if [ -f .github/workflows/release.yml ]; then
  nl -ba .github/workflows/release.yml | sed -n '1,90p'
else
  echo "missing .github/workflows.release.yml"
fi

echo
echo "== package files =="
for f in package.json .github/workflows/release.yml; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    nl -ba "$f" | sed -n '1,120p'
  fi
done

echo
echo "== npm-related workflow/package references =="
rg -n "npm(`@latest`|@[0-9]|publish|NODE_VERSION|engine|engines|trusted-publishers|npm-cli|npm@)" .github package.json 2>/dev/null || true

echo
echo "== package lock publish-related metadata, if present =="
if [ -f package-lock.json ]; then
  node - <<'JS'
const fs=require('fs');
const lock=JSON.parse(fs.readFileSync('package-lock.json','utf8'));
console.log("packageManager:", lock.packages?.[""]?.packageManager);
console.log("npm version:", lock.packages?.[""].version);
console.log("engines node:", lock.packages?.[""]?.engines?.node);
for (const [k,v] of Object.entries(lock.packages || {}).slice(0,5)) {
  if ((k === "" || k.includes("npm") || k.includes("pkg")) && (v.version || v.engines)) {
    console.log(k, v.version || "", v.engines || "");
  }
}
JS
else
  echo "missing package-lock.json"
fi

echo
echo "== Node 22/11 npm compatibility references (local docs or CI config) =="
rg -n "346|node:22|NODE_VERSION=22|npm@latest|npm@11|trusted-publishers|npm 11" .github package.json README.md package-lock.json 2>/dev/null || true

echo
echo "== current npm latest resolve =="
node - <<'JS'
const https = require('https');
https.get('https://registry.npmjs.org/npm/latest', {timeout: 10000}, (res) => {
  let body = '';
  res.on('data', c => body += c);
  res.on('end', () => {
    try {
      const json = JSON.parse(body);
      console.log("npm latest resolved version:", json.version);
      console.log("engines:", json.engines ? json.engines.node : null);
      if (json.dist && json.dist.integrity) console.log("tarball integrity sample:", json.dist.integrity.slice(0,80));
    } catch (e) {
      console.log("parse/error:", e.message, body.slice(0,200));
    }
  });
}).on('error', e => console.log("request error:", e.message));
JS

Repository: sommio/qwen-omni-mcp

Length of output: 221


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
if [ -f .github/workflows/release.yml ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' .github/workflows/release.yml | sed -n '1,100p'
else
  echo "missing .github/workflows/release.yml"
fi

echo
echo "== package files =="
for f in package.json .github/workflows/release.yml; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    awk '{printf "%6d\t%s\n", NR, $0}' "$f" | sed -n '1,140p'
  fi
done

echo
echo "== npm-related workflow/package references =="
rg -n "npm(`@latest`|@[0-9]|publish|NODE_VERSION|engine|engines|trusted-publishers|npm-cli|npm@)" .github package.json 2>/dev/null || true

echo
echo "== package lock publish-related metadata, if present =="
if [ -f package-lock.json ]; then
  node - <<'JS'
const fs=require('fs');
const lock=JSON.parse(fs.readFileSync('package-lock.json','utf8'));
console.log("packageManager:", lock.packages?.[""]?.packageManager);
root = lock.packages?.[""] || {};
console.log("root version:", root.version);
console.log("root engines:", root.engines);
console.log("root integrity:", root.integrity);
JS
else
  echo "missing package-lock.json"
fi

echo
echo "== Node 22/11 npm compatibility references (local docs or CI config) =="
rg -n "346|node:22|NODE_VERSION=22|npm@latest|npm@11|trusted-publishers|npm 11" .github package.json README.md package-lock.json 2>/dev/null || true

echo
echo "== current npm latest resolve =="
node - <<'JS'
const https = require('https');
https.get('https://registry.npmjs.org/npm/latest', {timeout: 10000}, (res) => {
  let body = '';
  res.on('data', c => body += c);
  res.on('end', () => {
    try {
      const json = JSON.parse(body);
      console.log("npm latest resolved version:", json.version);
      console.log("engines:", json.engines ? json.engines.node : null);
    } catch (e) {
      console.log("parse/error:", e.message, body.slice(0,200));
    }
  });
}).on('error', e => console.log("request error:", e.message));
JS

Repository: sommio/qwen-omni-mcp

Length of output: 9762


Pin the npm CLI version used for publishing.

The workflow uses npm install -g npm@latest, which resolves a mutable registry tag at release time. Pin an exact npm version at or above npm@11.5.1 used with Node 22, then review upgrades deliberately before publishing with trusted publishing.

🧰 Tools
🪛 zizmor (1.28.0)

[warning] 39-39: ad-hoc installation of packages (adhoc-packages): installs a package outside of a lockfile

(adhoc-packages)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 39, Update the npm installation
command in the release workflow to pin an exact npm CLI version compatible with
Node 22 and at least 11.5.1, replacing the mutable latest tag. Keep future
version changes explicit and deliberate for trusted publishing.

Source: Linters/SAST tools

Comment on lines +41 to +44
- name: Publish to npm
# No NODE_AUTH_TOKEN: npm CLI auto-detects the OIDC environment and
# obtains a short-lived token from the trusted publisher binding.
run: npm publish --provenance --access public

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow files =="
find .github/workflows -maxdepth 1 -type f -name '*.yml' -o -name '*.yaml' 2>/dev/null | sort

echo "== release workflow relevant lines =="
if [ -f .github/workflows/release.yml ]; then
  nl -ba .github/workflows/release.yml | sed -n '1,220p'
else
  echo "missing .github/workflows/release.yml"
fi

echo "== package metadata publishing scripts =="
for f in package.json; do
  [ -f "$f" ] && nl -ba "$f" | sed -n '1,220p'
done

Repository: sommio/qwen-omni-mcp

Length of output: 380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== release workflow =="
awk '{ printf "%6d\t%s\n", NR, $0 }' .github/workflows/release.yml | sed -n '1,240p'

echo "== search publish and release jobs =="
awk '
  /^  [a-zA-Z_][a-zA-Z0-9_-]*:|^      - name: npm publish|npm publish|--archive|--upload-release-asset|gh release/ { print NR": "$0 }
' .github/workflows/release.yml

echo "== package.json metadata scripts =="
awk '{ printf "%6d\t%s\n", NR, $0 }' package.json | sed -n '1,200p'

Repository: sommio/qwen-omni-mcp

Length of output: 5722


Publish after release artifact generation succeeds.

In the current release workflow, npm publish runs before cross-compilation and the action-gh-release upload. If the subsequent release artifact build or upload fails, this npm version is permanently consumed and cannot be republished under the same version. Move publishing to the final release step/job so it only runs after artifacts are generated and uploaded successfully.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 41 - 44, Move the “Publish to
npm” step from its current position to the final release step or job, after
cross-compilation completes and action-gh-release successfully uploads all
artifacts. Preserve the existing npm publish command and OIDC/provenance
configuration, ensuring publishing occurs only after artifact generation and
upload succeed.

@sommio
sommio merged commit 06e6ad5 into develop Jul 29, 2026
4 checks passed
@sommio
sommio deleted the ci/trusted-publishing branch July 29, 2026 08:19
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