-
Notifications
You must be signed in to change notification settings - Fork 0
ci: publish to npm via OIDC trusted publishing #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,17 +7,20 @@ on: | |||||||||
|
|
||||||||||
| permissions: | ||||||||||
| contents: write | ||||||||||
| id-token: write # Required for npm OIDC trusted publishing. | ||||||||||
|
|
||||||||||
| jobs: | ||||||||||
| release: | ||||||||||
| runs-on: ubuntu-latest | ||||||||||
| environment: release # Matches the npm trusted publisher binding; enables a manual approval gate. | ||||||||||
| steps: | ||||||||||
| - uses: actions/checkout@v4 | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 || trueRepository: sommio/qwen-omni-mcp Length of output: 4290 🌐 Web query:
💡 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., Citations:
Pin release workflow actions to immutable commit SHAs. This workflow has 🧰 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 AgentsSource: 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
fiRepository: 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 || trueRepository: sommio/qwen-omni-mcp Length of output: 7412 Disable checkout credential persistence.
Proposed fix - uses: actions/checkout@v4
+ with:
+ persist-credentials: false📝 Committable suggestion
Suggested change
🧰 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 AgentsSource: Linters/SAST tools |
||||||||||
|
|
||||||||||
| - uses: actions/setup-node@v4 | ||||||||||
| with: | ||||||||||
| node-version: 20 | ||||||||||
| node-version: 22 # Trusted publishing requires Node >=22.14. | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"; }
doneRepository: sommio/qwen-omni-mcp Length of output: 6776 🌐 Web query:
💡 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 Citations:
Pin the Node.js floor used for trusted publishing.
Proposed fix- node-version: 22
+ node-version: '22.14.0'📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| cache: npm | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 || trueRepository: sommio/qwen-omni-mcp Length of output: 7407 🌐 Web query:
💡 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:
💡 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 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||
| registry-url: https://registry.npmjs.org | ||||||||||
|
|
||||||||||
| - name: Install (skip husky in CI) | ||||||||||
| run: npm ci | ||||||||||
|
|
@@ -31,6 +34,15 @@ jobs: | |||||||||
| - run: npm test | ||||||||||
| - run: npm run build | ||||||||||
|
|
||||||||||
| # Trusted publishing (OIDC) requires npm CLI >=11.5.1; Node 22 ships npm 10. | ||||||||||
| - name: Upgrade npm | ||||||||||
| run: npm install -g npm@latest | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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));
JSRepository: 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));
JSRepository: sommio/qwen-omni-mcp Length of output: 9762 Pin the npm CLI version used for publishing. The workflow uses 🧰 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 AgentsSource: Linters/SAST tools |
||||||||||
|
|
||||||||||
| - 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 | ||||||||||
|
Comment on lines
+41
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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'
doneRepository: 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, 🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| - uses: oven-sh/setup-bun@v2 | ||||||||||
|
|
||||||||||
| - name: Read version | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
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:
Repository: sommio/qwen-omni-mcp
Length of output: 224
🏁 Script executed:
Repository: sommio/qwen-omni-mcp
Length of output: 224
🏁 Script executed:
Repository: sommio/qwen-omni-mcp
Length of output: 5464
Scope the OIDC permission to the release job.
id-token: writeis currently workflow-scoped, so it applies to every job in this workflow. Move it underjobs.release.permissionsand give the publishing job only the permissions it requires; the rest of the workflow can keepcontentsscoped 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
Source: Linters/SAST tools