diff --git a/.github/actions/fleet/github-ci-fix-app-token/mint-app-installation-token.mjs b/.github/actions/fleet/github-ci-fix-app-token/mint-app-installation-token.mjs index b06308de5..633ce2d28 100644 --- a/.github/actions/fleet/github-ci-fix-app-token/mint-app-installation-token.mjs +++ b/.github/actions/fleet/github-ci-fix-app-token/mint-app-installation-token.mjs @@ -110,6 +110,28 @@ function gh(method, path, jwt, body) { }) } +function sleep(milliseconds) { + return new Promise(resolve => setTimeout(resolve, milliseconds)) +} + +export async function requestAppInstallationToken(config) { + const { + body, + jwt, + method, + path, + requestImpl = gh, + sleepImpl = sleep, + } = config + for (let attempt = 0; ; attempt += 1) { + const response = await requestImpl(method, path, jwt, body) + if (![500, 502, 503, 504].includes(response.status) || attempt === 2) { + return response + } + await sleepImpl(1000 * 2 ** attempt) + } +} + // Parse a PERMISSIONS string (a JSON object) into the access-token request, or // undefined when blank. Throws on malformed or empty-object input — an empty // object would mint a blanket-permission token, the opposite of least-privilege. @@ -217,11 +239,32 @@ export function formatAppPermissionShortfall(config) { } export function formatAppTokenMintFailure(config) { - const { body, installationId, owner, slug, status } = config + const { installationId, owner, slug, status } = config + const where = `Where: POST /app/installations/${installationId}/access_tokens. ` + if (status >= 500 && status <= 599) { + return ( + `token mint failed: HTTP ${status}. ` + + where + + 'Saw: GitHub returned a server error while creating the installation token. ' + + 'Fix: retry the workflow later. Do not expand App permissions based on a GitHub server error.' + ) + } + const responseMessage = + status === 422 + ? 'GitHub rejected the requested repositories or permissions.' + : 'GitHub rejected the installation-token request.' + if (status !== 422) { + return ( + `token mint failed: HTTP ${status}. ` + + where + + `Saw: ${responseMessage} ` + + `Fix: verify the ${slug} App credentials, its installation on ${owner}, and repository access.` + ) + } return ( `token mint failed: HTTP ${status}. ` + - `Where: POST /app/installations/${installationId}/access_tokens. ` + - `Saw: ${body}. Fix: the requested permissions/repositories must be ` + + where + + `Saw: ${responseMessage}. Fix: the requested permissions/repositories must be ` + `a subset of what the app's installation on ${owner} grants. ` + `${formatAppRegistrationGuidance(slug)} ` + 'Open Permissions & events -> Repository permissions. ' + @@ -333,16 +376,15 @@ async function main() { tokenBody.permissions = permissions } tokenBody.repositories = repositories - const minted = await gh( - 'POST', - `/app/installations/${installationId}/access_tokens`, + const minted = await requestAppInstallationToken({ + body: JSON.stringify(tokenBody), jwt, - JSON.stringify(tokenBody), - ) + method: 'POST', + path: `/app/installations/${installationId}/access_tokens`, + }) if (minted.status !== 201) { die( formatAppTokenMintFailure({ - body: minted.body, installationId, owner, slug: installation.app_slug ?? '', diff --git a/.github/actions/fleet/github-payload-app-token/mint-app-installation-token.mjs b/.github/actions/fleet/github-payload-app-token/mint-app-installation-token.mjs index b06308de5..633ce2d28 100644 --- a/.github/actions/fleet/github-payload-app-token/mint-app-installation-token.mjs +++ b/.github/actions/fleet/github-payload-app-token/mint-app-installation-token.mjs @@ -110,6 +110,28 @@ function gh(method, path, jwt, body) { }) } +function sleep(milliseconds) { + return new Promise(resolve => setTimeout(resolve, milliseconds)) +} + +export async function requestAppInstallationToken(config) { + const { + body, + jwt, + method, + path, + requestImpl = gh, + sleepImpl = sleep, + } = config + for (let attempt = 0; ; attempt += 1) { + const response = await requestImpl(method, path, jwt, body) + if (![500, 502, 503, 504].includes(response.status) || attempt === 2) { + return response + } + await sleepImpl(1000 * 2 ** attempt) + } +} + // Parse a PERMISSIONS string (a JSON object) into the access-token request, or // undefined when blank. Throws on malformed or empty-object input — an empty // object would mint a blanket-permission token, the opposite of least-privilege. @@ -217,11 +239,32 @@ export function formatAppPermissionShortfall(config) { } export function formatAppTokenMintFailure(config) { - const { body, installationId, owner, slug, status } = config + const { installationId, owner, slug, status } = config + const where = `Where: POST /app/installations/${installationId}/access_tokens. ` + if (status >= 500 && status <= 599) { + return ( + `token mint failed: HTTP ${status}. ` + + where + + 'Saw: GitHub returned a server error while creating the installation token. ' + + 'Fix: retry the workflow later. Do not expand App permissions based on a GitHub server error.' + ) + } + const responseMessage = + status === 422 + ? 'GitHub rejected the requested repositories or permissions.' + : 'GitHub rejected the installation-token request.' + if (status !== 422) { + return ( + `token mint failed: HTTP ${status}. ` + + where + + `Saw: ${responseMessage} ` + + `Fix: verify the ${slug} App credentials, its installation on ${owner}, and repository access.` + ) + } return ( `token mint failed: HTTP ${status}. ` + - `Where: POST /app/installations/${installationId}/access_tokens. ` + - `Saw: ${body}. Fix: the requested permissions/repositories must be ` + + where + + `Saw: ${responseMessage}. Fix: the requested permissions/repositories must be ` + `a subset of what the app's installation on ${owner} grants. ` + `${formatAppRegistrationGuidance(slug)} ` + 'Open Permissions & events -> Repository permissions. ' + @@ -333,16 +376,15 @@ async function main() { tokenBody.permissions = permissions } tokenBody.repositories = repositories - const minted = await gh( - 'POST', - `/app/installations/${installationId}/access_tokens`, + const minted = await requestAppInstallationToken({ + body: JSON.stringify(tokenBody), jwt, - JSON.stringify(tokenBody), - ) + method: 'POST', + path: `/app/installations/${installationId}/access_tokens`, + }) if (minted.status !== 201) { die( formatAppTokenMintFailure({ - body: minted.body, installationId, owner, slug: installation.app_slug ?? '', diff --git a/.github/actions/fleet/github-pr-branch-app-token/mint-app-installation-token.mjs b/.github/actions/fleet/github-pr-branch-app-token/mint-app-installation-token.mjs index b06308de5..633ce2d28 100644 --- a/.github/actions/fleet/github-pr-branch-app-token/mint-app-installation-token.mjs +++ b/.github/actions/fleet/github-pr-branch-app-token/mint-app-installation-token.mjs @@ -110,6 +110,28 @@ function gh(method, path, jwt, body) { }) } +function sleep(milliseconds) { + return new Promise(resolve => setTimeout(resolve, milliseconds)) +} + +export async function requestAppInstallationToken(config) { + const { + body, + jwt, + method, + path, + requestImpl = gh, + sleepImpl = sleep, + } = config + for (let attempt = 0; ; attempt += 1) { + const response = await requestImpl(method, path, jwt, body) + if (![500, 502, 503, 504].includes(response.status) || attempt === 2) { + return response + } + await sleepImpl(1000 * 2 ** attempt) + } +} + // Parse a PERMISSIONS string (a JSON object) into the access-token request, or // undefined when blank. Throws on malformed or empty-object input — an empty // object would mint a blanket-permission token, the opposite of least-privilege. @@ -217,11 +239,32 @@ export function formatAppPermissionShortfall(config) { } export function formatAppTokenMintFailure(config) { - const { body, installationId, owner, slug, status } = config + const { installationId, owner, slug, status } = config + const where = `Where: POST /app/installations/${installationId}/access_tokens. ` + if (status >= 500 && status <= 599) { + return ( + `token mint failed: HTTP ${status}. ` + + where + + 'Saw: GitHub returned a server error while creating the installation token. ' + + 'Fix: retry the workflow later. Do not expand App permissions based on a GitHub server error.' + ) + } + const responseMessage = + status === 422 + ? 'GitHub rejected the requested repositories or permissions.' + : 'GitHub rejected the installation-token request.' + if (status !== 422) { + return ( + `token mint failed: HTTP ${status}. ` + + where + + `Saw: ${responseMessage} ` + + `Fix: verify the ${slug} App credentials, its installation on ${owner}, and repository access.` + ) + } return ( `token mint failed: HTTP ${status}. ` + - `Where: POST /app/installations/${installationId}/access_tokens. ` + - `Saw: ${body}. Fix: the requested permissions/repositories must be ` + + where + + `Saw: ${responseMessage}. Fix: the requested permissions/repositories must be ` + `a subset of what the app's installation on ${owner} grants. ` + `${formatAppRegistrationGuidance(slug)} ` + 'Open Permissions & events -> Repository permissions. ' + @@ -333,16 +376,15 @@ async function main() { tokenBody.permissions = permissions } tokenBody.repositories = repositories - const minted = await gh( - 'POST', - `/app/installations/${installationId}/access_tokens`, + const minted = await requestAppInstallationToken({ + body: JSON.stringify(tokenBody), jwt, - JSON.stringify(tokenBody), - ) + method: 'POST', + path: `/app/installations/${installationId}/access_tokens`, + }) if (minted.status !== 201) { die( formatAppTokenMintFailure({ - body: minted.body, installationId, owner, slug: installation.app_slug ?? '', diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 5eb700db9..7a8871dcc 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -743,7 +743,7 @@ jobs: - name: Validate repository publish contract run: pnpm run --if-present prepublish:check - name: Set up odai - if: ${{ inputs.resume-publish != true && inputs.release-as == '' && inputs.nightly-version == '' }} + if: ${{ inputs.resume-publish != true }} uses: ./.github/actions/fleet/setup-odai - name: 'Mint PR branch token' if: ${{ steps.content-trust.outputs.trusted == 'true' }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 11b4273b9..b9a752b08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [7.2.0](https://github.com/SocketDev/socket-lib/releases/tag/v7.2.0) - 2026-10-18 + +### Fixed + +- **`release`** — skip model setup for explicit targets +- **`node`** — support Node 18 array primordials +- **`node`** — support npm 10 on Node 18 +- **`node`** — support Node 18.20.8 runtime +- **`release`** — resume from reserved bump source +- **`release`** — use hosted npm runner + +### Internal + +- **`ci`** — track publish coverage workflow +- **`ci`** — repair required workflow gates +- **`ci`** — remove consumer freshness gate +- **`ci`** — move quality audits to follow-ups + ## [7.1.1](https://github.com/SocketDev/socket-lib/releases/tag/v7.1.1) - 2026-10-15 ### Fixed diff --git a/package.json b/package.json index d7c0b14f0..408d393d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@socketsecurity/lib", - "version": "7.1.1", + "version": "7.2.0", "description": "Core utilities and infrastructure for Socket.dev security tools", "keywords": [ "Socket.dev", @@ -4154,8 +4154,6 @@ "setup:token:rotate": "node scripts/fleet/setup/token.mts --rotate", "setup:xcode": "node scripts/fleet/setup/xcode-license.mts", "setup:xcode-license": "node scripts/fleet/setup/xcode-license.mts", - "slack:auth": "node scripts/fleet/setup/slack-token.mts", - "slack:token": "node scripts/fleet/setup/slack-token.mts", "soak:bypass": "node scripts/fleet/soak-bypass.mts", "soak:check": "node scripts/fleet/check/soak-time-is-consistent.mts", "socket-wheelhouse:emit-schema": "node scripts/fleet/socket-wheelhouse-emit-schema.mts", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 861814a23..d3518e582 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3596,16 +3596,6 @@ importers: specifier: 'catalog:' version: '@socketsecurity/lib@7.1.0(typescript@7.1.0-dev.20261008.1)' - .claude/hooks/fleet/setup-signing: - dependencies: - '@socketsecurity/lib-stable': - specifier: 'catalog:' - version: '@socketsecurity/lib@7.1.0(typescript@7.1.0-dev.20261008.1)' - devDependencies: - '@types/node': - specifier: 'catalog:' - version: 26.6.4 - .claude/hooks/fleet/shallow-clone-guard: devDependencies: '@types/node': @@ -3884,16 +3874,6 @@ importers: specifier: 'catalog:' version: 26.6.4 - .claude/hooks/fleet/unbacked-claim-guard: - dependencies: - shell-quote: - specifier: 'catalog:' - version: 1.12.0 - devDependencies: - '@types/node': - specifier: 'catalog:' - version: 26.6.4 - .claude/hooks/fleet/unbacked-claim-nudge: devDependencies: '@types/node':