diff --git a/.claude/hooks/setup-security-tools/README.md b/.claude/hooks/setup-security-tools/README.md index a88c222..fe3dc7c 100644 --- a/.claude/hooks/setup-security-tools/README.md +++ b/.claude/hooks/setup-security-tools/README.md @@ -46,7 +46,7 @@ registry between your last `pnpm install` and now. **How it's installed**: as a native binary, downloaded from GitHub, SHA-256 verified, cached at `~/.socket/_dlx/`. The script also writes -small wrapper scripts ("shims") at `~/.socket/sfw/shims/` — one per +small wrapper scripts ("shims") at `~/.socket/_wheelhouse/shims/` — one per package manager — that transparently route commands through the firewall. You make sure that directory is at the front of your PATH; nothing else changes about how you use the tools. @@ -80,7 +80,7 @@ then download whatever isn't already cached. | AgentShield | `~/.socket/_dlx//agentshield` | Yes | | zizmor | `~/.socket/_dlx//zizmor` | Yes | | SFW binary | `~/.socket/_dlx//sfw` | Yes | -| SFW shims | `~/.socket/sfw/shims/npm`, etc. | Yes | +| SFW shims | `~/.socket/_wheelhouse/shims/npm`, etc. | Yes | `` in `_dlx//` is a content-addressed directory keyed off the pinned version + sha256, so multiple versions can coexist @@ -137,9 +137,9 @@ version you have via Homebrew. The pin lives in `external-tools.json`. for package managers found on your `PATH` at install time. Install npm/pnpm/whatever first, then re-run setup. -**SFW shims not intercepting** — Make sure `~/.socket/sfw/shims` is +**SFW shims not intercepting** — Make sure `~/.socket/_wheelhouse/shims` is at the _front_ of your `PATH`. Run `which npm` — it should point at -the shim under `~/.socket/sfw/shims/`, not the real binary. +the shim under `~/.socket/_wheelhouse/shims/`, not the real binary. ## Cross-fleet sync diff --git a/.claude/hooks/setup-security-tools/index.mts b/.claude/hooks/setup-security-tools/index.mts index c1102c1..24ccb5d 100644 --- a/.claude/hooks/setup-security-tools/index.mts +++ b/.claude/hooks/setup-security-tools/index.mts @@ -8,7 +8,7 @@ // // What it checks: // -// 1. SFW shim integrity. Walks `~/.socket/sfw/shims/*` and reports +// 1. SFW shim integrity. Walks `~/.socket/_wheelhouse/shims/*` and reports // shims whose dlx-cached binary target no longer exists on disk. // Cache eviction (manifest rebuild, manual cleanup) leaves // shims pointing at vanished hashes — every `pnpm` / `npm` / @@ -43,6 +43,8 @@ import { existsSync, promises as fs } from 'node:fs' import path from 'node:path' import process from 'node:process' +import { getSocketAppDir } from '@socketsecurity/lib-stable/paths/socket' + interface Finding { readonly kind: | 'broken-shim' @@ -82,7 +84,12 @@ const TOKEN_401_RE = * the repair conditions weren't met / the script failed. */ function repairShims(home: string): Finding[] { - const sfwDir = path.join(home, '.socket', 'sfw') + // Use the lib-stable helper for cross-platform consistency and to + // honor the canonical "_wheelhouse" umbrella. The home arg is + // accepted for backwards-compat with the existing call site but + // ignored in favor of the lib-stable resolution. + void home + const sfwDir = getSocketAppDir('wheelhouse') const shimsDir = path.join(sfwDir, 'shims') const sfwBin = path.join(sfwDir, 'bin', 'sfw') const regen = path.join(sfwDir, 'regenerate-shims.sh') @@ -130,11 +137,7 @@ function repairShims(home: string): Finding[] { } async function checkShims(): Promise { - const home = process.env['HOME'] - if (!home) { - return [] - } - const shimsDir = path.join(home, '.socket', 'sfw', 'shims') + const shimsDir = path.join(getSocketAppDir('wheelhouse'), 'shims') if (!existsSync(shimsDir)) { return [] } @@ -180,11 +183,7 @@ async function checkShims(): Promise { } function checkEdition(): Finding[] { - const home = process.env['HOME'] - if (!home) { - return [] - } - const shimPath = path.join(home, '.socket', 'sfw', 'shims', 'pnpm') + const shimPath = path.join(getSocketAppDir('wheelhouse'), 'shims', 'pnpm') if (!existsSync(shimPath)) { return [] } diff --git a/.claude/hooks/stale-process-sweeper/index.mts b/.claude/hooks/stale-process-sweeper/index.mts index 1f4b054..def60af 100644 --- a/.claude/hooks/stale-process-sweeper/index.mts +++ b/.claude/hooks/stale-process-sweeper/index.mts @@ -12,7 +12,7 @@ // - tsgo / tsc type-check daemons // - type-coverage workers // - esbuild service processes -// - Socket Firewall wrappers (`~/.socket/sfw/bin/sfw`) — each pnpm / +// - Socket Firewall wrappers (`~/.socket/_wheelhouse/bin/sfw`) — each pnpm / // yarn invocation goes through one, and the wrapper sometimes // outlives its pnpm child. On a busy day this can pile up to // hundreds of orphans holding ~200MB RSS each (20+GB total). @@ -74,7 +74,7 @@ const STALE_PATTERNS: Array<{ name: string; rx: RegExp }> = [ rx: /esbuild\/(bin|lib)\/.*\bservice\b/, }, // Socket Firewall command wrappers. Three deployment layouts: - // - ~/.socket/sfw/bin/sfw[-] (current dev install) + // - ~/.socket/_wheelhouse/bin/sfw[-] (current dev install) // - ~/.socket/_dlx//sfw (planned: dlxBinary cache) // - ${RUNNER_TEMP}/sfw-bin/sfw[.exe] (CI runner install) // Path component is invariant across home prefixes (/Users// vs diff --git a/.claude/settings.json b/.claude/settings.json index 55c8aee..7392a19 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -202,6 +202,17 @@ ] } ], + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/socket-token-minifier-start/index.mts", + "timeout": 5 + } + ] + } + ], "PostToolUse": [ { "matcher": "mcp__.*", diff --git a/.config/sfw-bypass-list.txt b/.config/sfw-bypass-list.txt index aaacdb8..fd37521 100644 --- a/.config/sfw-bypass-list.txt +++ b/.config/sfw-bypass-list.txt @@ -8,7 +8,7 @@ # - socket-registry → .github/actions/setup/action.yml grep-reads # this list into SFW_CUSTOM_REGISTRIES in CI. # - socket-btm et al → scripts/install-sfw.mts writes the env to -# ~/.socket/sfw/env.sh so local sfw gets the +# ~/.socket/_wheelhouse/env.sh so local sfw gets the # same set the CI shared worker uses. # # Edit ONLY in socket-wheelhouse/template/.config/sfw-bypass-list.txt diff --git a/scripts/install-sfw.mts b/scripts/install-sfw.mts index bf88ef0..0755c8b 100644 --- a/scripts/install-sfw.mts +++ b/scripts/install-sfw.mts @@ -5,18 +5,25 @@ * @socketsecurity/lib-stable's downloadBinary helper. Matches the CI install * path: same version source, same binary integrity check (SHA-256 inline), * same on-disk layout (~/.socket/_dlx//sfw). The dev-only piece is a - * stable shim symlink at ~/.socket/sfw/bin/sfw → _dlx-hashed path so existing - * shims in ~/.socket/sfw/shims/ continue to resolve. Reads version + - * per-platform sha256 from the repo's root `external-tools.json` under - * `tools.sfw-free` / `tools.sfw-enterprise`. That file is the single fleet - * source of truth — every consumer of external tooling reads the same - * entries. Usage: pnpm run install:sfw # free flavor pnpm run install:sfw -- - * --enterprise # requires SOCKET_API_KEY (or SOCKET_API_TOKEN) pnpm run - * install:sfw -- --force # ignore cache, redownload pnpm run install:sfw -- - * --quiet. + * stable shim symlink at ~/.socket/_wheelhouse/bin/sfw → _dlx-hashed path so + * existing shims in ~/.socket/_wheelhouse/shims/ continue to resolve. + * + * Detects + migrates a pre-existing ~/.socket/sfw/ install in place on first + * run (rename to ~/.socket/_wheelhouse/). The `_` prefix matches the npm / + * lib-stable convention for "managed internal cache" (compare to _dlx, + * _cacache, etc.) — `sfw/` was the lone non-prefixed sibling, now + * regularized. + * + * Reads version + per-platform sha256 from the repo's root + * `external-tools.json` under `tools.sfw-free` / `tools.sfw-enterprise`. + * That file is the single fleet source of truth — every consumer of + * external tooling reads the same entries. Usage: pnpm run install:sfw # + * free flavor pnpm run install:sfw -- --enterprise # requires + * SOCKET_API_KEY (or SOCKET_API_TOKEN) pnpm run install:sfw -- --force # + * ignore cache, redownload pnpm run install:sfw -- --quiet. */ -import { existsSync, promises as fsPromises, readFileSync } from 'node:fs' +import { existsSync, promises as fsPromises, readFileSync, renameSync } from 'node:fs' import path from 'node:path' import process from 'node:process' import { fileURLToPath } from 'node:url' @@ -25,8 +32,9 @@ import { parseArgs } from 'node:util' import { WIN32, getArch } from '@socketsecurity/lib-stable/constants/platform' import { downloadBinary } from '@socketsecurity/lib-stable/dlx/binary' import { errorMessage } from '@socketsecurity/lib-stable/errors' -import { safeDelete } from '@socketsecurity/lib-stable/fs' +import { safeDelete, safeMkdirSync } from '@socketsecurity/lib-stable/fs' import { getDefaultLogger } from '@socketsecurity/lib-stable/logger' +import { getSocketAppDir, getUserHomeDir } from '@socketsecurity/lib-stable/paths/socket' const logger = getDefaultLogger() @@ -37,28 +45,26 @@ const __dirname = path.dirname(__filename) const REPO_ROOT = path.join(__dirname, '..') const EXTERNAL_TOOLS_PATH = path.join(REPO_ROOT, 'external-tools.json') -// HOME on POSIX, USERPROFILE on Windows. Both can be set to an empty -// string in degenerate shells (`HOME= some-cmd`) or to a non-absolute -// stub like `~`, which would resolve `path.join(HOME, ...)` to a path -// rooted at the current working directory — silently installing sfw -// somewhere unexpected. Insist on an absolute path before accepting -// either value. -const resolveHome = (): string | undefined => { - for (const candidate of [process.env['HOME'], process.env['USERPROFILE']]) { - if (candidate && path.isAbsolute(candidate)) { - return candidate - } - } - return undefined +// Resolve the user-home wheelhouse umbrella via the canonical lib-stable +// helper (getSocketAppDir('wheelhouse') → ~/.socket/_wheelhouse/). Cross- +// platform via getUserHomeDir() which handles HOME / USERPROFILE / fallback. +const WHEELHOUSE_DIR = getSocketAppDir('wheelhouse') +const WHEELHOUSE_BIN_DIR = path.join(WHEELHOUSE_DIR, 'bin') +// One-time migration: if a pre-rename ~/.socket/sfw/ install exists AND the +// new ~/.socket/_wheelhouse/ doesn't, rename the directory in place. Keeps +// existing shims valid (each will be regenerated on next setup pass to point +// at the new path). Idempotent: skips when either condition fails. Older +// fleet machines won't break across the rename. +const LEGACY_SFW_DIR = path.join(getUserHomeDir(), '.socket', 'sfw') +if (existsSync(LEGACY_SFW_DIR) && !existsSync(WHEELHOUSE_DIR)) { + logger.log(`Migrating legacy ${LEGACY_SFW_DIR} → ${WHEELHOUSE_DIR}…`) + renameSync(LEGACY_SFW_DIR, WHEELHOUSE_DIR) } -const HOME = resolveHome() -if (!HOME) { - logger.fail( - 'HOME / USERPROFILE not set to an absolute path — cannot resolve install dir', - ) - process.exit(1) -} -const SFW_BIN_DIR = path.join(HOME, '.socket', 'sfw', 'bin') +// Ensure the expected subdir layout exists. safeMkdirSync is recursive + +// EEXIST-safe by default. +safeMkdirSync(WHEELHOUSE_BIN_DIR) + +const SFW_BIN_DIR = WHEELHOUSE_BIN_DIR interface ToolEntry { version: string @@ -176,9 +182,9 @@ async function main(): Promise { logger.log(` ${downloaded ? 'downloaded' : 'cached'}: ${binaryPath}`) } - // Stable shim entry point: ~/.socket/sfw/bin/sfw → _dlx-hashed path. - // The shims in ~/.socket/sfw/shims/ exec this symlink so the _dlx - // hash is invisible to PATH-prepending consumers. Refresh on every + // Stable shim entry point: ~/.socket/_wheelhouse/bin/sfw → _dlx-hashed path. + // The shims in ~/.socket/_wheelhouse/shims/ exec this symlink so the + // _dlx hash is invisible to PATH-prepending consumers. Refresh on every // install so a version bump updates the link target. await fsPromises.mkdir(SFW_BIN_DIR, { recursive: true }) const linkPath = path.join(SFW_BIN_DIR, binaryName)