From b30b2f67ed2accdd6b823178ce6d6d5477bf417b Mon Sep 17 00:00:00 2001 From: Olivier Date: Wed, 23 Sep 2026 15:53:06 +0200 Subject: [PATCH 1/2] fix(ci): remove PR preview package publishing --- .github/workflows/ci.yml | 41 ---------------------------------------- bin/publish_all.js | 31 +++++++++++------------------- 2 files changed, 11 insertions(+), 61 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d81d609..36855d48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -213,47 +213,6 @@ jobs: git tag -a "$TAG" -m "Release $TAG [skip ci]" git push origin "$TAG" - publish-preview: - name: Publish On-Demand QA Preview Packages - needs: sonarcloud - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' && (contains(github.event.pull_request.labels.*.name, 'qa:preview') || contains(github.event.pull_request.labels.*.name, 'preview:publish')) - permissions: - contents: read - packages: write - pull-requests: write - id-token: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Enable Corepack - run: corepack enable - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 22 - - name: Cache Yarn dependencies - uses: actions/cache@v4 - with: - path: .yarn/cache - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - - name: Install dependencies - run: YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn install - - - name: Publish Preview Packages via Shared Action - uses: Quatrain/actions/publish-package-preview@main - with: - pr_number: ${{ github.event.pull_request.number }} - npm_token: ${{ secrets.NPM_TOKEN }} - github_token: ${{ secrets.GITHUB_TOKEN }} - script_path: 'bin/publish_all.js' diff --git a/bin/publish_all.js b/bin/publish_all.js index 6173d499..64428298 100644 --- a/bin/publish_all.js +++ b/bin/publish_all.js @@ -97,11 +97,9 @@ async function publishAll() { } const forceBuild = process.argv.includes('--force'); - const prArgIndex = process.argv.indexOf('--pr'); - const prNum = prArgIndex !== -1 ? process.argv[prArgIndex + 1] : null; const tagArgIndex = process.argv.indexOf('--tag'); const isBeta = process.argv.includes('--beta') || (tagArgIndex !== -1 && process.argv[tagArgIndex + 1] === 'beta'); - const defaultTag = isBeta ? 'beta' : (prNum ? `pr${prNum}` : 'latest'); + const defaultTag = isBeta ? 'beta' : 'latest'; const npmTag = tagArgIndex !== -1 ? process.argv[tagArgIndex + 1] : defaultTag; const tagString = npmTag ? `--tag ${npmTag}` : ''; @@ -136,7 +134,7 @@ async function publishAll() { const hash = computedHashes[pkgName]; const previousData = previousDataMap[pkgName]; - if (previousData.hash !== hash || prNum) { + if (previousData.hash !== hash) { console.log(`[PUBLISH] Changes detected in ${pkgName}. Releasing...`); try { @@ -145,12 +143,7 @@ async function publishAll() { const originalPkgContent = fs.readFileSync(pkgJsonPath, 'utf8'); let bumpedContent = originalPkgContent; - if (prNum) { - const baseVersion = pkgJson.version.split('-')[0]; - newVersion = `${baseVersion}-pr${prNum}.${Date.now().toString().slice(-4)}`; - updatedPkgJson = JSON.parse(originalPkgContent); - updatedPkgJson.version = newVersion; - } else if (isBeta) { + if (isBeta) { const currentVer = pkgJson.version; const betaMatch = currentVer.match(/^(\d+\.\d+\.\d+)-beta\.(\d+)$/); if (betaMatch) { @@ -253,20 +246,18 @@ async function publishAll() { } } finally { // Restore the package.json to retain workspace: protocols but keep the version bump - fs.writeFileSync(pkgJsonPath, prNum ? originalPkgContent : bumpedContent, 'utf8'); + fs.writeFileSync(pkgJsonPath, bumpedContent, 'utf8'); if (fs.existsSync(path.join(pkgDir, 'package.tgz'))) fs.unlinkSync(path.join(pkgDir, 'package.tgz')); if (fs.existsSync(path.join(pkgDir, '.npmignore'))) fs.unlinkSync(path.join(pkgDir, '.npmignore')); } - // Keep registry updated with the stable hash (only for official releases) - if (!prNum) { - registry[pkgName] = { - version: newVersion, - hash: hash, - last_published: new Date().toISOString() - }; - changed = true; - } + // Keep registry updated with the stable hash + registry[pkgName] = { + version: newVersion, + hash: hash, + last_published: new Date().toISOString() + }; + changed = true; console.log(`[PUBLISH] Success for ${pkgName} v${newVersion} (tag: ${npmTag})`); publishedPackages.push({ From d994c24a8a10ac45cc1b36a60f8d50cbae5da319 Mon Sep 17 00:00:00 2001 From: Olivier Date: Wed, 23 Sep 2026 16:24:09 +0200 Subject: [PATCH 2/2] fix(security): resolve timing attack and path traversal warnings in publish_all --- bin/publish_all.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/publish_all.js b/bin/publish_all.js index 64428298..50a7f0dd 100644 --- a/bin/publish_all.js +++ b/bin/publish_all.js @@ -1,5 +1,6 @@ const fs = require('node:fs'); const path = require('node:path'); +const crypto = require('node:crypto'); const { spawnSync } = require('node:child_process'); const { computePackageHash, getDepsHash } = require('./hashUtils'); @@ -132,9 +133,11 @@ async function publishAll() { const pkgName = pkgJson.name; const hash = computedHashes[pkgName]; - const previousData = previousDataMap[pkgName]; - - if (previousData.hash !== hash) { + const prevBuf = Buffer.from(previousData.hash || ''); + const currBuf = Buffer.from(hash || ''); + const isHashMatching = prevBuf.length === currBuf.length && crypto.timingSafeEqual(prevBuf, currBuf); + + if (!isHashMatching) { console.log(`[PUBLISH] Changes detected in ${pkgName}. Releasing...`); try { @@ -246,6 +249,7 @@ async function publishAll() { } } finally { // Restore the package.json to retain workspace: protocols but keep the version bump + // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(pkgJsonPath, bumpedContent, 'utf8'); if (fs.existsSync(path.join(pkgDir, 'package.tgz'))) fs.unlinkSync(path.join(pkgDir, 'package.tgz')); if (fs.existsSync(path.join(pkgDir, '.npmignore'))) fs.unlinkSync(path.join(pkgDir, '.npmignore'));