From f9385b7c3af373bcc7f436e2be29eb2ae5e562de Mon Sep 17 00:00:00 2001 From: barslev Date: Fri, 10 Jul 2026 10:39:27 +0200 Subject: [PATCH] ci: restore provenance.yml publish workflow on main The v2 monorepo cascade (f6165b07e, "chore(ci): land 3 ci changes") removed .github/workflows/provenance.yml from main. Because main is the default branch, that de-registered the "Publish to npm registry" workflow from the Actions UI (no sidebar entry, no Run button) and prune-workflow-runs.yml is wiping its run history. That workflow is still the only publisher for the socket 1.1.x line (current npm latest) shipped from v1.x. Restore the file verbatim from v1.x so the workflow re-registers and is dispatchable again. Dispatch it selecting the v1.x branch as the ref. --- .github/workflows/provenance.yml | 309 +++++++++++++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 .github/workflows/provenance.yml diff --git a/.github/workflows/provenance.yml b/.github/workflows/provenance.yml new file mode 100644 index 000000000..358a7b2d5 --- /dev/null +++ b/.github/workflows/provenance.yml @@ -0,0 +1,309 @@ +name: Publish to npm registry + +on: + workflow_dispatch: + inputs: + dist-tag: + description: 'npm dist-tag (latest, next, beta, canary, backport, etc.)' + required: false + default: 'latest' + type: string + debug: + description: 'Enable debug output' + required: false + default: '0' + type: string + options: + - '0' + - '1' + +permissions: + contents: read + +jobs: + build: + name: Build and Publish + runs-on: ubuntu-latest + + permissions: + # `contents: write` needed to create the v tag via gh api + # at the end of this job. Token is scoped to the dedicated tag step + # via GH_TOKEN env; never persisted in `.git/config` (checkout keeps + # persist-credentials: false so build/install steps can't reach it). + contents: write + id-token: write # NPM trusted publishing via OIDC + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 (2026-05-20) + with: + persist-credentials: false + + - name: Install pnpm + shell: bash + run: | # zizmor: ignore[github-env] + PNPM_VERSION="10.33.0" + PNPM_DIR="${RUNNER_TEMP:-/tmp}/pnpm-bin" + KERNEL="$(uname -s | cut -d- -f1)" + ARCH="$(uname -m)" + case "${KERNEL}-${ARCH}" in + Linux-x86_64) ASSET="pnpm-linux-x64" ; EXPECTED_SHA256="8d4e8f7d778e8ac482022e2577011706a872542f6f6f233e795a4d9f978ea8b5" ;; + Linux-aarch64) ASSET="pnpm-linux-arm64" ; EXPECTED_SHA256="06755ad2817548b84317d857d5c8003dc6e9e28416a3ea7467256c49ab400d48" ;; + Darwin-x86_64) ASSET="pnpm-macos-x64" ; EXPECTED_SHA256="c31e29554b0e3f4e03f4617195c949595e4dca36085922003de4896c3ca4057d" ;; + Darwin-arm64) ASSET="pnpm-macos-arm64" ; EXPECTED_SHA256="ed8a1f140f4de457b01ebe0be3ae28e9a7e28863315dcd53d22ff1e5a32d63ae" ;; + MINGW64_NT-x86_64|MSYS_NT-x86_64) ASSET="pnpm-win-x64.exe" ; EXPECTED_SHA256="afc96009dc39fe23a835d65192049e6a995f342496b175585dc2beda7d42d33f" ;; + *) echo "Unsupported platform: ${KERNEL}-${ARCH}" >&2; exit 1 ;; + esac + PNPM_BIN="$PNPM_DIR/$ASSET" + if [ ! -x "$PNPM_BIN" ]; then + mkdir -p "$PNPM_DIR" + curl -fsSL -o "$PNPM_BIN" "https://github.com/pnpm/pnpm/releases/download/v${PNPM_VERSION}/${ASSET}" + ACTUAL_SHA256="$( (sha256sum "$PNPM_BIN" 2>/dev/null || shasum -a 256 "$PNPM_BIN") | cut -d' ' -f1 | tr -d '\\')" + if [ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]; then + echo "Checksum mismatch for ${ASSET}!" >&2 + echo " Expected: ${EXPECTED_SHA256}" >&2 + echo " Actual: ${ACTUAL_SHA256}" >&2 + rm -f "$PNPM_BIN" + exit 1 + fi + chmod +x "$PNPM_BIN" + # Create pnpm alias. Windows needs a .exe copy; Unix uses a symlink. + if [[ "$ASSET" == *.exe ]]; then + cp "$PNPM_BIN" "$PNPM_DIR/pnpm.exe" + else + ln -sf "$PNPM_BIN" "$PNPM_DIR/pnpm" + fi + fi + echo "$PNPM_DIR" >> "${GITHUB_PATH:-/dev/null}" + + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 25.9.0 + cache: pnpm + registry-url: https://registry.npmjs.org + scope: '@socketsecurity' + + - name: Download sfw + shell: bash + env: + GH_TOKEN: ${{ github.token }} + SOCKET_API_KEY: ${{ secrets.SOCKET_API_KEY }} # zizmor: ignore[secrets-outside-env] + run: | # zizmor: ignore[github-env] + # Pinned version + per-platform checksum pairs. Bumping a tool + # requires updating the matching version AND every platform's + # SHA256 in the same commit, otherwise the download / verify + # steps will diverge. + SFW_FREE_VERSION="1.7.2" + SFW_ENTERPRISE_VERSION="1.7.2" + SFW_DIR="${RUNNER_TEMP:-/tmp}/sfw-bin" + KERNEL="$(uname -s | cut -d- -f1)" + ARCH="$(uname -m)" + USE_ENTERPRISE=false + [ -n "$SOCKET_API_KEY" ] && USE_ENTERPRISE=true + if [ "$USE_ENTERPRISE" = "true" ]; then + REPO="SocketDev/firewall-release" + SFW_VERSION="$SFW_ENTERPRISE_VERSION" + case "${KERNEL}-${ARCH}" in + Linux-x86_64) ASSET="sfw-linux-x86_64" ; SFW_BIN="$SFW_DIR/sfw" ; EXPECTED_SHA256="4482b52e6367bd4610519bfd57a104d5907ec87d5399142ed3bb3d222de1f33d" ;; + Linux-aarch64) ASSET="sfw-linux-arm64" ; SFW_BIN="$SFW_DIR/sfw" ; EXPECTED_SHA256="c24a79c27e1a01a59b7a160c165930ae029816c72b141fcfcdb2f73e0774898a" ;; + Darwin-x86_64) ASSET="sfw-macos-x86_64" ; SFW_BIN="$SFW_DIR/sfw" ; EXPECTED_SHA256="da252d2a9a5d0edb271bb771e0d01b9cd6fa1635b6d765f61efd61edb6739f12" ;; + Darwin-arm64) ASSET="sfw-macos-arm64" ; SFW_BIN="$SFW_DIR/sfw" ; EXPECTED_SHA256="b1cdc3bdbd2a3161247bd5cc215eb3c44a90b87fe0b800a33889a14f61bb0d6d" ;; + MINGW64_NT-x86_64|MSYS_NT-x86_64) ASSET="sfw-windows-x86_64.exe" ; SFW_BIN="$SFW_DIR/sfw.exe" ; EXPECTED_SHA256="e52ad806a1c41b440f04098eb1c7e407845f03f5740a6a79006ba6fd172056ec" ;; + *) echo "Unsupported platform: ${KERNEL}-${ARCH}" >&2; exit 1 ;; + esac + else + REPO="SocketDev/sfw-free" + SFW_VERSION="$SFW_FREE_VERSION" + case "${KERNEL}-${ARCH}" in + Linux-x86_64) ASSET="sfw-free-linux-x86_64" ; SFW_BIN="$SFW_DIR/sfw" ; EXPECTED_SHA256="93e2d9dfa244b82a74e014dc26b1c6af18b4adec20f35254378943db5fe91411" ;; + Linux-aarch64) ASSET="sfw-free-linux-arm64" ; SFW_BIN="$SFW_DIR/sfw" ; EXPECTED_SHA256="84a045e4e1bb320cc5c0d3929f02e53f199398b5be0637e8846d02d9ef0027b1" ;; + Darwin-x86_64) ASSET="sfw-free-macos-x86_64" ; SFW_BIN="$SFW_DIR/sfw" ; EXPECTED_SHA256="a5427d479d440f08e3789fa191ba57599be64997196daf42e67d964fec0382b4" ;; + Darwin-arm64) ASSET="sfw-free-macos-arm64" ; SFW_BIN="$SFW_DIR/sfw" ; EXPECTED_SHA256="248fb588e1e1a27e7192f7b079f739fc29a9de61f0bad7e90928363022dc5643" ;; + MINGW64_NT-x86_64|MSYS_NT-x86_64) ASSET="sfw-free-windows-x86_64.exe" ; SFW_BIN="$SFW_DIR/sfw.exe" ; EXPECTED_SHA256="6d333b4cac9d7c5712e2e99677ca634ac8a3020d550c6308312c60bea97f0a28" ;; + *) echo "Unsupported platform: ${KERNEL}-${ARCH}" >&2; exit 1 ;; + esac + fi + if [ ! -x "$SFW_BIN" ]; then + mkdir -p "$SFW_DIR" + DOWNLOAD_URL="$(gh api "repos/${REPO}/releases/tags/v${SFW_VERSION}" \ + --jq ".assets[] | select(.name == \"$ASSET\") | .browser_download_url")" + if [ -z "$DOWNLOAD_URL" ]; then + echo "Asset ${ASSET} not found in ${REPO}@v${SFW_VERSION}" >&2 + exit 1 + fi + curl -fsSL -o "$SFW_BIN" "$DOWNLOAD_URL" + ACTUAL_SHA256="$( (sha256sum "$SFW_BIN" 2>/dev/null || shasum -a 256 "$SFW_BIN") | cut -d' ' -f1 | tr -d '\\')" + if [ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]; then + echo "Checksum mismatch for ${ASSET} (${REPO}@v${SFW_VERSION})!" >&2 + echo " Expected: ${EXPECTED_SHA256}" >&2 + echo " Actual: ${ACTUAL_SHA256}" >&2 + rm -f "$SFW_BIN" + exit 1 + fi + chmod +x "$SFW_BIN" + fi + echo "SFW_BIN=$SFW_BIN" >> "${GITHUB_ENV:-/dev/null}" + echo "SFW_IS_ENTERPRISE=$USE_ENTERPRISE" >> "${GITHUB_ENV:-/dev/null}" + if [ "$USE_ENTERPRISE" = "true" ]; then + echo "SOCKET_API_KEY=$SOCKET_API_KEY" >> "${GITHUB_ENV:-/dev/null}" + fi + + - name: Create sfw shims + shell: bash + run: | # zizmor: ignore[github-env] + SHIM_DIR="${RUNNER_TEMP:-/tmp}/sfw-shim" + rm -rf "$SHIM_DIR" + mkdir -p "$SHIM_DIR" + IS_WINDOWS=false + [[ "$OSTYPE" == msys* || "$OSTYPE" == cygwin* ]] && IS_WINDOWS=true + msys_to_win_path() { + if $IS_WINDOWS && [[ "$1" =~ ^/([a-zA-Z])/(.*) ]]; then + echo "${BASH_REMATCH[1]^^}:\\${BASH_REMATCH[2]//\//\\}" + else + echo "$1" + fi + } + strip_shim_dir() { echo "$PATH" | tr ':' '\n' | grep -vxF "$SHIM_DIR" | paste -sd: -; } + CLEAN_PATH="$(strip_shim_dir)" + # Wrapper mode ecosystems (sfw-free): + # JavaScript/TypeScript: npm, yarn, pnpm + # Python: pip, uv + # Rust: cargo + # https://github.com/SocketDev/sfw-free?tab=readme-ov-file#supported-package-managers + # + # Additional wrapper mode ecosystems (sfw-enterprise): + # Ruby: gem, bundler + # .NET: nuget + # Go: go (Linux only) + # https://github.com/SocketDev/firewall-release/wiki#support-matrix + SSL_WORKAROUND="" + SHIM_CMDS="npm yarn pnpm pip uv cargo" + if [ "$SFW_IS_ENTERPRISE" = "true" ]; then + SHIM_CMDS="npm yarn pnpm pip uv cargo gem bundler nuget" + # Go wrapper mode is only supported on Linux. + [[ "$OSTYPE" == linux* ]] && SHIM_CMDS="$SHIM_CMDS go" + else + SSL_WORKAROUND='export GIT_SSL_NO_VERIFY=true # Workaround: sfw-free does not yet set GIT_SSL_CAINFO.' + fi + for CMD in $SHIM_CMDS; do + REAL="$(PATH="$CLEAN_PATH" command -v "$CMD" 2>/dev/null || true)" + [ -z "$REAL" ] && continue + REAL="$(msys_to_win_path "$REAL")" + SHIM_LINES=('#!/bin/bash' "export PATH=\"\$(echo \"\$PATH\" | tr ':' '\n' | grep -vxF '${SHIM_DIR}' | paste -sd: -)\"") + [ -n "$SSL_WORKAROUND" ] && SHIM_LINES+=("$SSL_WORKAROUND") + SHIM_LINES+=("exec \"${SFW_BIN}\" \"${REAL}\" \"\$@\"") + printf '%s\n' "${SHIM_LINES[@]}" > "$SHIM_DIR/$CMD" + chmod +x "$SHIM_DIR/$CMD" + if $IS_WINDOWS; then + printf '@echo off\r\nset "PATH=;%%PATH%%;"\r\nset "PATH=%%PATH:;%s;=;%%"\r\nset "PATH=%%PATH:~1,-1%%"\r\n"%s" "%s" %%*\r\n' \ + "$SHIM_DIR" "$SFW_BIN" "$REAL" > "$SHIM_DIR/$CMD.cmd" + fi + done + echo "$SHIM_DIR" >> "${GITHUB_PATH:-/dev/null}" + echo "SFW_SHIM_DIR=$SHIM_DIR" >> "${GITHUB_ENV:-/dev/null}" + + - name: Install dependencies + run: pnpm install --loglevel error + + # Compile the Maven manifest extension jar so the dist build bundles it + # into dist/manifest-scripts (the jar is never committed; it ships only in + # the published package). Invoke build-jar.sh directly, NOT via `pnpm run`: + # Socket Firewall wraps the package managers (npm/pnpm/...) it shims, so a + # `pnpm run` would route the Maven wrapper's download through sfw, which + # fails on the non-package fetch. Running bash directly keeps the Maven + # download outside the shimmed process tree. The org action allowlist forbids + # actions/setup-java, so use a JDK pre-installed on the runner image + # (JAVA_HOME_17_X64), falling back to the runner's default `java`. + - name: Build Maven manifest extension jar + run: | + if [ -n "${JAVA_HOME_17_X64:-}" ]; then + export JAVA_HOME="$JAVA_HOME_17_X64" + fi + bash src/commands/manifest/scripts/maven-extension/build-jar.sh + + - run: INLINED_SOCKET_CLI_PUBLISHED_BUILD=1 pnpm run build:dist + - name: Publish socket + id: publish_socket + run: npm publish --provenance --access public --tag "${NPM_DIST_TAG}" + continue-on-error: true + env: + NPM_DIST_TAG: ${{ inputs.dist-tag }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # zizmor: ignore[secrets-outside-env] + SOCKET_CLI_DEBUG: ${{ inputs.debug }} + - run: INLINED_SOCKET_CLI_PUBLISHED_BUILD=1 INLINED_SOCKET_CLI_LEGACY_BUILD=1 pnpm run build:dist + env: + SOCKET_CLI_DEBUG: ${{ inputs.debug }} + - run: npm publish --provenance --access public --tag "${NPM_DIST_TAG}" + continue-on-error: true + env: + NPM_DIST_TAG: ${{ inputs.dist-tag }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # zizmor: ignore[secrets-outside-env] + SOCKET_CLI_DEBUG: ${{ inputs.debug }} + - run: INLINED_SOCKET_CLI_PUBLISHED_BUILD=1 INLINED_SOCKET_CLI_SENTRY_BUILD=1 pnpm run build:dist + env: + SOCKET_CLI_DEBUG: ${{ inputs.debug }} + - run: npm publish --provenance --access public --tag "${NPM_DIST_TAG}" + continue-on-error: true + env: + NPM_DIST_TAG: ${{ inputs.dist-tag }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # zizmor: ignore[secrets-outside-env] + SOCKET_CLI_DEBUG: ${{ inputs.debug }} + + # Create v git tag at the published commit SHA after a + # successful socket-package publish, idempotently. GitHub Release + # Immutability ("Disallow assets and tags from being modified once a + # release is published") freezes tags once bound to a Release, so: + # - existing tag at same SHA → no-op + # - existing tag at different SHA → hard-fail (operator recovery + # required) + # Gated on the first publish step (publish_socket — the `socket` npm + # package) actually succeeding; the cli / cli-with-sentry publishes + # use `continue-on-error: true` and don't gate the tag. + # + # Uses gh api (not `git push`) so the token only lives in this step's + # env, never written to `.git/config` by an earlier `actions/checkout` + # with persist-credentials: true (which would leak it to every later + # step including `pnpm install` postinstall scripts). + - name: Tag release (idempotent) + if: steps.publish_socket.outcome == 'success' + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + run: | + PUBLISHED_SHA=$(git rev-parse HEAD) + PUBLISHED_VERSION=$(node -p "require('./package.json').version") + TAG="v$PUBLISHED_VERSION" + + # Look up any existing tag ref. gh api exits non-zero on 404 (tag + # absent) and writes the error body to stdout, so branch on the + # exit code — never on whether stdout is empty. EXISTING_JSON is + # only valid JSON when the call succeeded. + if EXISTING_JSON=$(gh api "repos/$REPO/git/ref/tags/$TAG" 2>/dev/null); then + # The ref's object is either a commit (lightweight tag) or a tag + # object (annotated/signed tag, e.g. the hand-created `git tag -s` + # tags). For an annotated tag, object.sha is the tag-object SHA, + # not the commit — dereference it via git/tags to get the commit + # the tag actually points at before comparing. + REF_TYPE=$(echo "$EXISTING_JSON" | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).object.type") + REF_OBJECT_SHA=$(echo "$EXISTING_JSON" | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).object.sha") + if [ "$REF_TYPE" = "tag" ]; then + EXISTING_SHA=$(gh api "repos/$REPO/git/tags/$REF_OBJECT_SHA" --jq '.object.sha') + else + EXISTING_SHA="$REF_OBJECT_SHA" + fi + if [ "$EXISTING_SHA" = "$PUBLISHED_SHA" ]; then + echo "Tag $TAG already exists at $PUBLISHED_SHA — no-op." + exit 0 + fi + echo "::error::Tag $TAG exists at $EXISTING_SHA but publish SHA is $PUBLISHED_SHA." + echo "::error::Release immutability is enabled; this requires manual recovery:" + echo "::error:: 1. Delete any GitHub Release tied to $TAG" + echo "::error:: 2. Delete the tag via the API" + echo "::error:: 3. Re-run this workflow" + exit 1 + fi + + gh api "repos/$REPO/git/refs" \ + -X POST \ + -f "ref=refs/tags/$TAG" \ + -f "sha=$PUBLISHED_SHA" + echo "Created tag $TAG at $PUBLISHED_SHA"