Skip to content

feat(release): publish to every supported ecosystem from one release dispatch (Maven Central + NuGet + consolidated release.yml) - #138

Open
Mikola Lysenko (mikolalysenko) wants to merge 1 commit into
mainfrom
feat/publish-all-ecosystems
Open

feat(release): publish to every supported ecosystem from one release dispatch (Maven Central + NuGet + consolidated release.yml)#138
Mikola Lysenko (mikolalysenko) wants to merge 1 commit into
mainfrom
feat/publish-all-ecosystems

Conversation

@mikolalysenko

Copy link
Copy Markdown
Collaborator

Summary

One Release dispatch now publishes (or stages) the CLI to every supported ecosystem package manager: crates.io, npm, PyPI, RubyGems (both gems), Packagist/Composer, Maven Central (new), and NuGet (new) — with idempotent jobs so "Re-run failed jobs" recovers a partial release.

New distribution channels

Both mirror the gem/composer download-on-first-run launcher pattern: fetch the version-matched socket-patch-<triple> archive from the GitHub release, verify against SHA256SUMS (HTTPS-only including redirects), cache per-user under the shared socket-patch/bin/<version>/<target>/ scheme, honor SOCKET_PATCH_BIN as the airgap escape.

Registry Package Install / run
Maven Central dev.socket:socket-patch mvn dependency:copy -Dartifact=dev.socket:socket-patch:<V> + java -jar, or jbang dev.socket:socket-patch:<V>
NuGet SocketSecurity.SocketPatch dotnet tool install -g SocketSecurity.SocketPatchsocket-patch on PATH
  • maven/socket-patch/ — dependency-free Java 11 launcher (Launcher.java is a line-faithful port of the gem launcher, incl. the security comments); pom wired for the Central Publisher Portal (GPG signing, sources+javadoc jars, central-publishing-maven-plugin, reproducible-build timestamp, <!-- x-version-sync --> marker). Honors https_proxy/HTTPS_PROXY env proxies (the JVM default ignores them; every sibling launcher honors them).
  • nuget/socket-patch/ — net8.0 dotnet tool (RollForward=Major); the managed shim is the launcher: no shell-outs (BCL zip/tar+gzip), manual redirect vetting, executability-gated cache with self-heal (a mode-stripped cache entry is re-downloaded instead of looping on Permission denied).

Release pipeline consolidation (release.yml)

  • Deleted release-ecosystems.yml — its release: published trigger could never fire: the release is created with GITHUB_TOKEN, and GitHub suppresses workflow events caused by that token (a push: tags: trigger would be suppressed for the same reason). The launcher-gem and Packagist jobs only ever ran by hand. Everything now fans out from the one workflow_dispatch, launcher jobs gated on needs: [version, github-release].
  • Fixed the no-op tag guard: the shallow tagless checkout made git rev-parse v$VERSION always fail, so the "tag already exists" check always passed. Replaced with a stateless git ls-remote check that also permits same-commit retries.
  • Idempotency across the board: gh release view → upload --clobber fallback; crates.io published-version probes; skip-existing: true on both PyPI uploads; the existing gem list --remote guards; --skip-duplicate on NuGet; repo1 probe on Maven Central; staged-but-unapproved npm versions tolerated on re-run.
  • RubyGems → OIDC trusted publishing: both gems publish from one consolidated job via rubygems/configure-rubygems-credentials (one exchange covers both gems); RUBYGEMS_API_KEY and the zizmor suppressions are gone. (rubygems/release-gem was evaluated and rejected — it runs rake release, which re-tags.)
  • PyPI: job now runs in environment: pypi (publishers registered without an environment constraint still match, so this lands safely before the tightening below).
  • npm fixes:
    • prepack: tsc — the published ./schema export pointed at a gitignored, never-built dist/; every published tarball shipped a broken export.
    • devDependencies installed with npm install --no-save --ignore-scripts, not npm ci: version-sync refreshes the lockfile while the release's platform packages are unpublished, so npm records them as hollow optional stubs and npm ci refuses the file (reproduced with npm 11.6.2: lock file's @socketsecurity/socket-patch-*@ does not satisfy ...@3.3.0). npm install skips unresolvable optionals — verified for both the published (3.3.0) and unpublished (simulated 9.9.9) states. The refreshed lockfile is committed, so npm ci works again for local dev.
  • Composer/Packagist unblocked: Packagist only ingests a composer.json at the VCS repo root — the channel could never publish as previously built. Manifest moved to the root (bincomposer/socket-patch/bin/socket-patch) with a fail-closed .gitattributes export-ignore allowlist so the dist zip ships only the composer files. ⚠️ Deliberate side effect (documented in .gitattributes + CHANGELOG): GitHub's auto-generated "Source code" release archives now contain only the allowlisted files; clone the repo for full source.
  • scripts/version-sync.sh stamps the four new version sites (pom marker line, Launcher.java constant, csproj <Version>, Program.cs fallback); lint-ecosystems in CI now builds the jar (mvn package -Dgpg.skip=true), packs the tool (dotnet pack), and validates the root composer.json. All new uses: are SHA-pinned and were verified against their tags (pin-check-compliant).

Verification

  • Live e2e against the real v3.3.0 release, both launchers: fresh cache → download → SHA256SUMS verify → socket-patch 3.3.0 (exit 0); cache hit on second run; SOCKET_PATCH_BIN stub honored with child exit code (7) propagated; non-executable override falls through; (.NET) mode-stripped cache entry self-heals via re-download. Full mvn package produces jar+sources+javadoc with correct Main-Class/Implementation-Version; dotnet pack clean with the informational version equal to the package version.
  • bash scripts/version-sync.sh 3.3.0 is a byte-exact no-op; git archive of the tree ships exactly the composer allowlist; YAML/shellcheck/php -l/gem build/javac --release 11 all green; no references to the deleted workflow remain.
  • Adversarially reviewed (3 independent reviewers + empirical adjudication); 9 confirmed findings fixed, including the npm ci release-breaker above.

One-time registry setup (maintainer checklist)

Publishing jobs skip gracefully with a ::notice until these land:

  • GitHub environments: create pypi, maven-central, nuget (and confirm rubygems, packagist); optionally add required reviewers to gate publishes. Delete the now-unused RUBYGEMS_API_KEY secret.
  • rubygems.org: two pending trusted publishers — gems socket-patch and socket-patch-bundler, owner SocketDev, repo socket-patch, workflow release.yml, environment rubygems (create shortly before the release; check the expiry window on the form).
  • PyPI: tighten the socket-patch publisher to require environment pypi; register the pending publisher for socket-patch-hook if it has never published.
  • packagist.org: after merge, submit https://github.com/SocketDev/socket-patch under the socketsecurity vendor; add PACKAGIST_USERNAME/PACKAGIST_TOKEN to the packagist environment; confirm the GitHub auto-update hook.
  • Maven Central (no OIDC exists for Central as of 2026-07 — this is the one long-lived-secret registry): portal account → claim dev.socket via DNS TXT on socket.dev → portal user token → CENTRAL_USERNAME/CENTRAL_PASSWORD; generate a release GPG keypair, publish the public key to keyserver.ubuntu.com + keys.openpgp.org, store CENTRAL_GPG_PRIVATE_KEY/CENTRAL_GPG_PASSPHRASE.
  • nuget.org: org account (+ optional SocketSecurity.* prefix reservation); Trusted Publishing policy (repo SocketDev/socket-patch, workflow file release.yml, environment nuget); NUGET_USER secret + a scoped NUGET_API_KEY fallback (keep armed for the inaugural push).

Confirm on the first real release: npm stage list output format (the staged-version re-run tolerance greps it best-effort); actions/setup-java skipping GPG import when the key secret is empty; whether nuget.org allows a brand-new package ID via an OIDC temp key.

🤖 Generated with Claude Code

… Maven Central + NuGet channels, consolidated release.yml, Packagist root manifest

Two new distribution channels, both download-on-first-run launchers
mirroring gem/composer (version-matched GitHub release binary, SHA256SUMS
verification, HTTPS-only incl. redirects, per-user cache, SOCKET_PATCH_BIN
airgap escape):

- Maven Central: dev.socket:socket-patch — dependency-free Java 11 launcher
  jar (java -jar / mvn dependency:copy / jbang), Central Portal publishing
  with GPG signing + sources/javadoc jars, env https_proxy support.
- NuGet: SocketSecurity.SocketPatch — net8.0 dotnet tool (`dotnet tool
  install -g` puts socket-patch on PATH), executability-gated cache with
  self-heal, no shell-outs (BCL zip/tar).

Release pipeline consolidation:

- Delete release-ecosystems.yml: its `release: published` trigger could
  never fire (GitHub suppresses events from GITHUB_TOKEN-created releases),
  so the launcher-gem/Packagist jobs only ever ran by hand. All publishing
  now fans out from the single workflow_dispatch in release.yml, with
  launcher jobs gated on `needs: [version, github-release]`.
- Fix the no-op tag guard (shallow tagless checkout) with a stateless
  `git ls-remote` check that permits same-commit retries; make
  github-release idempotent (view -> upload --clobber); probe crates.io
  before publishing; add `environment: pypi` + skip-existing; migrate both
  gems to one RubyGems OIDC trusted-publishing job; port the Packagist ping.
- npm: add prepack:tsc so the published ./schema export actually contains
  dist/ (it pointed at a gitignored, never-built directory); install
  devDependencies with `npm install --no-save --ignore-scripts` — NOT
  `npm ci`, which refuses the lockfile's hollow optional platform entries
  (version-sync refreshes the lock while the new platform packages are
  unpublished; reproduced with npm 11.6.2); tolerate staged-but-unapproved
  versions on re-runs; refresh package-lock.json.
- Packagist requires the manifest at the VCS repo root: move composer.json
  there (bin -> composer/socket-patch/bin/socket-patch) with a fail-closed
  .gitattributes export-ignore allowlist so the dist zip ships only the
  composer files. Deliberate side effect (documented): GitHub "Source code"
  release archives now contain only the allowlisted files.
- version-sync.sh stamps the four new version sites; lint-ecosystems builds
  the jar (`mvn package -Dgpg.skip=true`) and packs the tool (`dotnet pack`)
  and validates the root composer.json.

Both launchers e2e-verified against the live v3.3.0 release: fresh-cache
download + SHA verify + `socket-patch 3.3.0`, cache hit, stub escape with
exit-code propagation, non-executable-override fall-through, and (.NET)
mode-stripped-cache self-heal.

One-time registry setup (environments, trusted publishers, dev.socket DNS
claim, GPG key, nuget.org policy) is listed in the PR description; RubyGems,
Packagist, Maven Central and NuGet jobs skip gracefully (or fail loud where
sanctioned) until credentials land.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@socket-security

Copy link
Copy Markdown

@socket-security

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Potentially malicious package (AI signal): github actions/setup-dotnet is 72.0% likely malicious

Notes: This fragment is a high-impact disruption pattern: it conditionally relocates existing .NET SDK/runtime directories from standard locations to randomized destinations, with no rollback or safeguard logic shown. While it could theoretically be part of an intentional uninstall/cleanup workflow, the use of randomized, non-obvious relocation strongly aligns with sabotage/denial-of-service against build and runtime environments. In a supply-chain context, this is a serious red flag and should be treated as suspicious until verified by surrounding code and intended execution path.

Confidence: 0.72

Severity: 0.80

From: .github/workflows/release.ymlgithub/actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1

ℹ Read more on: This package | This alert | What is AI-detected potential malware?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Given the AI system's identification of this package as malware, extreme caution is advised. It is recommended to avoid downloading or installing this package until the threat is confirmed or flagged as a false positive.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore github/actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants