Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 67 additions & 8 deletions .github/workflows/provenance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,27 @@ on:
required: false
default: 'latest'
type: string
dry-run:
description: 'Build everything but do NOT publish, tag, or cut a release. Defaults to true so an accidental dispatch never reaches the registry — set to false for a real release.'
required: false
default: true
type: boolean
debug:
description: 'Enable debug output'
required: false
default: '0'
type: string
options:
- '0'
- '1'

permissions:
contents: read

# Serialize publishes per dist-tag. Two concurrent dispatches with the same
# tag would race on `npm publish` (one wins, the other 409s). Don't cancel an
# in-flight publish — a half-published release is worse than a queued one.
concurrency:
group: publish-${{ inputs.dist-tag }}
cancel-in-progress: false

jobs:
build:
name: Build and Publish
Expand Down Expand Up @@ -57,6 +66,7 @@ jobs:
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}"
# shellcheck disable=SC1003 # `tr -d '\\'` strips the leading backslash GNU coreutils prepends to a checksum line when the path has a backslash (Windows RUNNER_TEMP).
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
Expand Down Expand Up @@ -131,6 +141,7 @@ jobs:
exit 1
fi
curl -fsSL -o "$SFW_BIN" "$DOWNLOAD_URL"
# shellcheck disable=SC1003 # `tr -d '\\'` strips the leading backslash GNU coreutils prepends to a checksum line when the path has a backslash (Windows RUNNER_TEMP).
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
Expand Down Expand Up @@ -220,32 +231,46 @@ jobs:
fi
bash src/commands/manifest/scripts/maven-extension/build-jar.sh

# All three publishes use npm trusted publishing (OIDC): no long-lived
# NPM_TOKEN is set, so npm mints a short-lived registry token from the
# `id-token: write` OIDC token at publish time and attaches provenance.
# Each package (`socket`, `@socketsecurity/cli`,
# `@socketsecurity/cli-with-sentry`) must have a matching trusted
# publisher configured on npm for SocketDev/socket-cli + this workflow
# file + the v1.x branch, or the publish 404s on the token exchange.
#
# The publish steps are skipped entirely on a dry run (inputs.dry-run
# defaults to true), so an accidental dispatch builds but never reaches
# the registry. When skipped, publish_socket.outcome is not 'success',
# so the tag + release steps below also skip.
- run: INLINED_SOCKET_CLI_PUBLISHED_BUILD=1 pnpm run build:dist
- name: Publish socket
id: publish_socket
if: ${{ inputs.dry-run == false }}
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}"
- name: Publish @socketsecurity/cli (legacy)
if: ${{ inputs.dry-run == false }}
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}"
- name: Publish @socketsecurity/cli-with-sentry
if: ${{ inputs.dry-run == false }}
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<version> git tag at the published commit SHA after a
Expand All @@ -264,6 +289,7 @@ jobs:
# with persist-credentials: true (which would leak it to every later
# step including `pnpm install` postinstall scripts).
- name: Tag release (idempotent)
id: tag
if: steps.publish_socket.outcome == 'success'
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -272,6 +298,10 @@ jobs:
PUBLISHED_SHA=$(git rev-parse HEAD)
PUBLISHED_VERSION=$(node -p "require('./package.json').version")
TAG="v$PUBLISHED_VERSION"
# Emit the tag before any early exit so the release step below can
# consume it on every non-error path (fresh tag AND the same-SHA
# no-op).
echo "tag=$TAG" >> "$GITHUB_OUTPUT"

# 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
Expand Down Expand Up @@ -307,3 +337,32 @@ jobs:
-f "ref=refs/tags/$TAG" \
-f "sha=$PUBLISHED_SHA"
echo "Created tag $TAG at $PUBLISHED_SHA"

# Cut the immutable GitHub Release for the tag, idempotently. ORDER
# RULE: the tag + GH release are the FINAL markers of a release — they
# only exist AFTER the npm publish is confirmed live (this step is gated
# on the same publish_socket success as the tag step, and runs after
# it). Uses gh release (gh api under the hood) so GH_TOKEN only lives in
# this step's env, never written to `.git/config`. Skips cleanly if a
# release already exists for the tag so re-runs are safe.
- name: Cut GitHub release (idempotent)
if: steps.publish_socket.outcome == 'success'
Comment thread
jdalton marked this conversation as resolved.
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
TAG: ${{ steps.tag.outputs.tag }}
run: |
if gh release view "$TAG" --repo "$REPO" >/dev/null 2>&1; then
echo "Release $TAG already exists — no-op."
exit 0
fi
# --verify-tag: refuse to create if the tag ref is somehow missing
# (the tag step above creates it; this guards against a race).
# --generate-notes: auto-populate notes from commits since the last
# release.
gh release create "$TAG" \
--repo "$REPO" \
--title "$TAG" \
--verify-tag \
--generate-notes
echo "Created GitHub release $TAG"