Release 2026.07.22 (v2.4.0) - #40
Conversation
The example customer server signs serially: one process, one PKCS#11 session, and synchronous pkcs11js calls that block Node's single event loop, capping signing throughput at about one CPU core. Add an opt-in docker-compose.cluster.yml that scales signing across cores on a single host: nginx (sole published port, optional TLS termination) routes /api/hsm/ (key generation, token writes) to a single primary replica and round-robins /api/ (signing, token reads) across a scalable signer pool. All replicas share the existing SoftHSM token volumes and MongoDB. The default single-process compose file and its per-process SSL behavior are unchanged. Document how to run, how to choose the replica count, the keygen-single-process and restart-after-keygen caveats, and the SSL model per mode in examples/server/README.md.
Every sign/verify re-scanned the whole token via C_FindObjects; with many keys on the token this scan dominates signing latency. PKCS#11 object handles stay valid for the lifetime of the session (the process lifetime here), so keep a per-process map from key id to handle and skip the scan after the first lookup.
Required by the check_version_change CI job, which expects every merge request to modify AGENT_VERSION in src/version.ts.
Review feedback: the example mongo has no authentication, so the new cluster compose file no longer publishes 27017 on the host. The customer-server containers reach it over the internal Docker network.
This reverts commit f51b29579e52efbf2890d5e5011a4fe4c449d298. The security pre-merge check rightly expects tests around changed key handling, and examples/server has no test infrastructure today. Keep this MR scoped to the opt-in cluster mode; the per-process handle cache (skipping the per-sign C_FindObjects token scan) is noted in the MR description as a recommended follow-up together with test coverage.
…ching runners for MR pipelines)
The format and test jobs ran 'npm install' against the public registry (registry.npmjs.org), which is unreachable from the Fireblocks CI runners (ECONNREFUSED), so both jobs failed. Add the shared configure_jfrog_npmrc before_script (writes an .npmrc pointing at the internal JFrog registry, authed via JFROG_NPM_READ_TOKEN) to the format and test jobs, matching the working setup on !21.
Address CodeRabbit review on !20: (1) the guide claimed cluster mode needs Agent v2.4.0+, but this MR ships AGENT_VERSION 2.2.9 -> correct to v2.2.9+; (2) the 'port already allocated' troubleshooting row said `docker compose down`, which only stops the single-process compose and leaves a cluster instance running -> document `docker compose -f docker-compose.cluster.yml down` for cluster mode.
main advanced (via !19) to AGENT_VERSION 2.3.0, which conflicted with this branch's 2.2.9. Resolve by bumping to 2.4.0 (the version the cluster-setup guide already documents for cluster mode) and align the guide's "Applies to" line back to v2.4.0+ so code and docs match.
- Remove .gitlab-ci.yml (internal CI; JFrog npmrc goes with it) - Remove .env.local/.env.prod/.env.test; add .env.example with placeholders including new WEBSOCKET_ENABLED and WS tuning vars - Update .gitignore to exclude .env* (allow .env.example) - Fix package.json: GitHub repo URL, drop @semantic-release/gitlab - Add CHANGELOG 2.4.0 entry (WebSocket delivery, resilience, cluster mode); strip GitLab hyperlinks from historical entries Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
ws@7.4.6, ws@7.5.11, ws@8.21.1 and @types/ws@8.18.1 were resolved from the internal Artifactory mirror — unreachable from GitHub CI. Point all four back to registry.npmjs.org. Integrity hashes unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", | ||
| "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", | ||
| "version": "8.21.1", | ||
| "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", |
There was a problem hiding this comment.
Semgrep identified an issue in your code:
package-lock.json contains a reference to a public package registry or CDN (https://registry.npmjs.org/ws/-/ws-8.21.1.tgz). Dependencies must be resolved through the approved internal JFrog/Artifactory registry. Update your .npmrc to point at the approved registry, delete node_modules and package-lock.json, then re-run npm install and commit the regenerated lockfile.
To resolve this comment:
✨ Commit fix suggestion
| "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", | |
| "resolved": "https://example.com/artifactory/api/npm/npm/ws/-/ws-8.21.1.tgz", |
View step-by-step instructions
-
Update the project’s
.npmrcso npm uses the approved internal registry instead of the public npm registry. Setregistry=to your internal JFrog/Artifactory URL, for exampleregistry=https://example.com/artifactory/api/npm/npm/. -
Add the lockfile replacement setting to
.npmrcif your environment requires it, so regenerated entries use the internal host consistently. For example, keep.npmrcwith only approved registry settings such asregistry=https://example.com/artifactory/api/npm/npm/. -
Delete the existing lockfile and installed modules so npm can regenerate
resolvedURLs from the approved registry. Removepackage-lock.jsonandnode_modules/. -
Regenerate dependencies through the internal registry by running
npm installafter the.npmrcchange. This should recreatepackage-lock.jsonwithresolvedentries that point to your internal registry instead ofhttps://registry.npmjs.org/.... -
Confirm the specific
wsentry now resolves through the internal registry, for example thenode_modules/wslockfile entry should use your approved Artifactory/JFrog URL rather than the publicregistry.npmjs.orgtarball URL.
💬 Ignore this finding
Reply with Semgrep commands to ignore this finding.
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by block-public-registry-refs-in-package-lock.
You can view more details about this finding in the Semgrep AppSec Platform.
| }, | ||
| "node_modules/@types/ws": { | ||
| "version": "8.18.1", | ||
| "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", |
There was a problem hiding this comment.
Semgrep identified an issue in your code:
package-lock.json contains a reference to a public package registry or CDN (https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz). Dependencies must be resolved through the approved internal JFrog/Artifactory registry. Update your .npmrc to point at the approved registry, delete node_modules and package-lock.json, then re-run npm install and commit the regenerated lockfile.
To resolve this comment:
✨ Commit fix suggestion
| "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", | |
| "resolved": "https://example.com/artifactory/api/npm/npm/@types/ws/-/ws-8.18.1.tgz", |
View step-by-step instructions
-
Update your project’s npm registry configuration to use the approved internal JFrog/Artifactory registry instead of the public npm registry.
Add or update.npmrcwithregistry=https://example.com/artifactory/api/npm/npm/or your approved internal registry URL. -
Remove any npm settings that force public downloads, such as
@scope:registry=https://registry.npmjs.org/or directregistry=https://registry.npmjs.org/entries in.npmrc, user-level npm config, or CI config. -
Regenerate the lockfile so the
resolvedentries are rewritten to the internal registry.
Run$ rm -rf node_modules package-lock.jsonand then$ npm install.
The newpackage-lock.jsonshould replace entries likehttps://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgzwith your internal registry URL. -
Verify that the regenerated lockfile no longer contains public URLs in
resolved,registry, ortarballfields for packages such as@types/wsand@types/node.
This works becausepackage-lock.jsonrecords the exact download source used during install.
💬 Ignore this finding
Reply with Semgrep commands to ignore this finding.
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by block-public-registry-refs-in-package-lock.
You can view more details about this finding in the Semgrep AppSec Platform.
Removing .env.prod over-cleaned: it is non-secret (empty auth token, public gateway URL) and is the project's shipped default config, required by `npm run build` (copy:env), the test suite (dotenv loads .env.prod for MOBILE_GATEWAY_URL), the Dockerfile (COPY .env.prod), and the README. Restore it, gitignore-allow it (!.env.prod), and drop .env.example (the README uses .env.prod as the template). .env.local/.env.test stay removed (dev/test scaffolding with demo JWTs). Build + 184 tests pass on Node 20. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- WebSocket push delivery, default-on (opt out with WEBSOCKET_ENABLED=false) - Process-level unhandledRejection / uncaughtException handlers - Cluster mode for the example customer server (see docs/cluster-setup-guide.md)
Summary
WEBSOCKET_ENABLED=false)unhandledRejection/uncaughtExceptionhandlersdocs/cluster-setup-guide.md,docker-compose.cluster.yml,nginx.conf)Cleanup
.env.exampleupdated with newWEBSOCKET_ENABLEDand optional WS tuning varspackage.jsonrepository URLAfter merge
Tag the merge commit as
2026.07.22:🤖 Generated with Claude Code