|
| 1 | +# path-guard |
| 2 | + |
| 3 | +Claude Code `PreToolUse` hook that refuses `Edit`/`Write` tool calls that would *construct* a multi-segment build/output path inline in a `.mts` or `.cts` file. Mandatory across the Socket fleet — every repo ships this file byte-for-byte via `scripts/sync-scaffolding.mjs`. |
| 4 | + |
| 5 | +**Mantra: 1 path, 1 reference.** |
| 6 | + |
| 7 | +Construct a path *once* in the canonical `paths.mts` (or a build-infra helper); reference the computed value everywhere else. |
| 8 | + |
| 9 | +## What it blocks |
| 10 | + |
| 11 | +| Rule | Example | Fix | |
| 12 | +|------|---------|-----| |
| 13 | +| **A** — Multi-stage path constructed inline | `path.join(PKG, 'build', mode, 'out', 'Final', name)` | Construct in the package's `scripts/paths.mts` (or use `getFinalBinaryPath` from `build-infra/lib/paths`); import the computed value here | |
| 14 | +| **B** — Cross-package path traversal | `path.join(PKG, '..', 'lief-builder', 'build', ...)` | Add `lief-builder: workspace:*` as a dep; import its `paths.mts` via the workspace `exports` field | |
| 15 | + |
| 16 | +The hook fires on `Edit` and `Write` tool calls when the target path ends in `.mts` or `.cts`. Other extensions (`.ts`, `.mjs`, `.js`, `.yml`, `.json`, `.md`) pass through — TS path code lives in `.mts` per CLAUDE.md, and other file types are covered by the `scripts/check-paths.mts` gate at commit time. |
| 17 | + |
| 18 | +## What it allows |
| 19 | + |
| 20 | +- Edits to a `paths.mts` (canonical constructor — every package's source of truth). |
| 21 | +- Edits to `scripts/check-paths.mts` (the gate, which legitimately enumerates patterns). |
| 22 | +- Edits to this hook's own files (the test suite has to enumerate the same patterns). |
| 23 | +- Edits to `scripts/check-consistency.mts` (existing path-scanning gate). |
| 24 | +- `path.join` calls with a single stage segment (e.g. `path.join(packageRoot, 'build', 'temp')`) — that's a one-off helper path, not a multi-stage build output. |
| 25 | +- `path.join` calls with no stage segments at all (most general-purpose joins). |
| 26 | +- Any string concatenation that doesn't go through `path.join` — the hook is regex-based and intentionally narrow; the gate runs a deeper scan at commit time. |
| 27 | + |
| 28 | +## Stage segments the hook recognizes |
| 29 | + |
| 30 | +These come from `build-infra/lib/constants.mts` `BUILD_STAGES` plus the lowercase directory-name siblings used by some builders: |
| 31 | + |
| 32 | +`Final`, `Release`, `Stripped`, `Compressed`, `Optimized`, `Synced`, `wasm`, `downloaded` |
| 33 | + |
| 34 | +Two or more in the same `path.join` call (or one stage + one of `'build'`/`'out'` + one mode `'dev'`/`'prod'`) triggers Rule A. |
| 35 | + |
| 36 | +## Known sibling packages (for Rule B) |
| 37 | + |
| 38 | +The hook recognizes Rule B traversals only when the next segment after `..` is a known fleet package name: |
| 39 | + |
| 40 | +`binflate`, `binject`, `binpress`, `bin-infra`, `build-infra`, `codet5-models-builder`, `curl-builder`, `iocraft-builder`, `ink-builder`, `libpq-builder`, `lief-builder`, `minilm-builder`, `models`, `napi-go`, `node-smol-builder`, `onnxruntime-builder`, `opentui-builder`, `stubs-builder`, `ultraviolet-builder`, `yoga-layout-builder` |
| 41 | + |
| 42 | +When a new package joins the workspace, add it here. |
| 43 | + |
| 44 | +## Control flow |
| 45 | + |
| 46 | +The hook reads the tool-use payload from stdin, type-checks `tool_name === 'Edit'` or `'Write'`, filters to `.mts`/`.cts` files, and runs `check(source)`. Any rule violation `throw`s a typed `BlockError`; a single top-level `try/catch` in `main()` writes the block message to stderr and sets `process.exitCode = 2`. |
| 47 | + |
| 48 | +Hook bugs fail **open** — a crash in the hook writes a log line and returns exit 0 so legitimate work isn't blocked on a bad deploy. The companion `scripts/check-paths.mts` gate runs a thorough whole-repo scan at `pnpm check` time, catching anything the hook misses. |
| 49 | + |
| 50 | +## Testing |
| 51 | + |
| 52 | +```bash |
| 53 | +pnpm --filter @socketsecurity/hook-path-guard test |
| 54 | +``` |
| 55 | + |
| 56 | +Adding a new detection pattern: update `STAGE_SEGMENTS` (or `KNOWN_SIBLING_PACKAGES`) in `index.mts`, add a positive and negative test in `test/path-guard.test.mts`. |
| 57 | + |
| 58 | +## Updating across the fleet |
| 59 | + |
| 60 | +This file is in `IDENTICAL_FILES` in `scripts/sync-scaffolding.mjs` (in `socket-repo-template`). After editing, run from `socket-repo-template`: |
| 61 | + |
| 62 | +```bash |
| 63 | +node scripts/sync-scaffolding.mjs --all --fix |
| 64 | +``` |
| 65 | + |
| 66 | +to propagate the change to every fleet repo. |
0 commit comments