Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
112ee5f
feat: add sfw input to wrap vp install with Socket Firewall Free
fengmk2 May 26, 2026
fafe07a
test(ci): add test-sfw-blocks-malicious job using lodahs canary
fengmk2 May 26, 2026
c4df421
ci: limit test-sfw to ubuntu-latest, document sfw rustls TLS limitation
fengmk2 May 26, 2026
f5f2b16
feat(sfw): fall back to plain vp install on non-Linux with a warning
fengmk2 May 26, 2026
b3e3a58
chore: point sfw non-Linux warning at setup-vp tracker issue
fengmk2 May 26, 2026
4d71ec2
docs: point README sfw fallback note at setup-vp#73 tracker
fengmk2 May 26, 2026
b0bece4
docs(ci): collapse sfw-free issue references to setup-vp#73 tracker
fengmk2 May 26, 2026
f0618a4
docs: point isSfwSupported comment at setup-vp#73 tracker
fengmk2 May 26, 2026
d2ed525
test(ci): add test-sfw-package-managers covering pnpm/npm/yarn/bun
fengmk2 May 26, 2026
f13d0ba
test(ci): fix yarn job + promote bun to required
fengmk2 May 26, 2026
9fbb519
test(ci): force Yarn nodeLinker=node-modules so verify step is uniform
fengmk2 May 26, 2026
e6024b3
test(ci): merge test-sfw and test-sfw-package-managers into one matrix
fengmk2 May 26, 2026
aa92f44
ci+docs: address code-review findings on the matrix-merge diff
fengmk2 May 26, 2026
381ef2b
ci+src: handle PR #72 review comments
fengmk2 May 26, 2026
2e0144f
ci: only test latest vp release in test-sfw matrix
fengmk2 May 26, 2026
65e179d
ci: also drop alpha from test-sfw-alpine and test-sfw-blocks-malicious
fengmk2 May 26, 2026
b6a100f
ci: drop single-value version axis from sfw jobs entirely
fengmk2 May 26, 2026
c69ca6f
ci+src: fix code-review findings on commits since aa92f44
fengmk2 May 26, 2026
91936e2
feat(sfw): auto-detect pre-installed sfw + pin version + Renovate rule
fengmk2 May 27, 2026
18c1cc4
fix(sfw): hard-gate macOS/Windows before PATH detect + add negative a…
fengmk2 May 27, 2026
fbb345b
feat(sfw): cache the sfw binary via @actions/cache
fengmk2 May 27, 2026
77a3bfa
ci: add one-off verify-vp-1686-sfw workflow_dispatch
fengmk2 May 28, 2026
3f209a2
ci: switch verify-vp-1686-sfw to push-with-paths-filter trigger
fengmk2 May 28, 2026
80dd299
ci(verify): drop socketdev/action, install sfw manually from same URL…
fengmk2 May 28, 2026
b323a21
ci(verify): also exercise musl via alpine:3.23 container
fengmk2 May 28, 2026
43f7df0
ci(verify): bump pkg-pr-new target to vp PR #1703 (v0.1.23 release)
fengmk2 May 28, 2026
d34bd47
feat(sfw): bump SFW_VERSION to v1.11.0
fengmk2 May 28, 2026
2161235
test(sfw): add branch coverage for setupSfw, installSfw, isSfwSupported
fengmk2 May 28, 2026
d9921a6
feat(sfw): support macOS and Windows (vite-plus v0.1.23+)
fengmk2 May 29, 2026
a75aa97
refactor(sfw): drop cacheHit dead state; prove blocking on macOS/Windows
fengmk2 May 29, 2026
113b54c
chore(deps): vite-plus ^0.1.23; assert sfw blocks on composition path
fengmk2 May 29, 2026
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
11 changes: 10 additions & 1 deletion .github/renovate.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>Boshen/renovate"],
"ignoreDeps": ["@void-sdk/void"]
"ignoreDeps": ["@void-sdk/void"],
"customManagers": [
{
"customType": "regex",
"fileMatch": ["^src/install-sfw\\.ts$"],
"matchStrings": ["const SFW_VERSION = \"(?<currentValue>v[^\"]+)\";"],
"depNameTemplate": "SocketDev/sfw-free",
"datasourceTemplate": "github-releases"
}
]
}
252 changes: 252 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,258 @@ jobs:
- name: Verify vp exec works
run: vp exec node -e "console.log('vp exec works in Alpine')"

test-sfw:
# sfw wraps vp install end-to-end on all OSes (macOS / Windows supported
# since vite-plus v0.1.23). On Linux we verify across every package
# manager vp auto-detects via lockfile (pnpm/npm/yarn/bun); macOS / Windows
# run pnpm only because the PM choice doesn't change the sfw wrap path —
# those cells exist to prove the cross-platform sfw download + wrap works.
# vp version is left at the action default (`latest`) — sfw is decoupled
# from vp's release channel. Other test jobs (test-cache-*,
# test-node-version, etc.) still cover the alpha channel for vp itself.
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
package-manager: [pnpm, npm, yarn, bun]
exclude:
# Non-Linux runs pnpm only — PM diversity adds no sfw-wrap coverage.
- { os: macos-latest, package-manager: npm }
- { os: macos-latest, package-manager: yarn }
- { os: macos-latest, package-manager: bun }
- { os: windows-latest, package-manager: npm }
- { os: windows-latest, package-manager: yarn }
- { os: windows-latest, package-manager: bun }
runs-on: ${{ matrix.os }}
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2

- name: Create test project for ${{ matrix.package-manager }}
shell: bash
run: |
case "${{ matrix.package-manager }}" in
pnpm) LOCKFILE=pnpm-lock.yaml; CONTENTS='' ;;
npm) LOCKFILE=package-lock.json; CONTENTS='{"name":"test-project","lockfileVersion":3}' ;;
yarn) LOCKFILE=yarn.lock; CONTENTS='' ;;
bun) LOCKFILE=bun.lock; CONTENTS='' ;;
*) echo "Unsupported package-manager: ${{ matrix.package-manager }}" >&2; exit 1 ;;
esac
mkdir -p test-project
cd test-project
echo '{"name":"test-project","private":true,"dependencies":{"is-odd":"^3.0.1"}}' > package.json
printf '%s' "$CONTENTS" > "$LOCKFILE"

- name: Configure Yarn .yarnrc.yml (Linux + yarn only)
if: matrix.package-manager == 'yarn' && runner.os == 'Linux'
# nodeLinker=node-modules: Yarn Berry defaults to Plug'n'Play, which
# makes plain `require()` from a non-yarn-wrapped node process fail.
# enableImmutableInstalls=false: Yarn Berry auto-enables immutable
# installs under CI, which makes the bootstrap from an empty
# yarn.lock fail with YN0028. Setting it here (instead of via the
# YARN_ENABLE_IMMUTABLE_INSTALLS env var) survives any future
# env-sanitization vp might apply to spawned subprocesses.
shell: bash
run: |
{
echo "nodeLinker: node-modules"
echo "enableImmutableInstalls: false"
} > test-project/.yarnrc.yml

- name: Setup Vite+ with sfw + ${{ matrix.package-manager }}
uses: ./
with:
sfw: true
run-install: |
- cwd: test-project
cache: false

- name: Verify sfw is on PATH
run: sfw --version

- name: Verify dependency installed via ${{ matrix.package-manager }}
working-directory: test-project
run: vp exec node -e "console.log(require('is-odd')(3))"

test-sfw-alpine:
# vp version is left at the action default (`latest`) — sfw's musl asset
# selection is decoupled from vp's release channel.
# NOTE: if this job is later re-matrixed (alpha+latest, multiple alpine
# versions, etc.), restore `strategy: { fail-fast: false }` so a flake in
# one shard doesn't cancel the others.
runs-on: ubuntu-latest
container:
image: alpine:3.23
steps:
- name: Install Alpine dependencies
run: apk add --no-cache bash curl gcompat libstdc++

- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2

- name: Create test project with a real dependency
run: |
mkdir -p test-project
cd test-project
echo '{"name":"test-project","private":true,"dependencies":{"is-odd":"^3.0.1"}}' > package.json

- name: Setup Vite+ with sfw (musl)
uses: ./
with:
sfw: true
run-install: |
- cwd: test-project
cache: false

- name: Verify sfw is on PATH (musl)
run: sfw --version

- name: Verify dependency installed under sfw (musl)
working-directory: test-project
run: vp exec node -e "console.log(require('is-odd')(3))"

test-sfw-blocks-malicious:
# Verifies sfw actually intercepts a known-malicious package, not just
# that it wraps the install. Uses `lodahs` (lodash typosquat), the same
# canary SocketDev's own workflows use:
# https://github.com/SocketDev/bun-security-scanner/blob/main/.github/workflows/test.yml
# If this job ever stops blocking, either sfw is misconfigured or the
# canary itself has been delisted — swap it for another Socket-flagged
# package from https://socket.dev/blog/category/threat-research.
# vp version is left at the action default (`latest`) — sfw block behavior
# is decoupled from vp's release channel.
# Runs on all three OSes: a fail-open regression in vp/sfw's proxy or CA
# handling can be platform-specific (the #73 rustls cert-trust class of
# bug was), so each OS needs its own block assertion — benign-install
# success (the test-sfw job) is not enough proof.
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2

- name: Create test project with a benign dependency
shell: bash
run: |
mkdir -p test-project
cd test-project
echo '{"name":"test-project","private":true,"dependencies":{"is-odd":"^3.0.1"}}' > package.json

- name: Setup Vite+ with sfw and install benign dep
uses: ./
with:
sfw: true
run-install: |
- cwd: test-project
cache: false

- name: Assert sfw blocks malicious package (lodahs typosquat of lodash)
shell: bash
working-directory: test-project
# Exit code alone isn't sufficient: a non-zero exit from npm 404,
# network blip, or vp crash would also produce a false positive. We
# also require the literal sfw block-line for lodahs in the combined
# output so an unrelated failure doesn't get reported as "sfw blocked
# it". The block-line format observed in CI is:
# " - blocked npm package: name: lodahs; version: ...; reason: ..."
# The banner "Protected by Socket Firewall" and the "=== Socket
# Firewall ===" header are emitted on EVERY sfw invocation, so neither
# of those is a usable marker — use the unique "blocked npm package:
# name: lodahs" line instead.
run: |
set +e
OUTPUT=$(sfw vp install lodahs 2>&1)
CODE=$?
set -e
printf '%s\n' "$OUTPUT"
if [ "$CODE" -eq 0 ]; then
echo "::error::sfw failed to block lodahs on ${{ matrix.os }} — install exited 0"
exit 1
fi
if ! printf '%s' "$OUTPUT" | grep -qF -- "blocked npm package: name: lodahs"; then
echo "::error::sfw vp install exited $CODE on ${{ matrix.os }} but the lodahs block-line was not in the output — likely failed for a non-sfw reason (canary delisted, network blip, vp crash, or sfw output format changed). Swap the canary if Socket has delisted lodahs, or update the marker grep if sfw's block-line format changed."
exit 1
fi
echo "OK: sfw blocked lodahs on ${{ matrix.os }} (exit $CODE, block-line found)"

test-sfw-with-socketdev-action:
# Exercises the composition path: install sfw via the upstream
# `socketdev/action@<sha>` step first, then call setup-vp with `sfw:
# true`. setup-vp should DETECT the pre-installed sfw on PATH (via
# findSfwOnPath()) and SKIP its bundled download. We assert that by
# checking $RUNNER_TEMP/sfw-bin/sfw[.exe] — the exact path
# installSfw() would have created — was NOT created. We also assert the
# composed sfw actually BLOCKS a malicious package (lodahs), so this path
# is proven to enforce, not just to be wired up.
# See README "Advanced: stricter supply chain via socketdev/action".
runs-on: ubuntu-latest
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2

- name: Create test project with a real dependency
shell: bash
run: |
mkdir -p test-project
cd test-project
echo '{"name":"test-project","private":true,"dependencies":{"is-odd":"^3.0.1"}}' > package.json

- name: Install sfw via socketdev/action
uses: socketdev/action@ba6de6cc0565af1f42295590380973573297e31f
with:
mode: firewall-free

- name: Setup Vite+ with sfw (composition path)
uses: ./
id: setup-vp
with:
sfw: true
run-install: |
- cwd: test-project
cache: false

Comment thread
fengmk2 marked this conversation as resolved.
- name: Verify setup-vp used the composed sfw (no bundled download)
shell: bash
# Negative assertion: if setup-vp had downloaded its own sfw, it
# would land at $RUNNER_TEMP/sfw-bin/sfw[.exe] (per
# getSfwBinDir() in install-sfw.ts). On the composition path the
# PATH-detection branch should fire FIRST and skip the download
# entirely, so neither file should exist.
run: |
if [ -e "$RUNNER_TEMP/sfw-bin/sfw" ] || [ -e "$RUNNER_TEMP/sfw-bin/sfw.exe" ]; then
echo "::error::setup-vp downloaded its own sfw binary even though one was pre-installed via socketdev/action. The PATH-detection branch in setupSfw() regressed."
exit 1
fi
echo "OK: setup-vp used the pre-installed sfw (no bundled download at \$RUNNER_TEMP/sfw-bin/)"

- name: Verify dependency installed under composed sfw
working-directory: test-project
run: vp exec node -e "console.log(require('is-odd')(3))"

- name: Assert composed sfw blocks malicious package (lodahs)
shell: bash
working-directory: test-project
# Proves the composition path actually enforces, not just that sfw is
# present. socketdev/action exports SFW_JSON_REPORT_PATH into the env,
# which makes sfw write its block report to JSON instead of stdout —
# we unset it here so the block-line goes to stdout, matching the
# marker check used by test-sfw-blocks-malicious.
run: |
unset SFW_JSON_REPORT_PATH
set +e
OUTPUT=$(sfw vp install lodahs 2>&1)
CODE=$?
set -e
printf '%s\n' "$OUTPUT"
if [ "$CODE" -eq 0 ]; then
echo "::error::composed sfw failed to block lodahs — install exited 0"
exit 1
fi
if ! printf '%s' "$OUTPUT" | grep -qF -- "blocked npm package: name: lodahs"; then
echo "::error::composed sfw vp install exited $CODE but the lodahs block-line was not in the output (canary delisted, network blip, or sfw output format changed)."
exit 1
fi
echo "OK: composed sfw blocked lodahs (exit $CODE, block-line found)"

Comment thread
fengmk2 marked this conversation as resolved.
build:
runs-on: ubuntu-latest
steps:
Expand Down
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ GitHub Action to set up [Vite+](https://viteplus.dev) (`vp`) with dependency cac
- Optionally set up a specific Node.js version via `vp env use`
- Cache project dependencies with auto-detection of lock files
- Optionally run `vp install` after setup
- Optionally wrap `vp install` with [Socket Firewall Free (`sfw`)](https://docs.socket.dev/docs/socket-firewall-free) to block malicious dependencies
- Support for all major package managers (npm, pnpm, yarn, bun)

## Usage
Expand Down Expand Up @@ -135,6 +136,45 @@ steps:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```

### With Socket Firewall Free (sfw)

Set `sfw: true` to wrap `vp install` with [Socket Firewall Free](https://docs.socket.dev/docs/socket-firewall-free). The action downloads the matching `sfw` binary from the upstream [releases](https://github.com/SocketDev/sfw-free/releases) (auto-detected per OS/arch, with musl support on Alpine) and runs `sfw vp install …` so the underlying npm / pnpm / yarn fetches are inspected before packages are installed. Works on Linux, macOS, and Windows:

```yaml
steps:
- uses: actions/checkout@v6
- uses: voidzero-dev/setup-vp@v1
with:
sfw: true
run-install: true
```

`sfw` is only applied when `run-install` is enabled; other `vp` commands (e.g. `vp env use`, `vp --version`) run unwrapped.

The action pins the `sfw` version it downloads so a re-run of the same commit gets the same binary; [Renovate](https://docs.renovatebot.com/) opens a PR whenever SocketDev publishes a new `sfw-free` release (see [`.github/renovate.json`](.github/renovate.json)).

#### Advanced: stricter supply chain via `socketdev/action`

The bundled download uses a pinned URL but is not itself SHA-pinned. For workflows that want the `sfw` binary itself SHA-pinned (so a compromise of the upstream release artifact cannot land silently on the next run), compose with [`socketdev/action`](https://github.com/SocketDev/action) in an earlier step. setup-vp auto-detects an existing `sfw` on `PATH` and uses it instead of downloading:

```yaml
steps:
- uses: actions/checkout@v6
# SHA-pinned; let Renovate bump it
- uses: socketdev/action@<sha>
with:
mode: firewall-free
- uses: voidzero-dev/setup-vp@v1
with:
sfw: true
run-install: true
```

In the action log you will see `Using existing sfw on PATH: …` when this composition is detected, vs. `Installing sfw from …` for the bundled-download path.

> [!NOTE]
> **macOS / Windows require Vite+ v0.1.23 or newer.** Earlier `vp` releases didn't honor `HTTPS_PROXY` / `SSL_CERT_FILE`, so `sfw vp install` failed the TLS handshake on macOS / Windows (it always worked on Linux). The action's default `version: latest` satisfies this; if you pin an older `vp` and enable `sfw` on macOS / Windows, the install will fail the handshake. On a runner architecture with no published `sfw` binary (e.g. `riscv64`), the action logs a warning and falls back to plain `vp install`.

### Alpine Container

Alpine Linux uses musl libc instead of glibc. Install compatibility packages before using the action:
Expand Down Expand Up @@ -178,6 +218,7 @@ jobs:
| `node-version-file` | Path to file containing Node.js version (`.nvmrc`, `.node-version`, `.tool-versions`, `package.json`) | No | |
| `working-directory` | Project directory used for relative paths, lockfile auto-detection, environment checks, and default install | No | Workspace root |
| `run-install` | Run `vp install` after setup. Accepts boolean or YAML object with `cwd`/`args` | No | `true` |
| `sfw` | Wrap `vp install` with [Socket Firewall Free](https://docs.socket.dev/docs/socket-firewall-free) (`sfw`) | No | `false` |
| `cache` | Enable caching of project dependencies | No | `false` |
| `cache-dependency-path` | Path to lock file for cache key generation | No | Auto-detected |
| `registry-url` | Optional registry to set up for auth. Sets the registry in `.npmrc` and reads auth from `NODE_AUTH_TOKEN` | No | |
Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ inputs:
description: "Run `vp install` after setup. Accepts boolean or YAML object with cwd/args."
required: false
default: "true"
sfw:
description: "Wrap `vp install` with Socket Firewall Free (sfw) to block malicious dependency fetches. Works on Linux, macOS, and Windows (macOS/Windows require Vite+ v0.1.23+). See https://docs.socket.dev/docs/socket-firewall-free."
required: false
default: "false"
node-version:
description: "Node.js version to install via `vp env use`. Defaults to Node.js latest LTS version."
required: false
Expand Down
Loading
Loading