feat: add sfw input to wrap vp install with Socket Firewall Free#72
feat: add sfw input to wrap vp install with Socket Firewall Free#72fengmk2 wants to merge 21 commits into
Conversation
When `sfw: true`, the action downloads the matching `sfw` binary from github.com/SocketDev/sfw-free/releases (auto-detected per OS/arch, with musl support on Alpine) and runs `sfw vp install ...` so the underlying package manager's network fetches are inspected before install. Other `vp` commands run unwrapped. CI: new `test-sfw` (Ubuntu/macOS/Windows x latest/alpha) and `test-sfw-alpine` (alpine:3.23, musl branch) jobs exercise the new input end-to-end.
New job installs `is-odd` benignly to put sfw on PATH, then runs `sfw vp install lodahs` (lodash typosquat) and asserts the install exits non-zero. `lodahs` is the same canary Socket uses in their bun-security-scanner workflow: https://github.com/SocketDev/bun-security-scanner/blob/main/.github/workflows/test.yml Verifies sfw actually intercepts malicious packages end-to-end, not just that the wrapping plumbing is wired.
sfw v1.10.0 issues a CA cert with a present-but-empty EKU extension. OpenSSL accepts it; rustls (vp's TLS stack), Go crypto/x509, and other strict implementations reject it as UnknownIssuer. So `vp install` through sfw works on Ubuntu only because pnpm is preinstalled and vp skips the bootstrap fetch. On macOS / Windows, vp must fetch `https://registry.npmjs.org/pnpm/latest` and the handshake fails before sfw can inspect the install. Action still installs the sfw binary on all OSes (asset mapping unit- tested) so users can call `sfw npm ci` directly; only setup-vp's own run-install path is Linux-verified for now. Tracking upstream: SocketDev/sfw-free#30 SocketDev/sfw-free#43
sfw v1.10.0 issues a TLS cert with an empty EKU extension that vp's
rustls rejects (UnknownIssuer). To keep `sfw: true` safe to set in
cross-platform workflows, the action now:
- Checks process.platform === 'linux' via isSfwSupported()
- On non-Linux: emits a warning, skips the sfw binary download, and
runs plain `vp install` (no sfw wrap)
- On Linux: behavior unchanged — downloads sfw and runs `sfw vp install`
CI: test-sfw matrix is restored to Linux/macOS/Windows. Linux asserts
sfw is on PATH; macOS/Windows assert sfw is NOT on PATH (fallback) and
the plain install still completes.
Tracking upstream:
SocketDev/sfw-free#30
SocketDev/sfw-free#43
The warning now links to #73 (the consolidated follow-up tracker) instead of SocketDev/sfw-free#30, since #73 is the single place that aggregates both the upstream sfw fix and the vp-side work needed to lift the Linux-only restriction.
Replace the direct SocketDev/sfw-free#30 / #43 references with a link to the consolidated #73 tracker, which already links out to both upstream issues plus the vp-side work needed.
The test-sfw job header now points to #73 (which already fans out to SocketDev/sfw-free#30 / #43 and the vp-side work) instead of duplicating the upstream links. The lodahs canary citation to SocketDev/bun-security-scanner is left in place — it's a pattern reference, not an upstream tracking link.
The comment used to enumerate the specific sfw v1.10.0 EKU symptoms and the SocketDev/sfw-free#30/#43 upstream issues. Those details belong on the tracker — the source comment just needs to point readers to it.
There was a problem hiding this comment.
Pull request overview
This PR adds an optional sfw input to the action to wrap vp install with Socket Firewall Free, enabling dependency-fetch inspection during installs while preserving existing behavior by default.
Changes:
- Introduces a new
sfwboolean input (wired throughaction.yml,README, andgetInputs()/Inputstyping). - Adds
installSfw()to download and place thesfwbinary onPATH, and updates install execution to runsfw vp install ...when enabled. - Extends CI workflows with new
test-sfwjobs (including Alpine/musl validation and a “blocks malicious” canary test).
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils.test.ts | Updates test fixtures to include the new sfw input field. |
| src/types.ts | Extends Inputs with sfw: boolean. |
| src/run-install.ts | Switches install execution to sfw vp install ... when inputs.sfw is enabled. |
| src/install-viteplus.test.ts | Updates base Inputs fixture to include sfw. |
| src/install-sfw.ts | Adds SFW download/install logic and platform/musl detection helpers. |
| src/install-sfw.test.ts | Adds unit coverage for asset selection and platform support gating. |
| src/inputs.ts | Parses sfw via getBooleanInput("sfw"). |
| src/inputs.test.ts | Adds coverage for parsing the sfw input. |
| src/index.ts | Installs sfw before running installs (Linux-only fallback behavior) and passes an effective sfw flag to runViteInstall. |
| README.md | Documents sfw usage, including Linux-only fallback rationale. |
| action.yml | Exposes the sfw input for action consumers. |
| .github/workflows/test.yml | Adds new CI jobs validating sfw behavior across OSes and on Alpine/musl. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
New Linux-only job iterates over each package manager vp auto-detects via lockfile and verifies `sfw vp install` succeeds end-to-end with each. bun is marked experimental (continue-on-error) since sfw does not officially list it as a supported PM.
- yarn: set YARN_ENABLE_IMMUTABLE_INSTALLS=false so Yarn Berry can regenerate the empty yarn.lock under CI (the previous failure was YN0028 — sfw itself worked fine, fetching 1 package successfully). - bun: drop the experimental/continue-on-error flag — bun installs through sfw cleanly in the first run, so failures should gate CI.
Yarn Berry's default Plug'n'Play linker means a plain `node -e
require('is-odd')` from the verify step can't resolve the module
(it's in .yarn/cache, not node_modules). Add a .yarnrc.yml only for
the yarn matrix entry so all four package managers produce the same
node_modules layout for the test.
The split was over-engineering — they test orthogonal axes (platform
fallback × package-manager diversity) that compose naturally in a
single matrix.
Merged: os × version × package-manager with excludes so non-Linux
only runs the default PM (pnpm). 12 jobs total instead of 14:
- Linux × {pnpm,npm,yarn,bun} × {latest,alpha} = 8 (sfw wrap)
- {macos,windows} × pnpm × {latest,alpha} = 4 (sfw fallback)
Failure isolation improves too — a yellow check now reads
`test-sfw (windows-latest, latest, pnpm)` so you see which exact
combo broke.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit e6024b3. Configure here.
- case statement: add `*)` default branch so future axis additions fail loudly with a clear message instead of opaquely at the redirect. - yarn config: move `enableImmutableInstalls: false` from the action's env block into the same `.yarnrc.yml` that already sets `nodeLinker`. Survives any future env-sanitization vp might apply to spawned subprocesses. - yarn config step: add explicit `runner.os == 'Linux'` gate so a future PR that broadens cross-OS coverage doesn't accidentally run it on Windows where Yarn-Berry-on-bash has historically been flaky. - non-Linux fallback assert: check `$RUNNER_TEMP/sfw-bin/sfw[.exe]` doesn't exist (the exact path the action would have created) rather than `command -v sfw` so a runner image that pre-installs sfw globally doesn't false-fail the assertion. - isSfwSupported comment: restore the in-tree technical breadcrumb (empty-EKU + rustls) so future maintainers have context when setup-vp#73 eventually closes.
Addresses the Copilot review threads on PR #72: - action.yml: input description now documents Linux-only behavior and the macOS/Windows fallback so Marketplace/IDE listings don't imply cross-platform support. - isSfwSupported(): also returns false when getSfwAssetName can't resolve an asset for the current arch (e.g. Linux self-hosted runner on riscv64). Previously installSfw would throw; now we fall back to plain vp install like we do on macOS/Windows. - index.ts: fallback warning only fires when run-install is actually enabled — workflows that set sfw:true but install separately no longer see noise. - test-sfw-blocks-malicious: grep stdout/stderr for an sfw-specific block marker in addition to checking exit code, so npm 404 / network / vp crash can't silently masquerade as "sfw blocked it". Explicitly NOT pinning the sfw version (per maintainer guidance): keeping releases/latest so users automatically pick up upstream malware-detection updates. Added a comment explaining the tradeoff.
Drop the [latest, alpha] axis from test-sfw — sfw is decoupled from vp's release channel, and the OS × package-manager matrix is already 6 cells. Other test jobs (test, test-node-version, test-cache-*) still cover the alpha channel for vp itself. Cuts test-sfw from 12 jobs to 6.
Consistent with the test-sfw slim-down: sfw's musl asset selection and block behavior are both decoupled from vp's release channel, so testing both vp versions adds no signal — only doubles CI cost. Other test jobs still cover the alpha channel for vp itself.
A one-value matrix is dead weight — let vp version fall to the action's default (`latest` per action.yml) and remove the matrix axis from test-sfw, test-sfw-alpine, and test-sfw-blocks-malicious. test-sfw-alpine and test-sfw-blocks-malicious had `version` as their ONLY matrix axis, so they drop the `strategy.matrix` block entirely and become plain single-cell jobs.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit b6a100f. Configure here.
Headline fix: lodahs block-marker grep was functionally broken — sfw prints `Protected by Socket Firewall` and `=== Socket Firewall ===` on EVERY invocation, not just real blocks. The previous regex `(socket firewall|blocked|malware|sfw)` matched those banner lines, so any non-zero exit from `sfw vp install lodahs` (npm 404, network blip, canary delisting, vp crash) would silently pass as "sfw blocked it". Confirmed against the actual CI log of test-sfw-blocks-malicious: the banner is printed even on a clean install of `is-odd`. The unique block-line is: " - blocked npm package: name: lodahs; version: ...; reason: ..." Switched the assertion to a literal-string `grep -qF` on that line. Other findings addressed: - src/index.ts diagnostic now includes process.arch and isMuslLinux() so the message doesn't claim "only supported on Linux" on a Linux runner with an unsupported arch (riscv64, ppc64, ia32). - Added an info() log for `sfw: true` + (unsupported || run-install: false) so the no-op is visible — previously the gating change silenced the only signal in that combo. - isSfwSupported() defensive `!!asset` check: if getSfwAssetName is ever refactored from throw → return undefined, the predicate stays correct instead of silently flipping to always-true. - Stale comment about "non-Linux platforms" updated — isSfwSupported now also returns false on Linux+unsupported-arch. - TODO comments near test-sfw-alpine and test-sfw-blocks-malicious so a future re-matrixing restores `strategy.fail-fast: false`.
Addresses serhalp's review (#72 thread r3306983615) about the meta-supply-chain risk of pulling sfw from releases/latest. Three changes ship together: 1. Pin sfw to v1.10.0 via SFW_VERSION constant. Renovate's custom regex manager (added to .github/renovate.json) watches that constant and opens a PR whenever SocketDev publishes a new sfw-free release, so the bump is mechanical. 2. Auto-detect an existing sfw on PATH (via `which` / `where`) before downloading. When the user composes `socketdev/action@<sha>` ahead of this action, that step puts sfw on PATH; setup-vp now detects it, logs "Using existing sfw on PATH: …", and skips the bundled download entirely. Lets users SHA-pin sfw via Renovate against the upstream action repo for stricter supply-chain hygiene. 3. Centralize sfw decision logic in setupSfw(inputs). Covers all four branches (run-install disabled, sfw already on PATH, supported platform, unsupported platform) with one log line per branch. Docs: new "Advanced: stricter supply chain via socketdev/action" section in README with the composition example. CI: new test-sfw-with-socketdev-action job exercises the composition path end-to-end (socketdev/action@<sha> → setup-vp with sfw:true → verify install).
…ssert Addresses the two remaining Copilot review threads on PR #72. setupSfw(): Order was: PATH-detect → platform gate. So a macOS/Windows workflow that composes `socketdev/action@<sha>` (which works fine on those platforms and puts sfw on PATH) would skip the platform-fallback warning and run `sfw vp install`, hitting the rustls TLS handshake bug (#73). New order: platform gate FIRST, then on Linux prefer PATH, then on unsupported-arch Linux fall back with a more specific message. Pulled the unsupported-arch suggestion out of the macOS/Windows warning since composing socketdev/action wouldn't help there. test-sfw-with-socketdev-action: The job comment claimed an assertion that wasn't actually in the workflow. Added a negative check that `$RUNNER_TEMP/sfw-bin/sfw[.exe]` was NOT created — proves setup-vp took the PATH-detection branch instead of downloading. If the branch ever regresses (e.g. someone reorders setupSfw), this catches it.
Saves ~130 MB and ~5-15s per run, and gives us a fallback when the GitHub releases CDN flakes (we've hit that class of incident before for the vp install scripts in #67). Implementation: - restoreCache at the start of installSfw with key sfw-${SFW_VERSION}-${platform}-${arch}-${libc} No restoreKeys: we never accept a different version's binary as a fallback. The version is in the key so a Renovate bump to SFW_VERSION naturally evicts stale entries (GHA also auto-evicts after 7 days). - On cache hit: chmod+addPath and return — skip the download entirely. - On cache miss: existing download/retry path runs, then saveCache publishes to GHA cache for future runs. - All cache calls wrapped in try/catch so any cache-service failure (network blip, 5xx, ReserveCacheError on duplicate key) falls through cleanly to the download path; cache is never load-bearing. - The composition path (socketdev/action@<sha>) is untouched — findSfwOnPath() still wins and we don't double-cache what @actions/tool-cache (which socketdev/action uses) already manages. CI: existing test-sfw matrix exercises both the cache-miss (first push on a branch) and cache-hit (subsequent pushes) paths; no new job needed.
| it("returns true on Linux, false elsewhere (matches current platform)", () => { | ||
| expect(isSfwSupported()).toBe(process.platform === "linux"); |
| export async function setupSfw(inputs: Inputs): Promise<boolean> { | ||
| if (!inputs.sfw) return false; | ||
|
|
||
| if (inputs.runInstall.length === 0) { | ||
| info("sfw was requested but `run-install` is disabled; sfw will not be invoked."); |
Summary
sfwinput. Whentrueandrun-installis enabled, the action downloads the matchingsfwbinary from the upstream releases (auto-detected per OS/arch, with musl support on Alpine) and runssfw vp install …so the underlying package manager's network fetches are inspected before install.false— no behavior change for existing consumers.sfwonly wrapsvp install. Othervpinvocations (vp env use,vp --version) stay unwrapped.Why
Lets open-source projects opt into Socket Firewall Free protection for CI installs with a single input — no need to compose a separate
socketdev/action@v1step. See https://docs.socket.dev/docs/socket-firewall-free.Implementation notes
src/install-sfw.ts— downloadssfw-free-<asset>fromreleases/latest/download/,chmod +xon POSIX, thenaddPath. Mirrors the retry pattern ininstall-viteplus.ts.process.report.getReport().header.glibcVersionRuntimewithfs.existsSync('/etc/alpine-release')as a fallback. Selectssfw-free-musl-linux-{arm64,x86_64}on Alpine.src/run-install.ts— execssfwwith["vp", "install", …]instead ofvpwith["install", …]wheninputs.sfwis true.Test plan
vp run test— 133 passed (13 new ininstall-sfw.test.tscovering all 8 platform/arch/libc combos + error cases; 1 new ininputs.test.ts).vp run typecheckclean.vp run check:fixclean.vp run buildregenerateddist/index.mjs.test-sfwjob (Ubuntu/macOS/Windows × latest/alpha) — installsis-oddundersfw vp installand verifiessfw --versionresolves on PATH.test-sfw-alpinejob (alpine:3.23container) — proves the musl asset is selected (a glibc binary would fail to exec inside Alpine).sfw: falsedefault (no behavior change).Note
Medium Risk
Changes the install execution path and downloads a third-party binary when enabled; default
falsepreserves existing behavior, but misconfiguration or upstream sfw/TLS issues could break CI installs on Linux.Overview
Adds an optional
sfwinput (defaultfalse) so CI can runsfw vp installinstead of plainvp installwhenrun-installis enabled, using Socket Firewall Free to inspect dependency fetches. On Linux, the action downloads the matchingsfwrelease asset (glibc or musl on Alpine), puts it on PATH, and only wraps install—not othervpcommands. On macOS/Windows, it warns and falls back to unwrapped install without downloadingsfw(documented Linux-only limitation).README and
action.ymldocument the input and behavior. CI addstest-sfw(Linux × pnpm/npm/yarn/bun; non-Linux fallback checks),test-sfw-alpine(musl), andtest-sfw-blocks-malicious(typosquatlodahsmust fail with an sfw marker in output).Reviewed by Cursor Bugbot for commit b6a100f. Configure here.