Skip to content

controls: socket-firewall-ci — are committed workflow installs behind Socket Firewall? (47 to 48) - #70

Merged
p4gs merged 1 commit into
mainfrom
feat/socket-firewall-ci
Sep 11, 2026
Merged

p4gs merged 1 commit into
mainfrom
feat/socket-firewall-ci

Conversation

@p4gs

@p4gs p4gs commented Sep 11, 2026 •

Copy link
Copy Markdown
Owner

What

Adds socket-firewall-ci (phase 2, off by default) — control 48. It is additive: socket-firewall is untouched.

socket-firewall asks whether sfw is on the developer's PATH. That is a fact about a machine, and nobody cloning the repository can check it. This control asks the repository-observable question with different evidence: do this repository's COMMITTED workflows put their package-manager installs behind Socket Firewall? Every subject is read through git show HEAD:<path>, so an index-only git add and a working-tree edit are both excluded. No tool runs; no network call is made.

⚠️ Cross-repo ordering

The site repo must register the socket-firewall-ci id before this merges — every directory scan throws otherwise. Held for that; do not merge until the site side lands.

The design

Manager tiers are the false-positive defence. Socket Firewall Free proxies exactly npm, yarn, pnpm, pip (incl. python -m pip), uv, cargo. Go/Maven/Gradle/Bundler/gem/dotnet/NuGet are Enterprise-only; bun/composer/apt/brew/rustup are proxied at no tier. Only the free tier can produce a Fail — failing a maintainer for an ecosystem the free tool cannot protect is a false positive by construction. A repository whose only installs are Enterprise-tier verifies INFO, never PASS and never FAIL.

The first-acquisition rule. The unit is (job, ecosystem), with a local composite action (uses: ./…) spliced into the calling job at the step that uses it. The first acquisition of each pair must carry the sfw prefix:

- run: sfw cargo fetch --locked        # ← the one step that has to change
- run: cargo build --release --locked  # reads the filtered cache; silent

One-step remedy per job, instead of a prefix on every matrix cargo test.

Acquiring subcommands are an allowlist. An unrecognised subcommand is not an acquisition — cargo fmt, npm run, npm test are silent. Detection fails open on purpose: a missed install is a disclosed gap, an invented one sends a maintainer to fix nothing.

Recognition reuses workflows's shell tokeniser, not string matching, so an install inside a heredoc, a comment or a quoted string is text, and sudo / env / time / FOO=1 hide neither the manager (sudo npm ci fails) nor the firewall (sudo sfw npm ci passes).

workflows's operator gates are deliberately NOT imported. sfw npm ci || true still ran the install through the firewall, which is the whole question — importing the ! / set +e / || / | / & gates would be a bug here rather than an omission. Stated in the module docs and covered by a test.

Verdicts. Pass / Fail as above; Degraded + no-inventory when workflows parsed but nothing of any tier was acquired (nothing verified is not a pass); Degraded + scan-error when HEAD cannot be read; Info when an inventory exists with no free-tier acquisition. A workflow_dispatch-only workflow, one GitHub would refuse to run, a constant-false job or step, a non-POSIX run: body, an unresolvable uses: ./…, and the second level of a composite chain are each named not examined rather than counted as clean.

No evidence. machine.rs asserts every row's artifacts == workflows::artifacts_for(id), which is empty for this control, so the proving files are named in messages instead.

Four disclosed misses

Written into docs/phase-2.md and the changelog:

  1. SocketDev/action's shims: true PATH-shimming is unreleased upstream; when it ships, an unprefixed install under it will be protected and this control will read it as unprotected.
  2. Indirect acquisition (npm run build whose script installs, a Makefile target) is not followed.
  3. Composites are spliced depth 1 only.
  4. It proves the workflow says installs go through the firewall — not that it ran, nor that sfw resolved on PATH.

Dogfood: this repo FAILS, and ships the control off

[FAIL    ] socket-firewall-ci — 6 (job, ecosystem) unit(s) acquire packages outside Socket Firewall
  • ci.yml job lint — bare cargo clippy --all-targets -- -D warnings
  • ci.yml job test → setup-sscsb-tools/action.yml — python3 -m pip install --quiet semgrep==…
  • ci.yml job test — cargo build --release --locked
  • ci.yml job coverage → setup-sscsb-tools/action.yml — the same pip install, reached again
  • ci.yml job coverage — cargo llvm-cov --locked …
  • release.yml job build — cargo build --release --locked --target "$TARGET"

Plus one [info] (rustup target add — unsupported at any tier) and one later cargo test --locked correctly not judged.

Deliberately NOT enabled in .sscsb/config.toml here — the gap is visible and fixable as its own change. Default-off also means the directory's in_scope excludes it, so adding this control cannot move any other repository's grade.

Two corrections while in these files

docs/phase-2.md said socket-firewall "needs a Socket account" and the bundled skill filed it under "a paid or unreleased tool". Socket Firewall Free needs neither an account nor an API key. Both controls are now off for the real reason: putting a filtering proxy in front of every install is a decision about how builds fetch, not a binary you drop in.

Tests

26 in the new module, written red-first against a stub that returned Pass — 19 of the 21 cases that existed at that point failed against it (log kept). Coverage of src/socket_firewall.rs: 95.89% lines / 100% functions; crate total (lib) 96.52% lines / 96.24% functions, above the 95/94 gate.

cargo fmt --check clean, cargo clippy --all-targets --locked -- -D warnings clean.

src/workflows.rs changes are visibility only — sixteen items promoted to pub(crate) with no behaviour change, the way Finding::new was promoted for pinning.rs.

Lock-step files touched

src/socket_firewall.rs (new), src/lib.rs, src/controls.rs (entry + dispatch + default-off assertion), src/workflows.rs (visibility), templates/compliance/map.json, tests/tool_orchestration.rs (both lists), .claude/skills/sscsb/SKILL.md + templates/skills/sscsb/SKILL.md (byte-identical: 47→48, 32 on / 16 off, phase-2 row 9→10, taxonomy), README.md, docs/phase-2.md, CHANGELOG.md.

🤖 Generated with Claude Code

…he Socket question (47 to 48)

`socket-firewall` asks whether `sfw` is on the developer's PATH. That is
a fact about a machine, and nobody cloning the repository can check it --
it is one of the class-C controls a public scan has to leave
`unverified`. This additive control asks a different question with
different evidence and leaves that one untouched: do this repository's
COMMITTED workflows put their package-manager installs behind Socket
Firewall? Every subject is read through `git show HEAD:<path>`, so an
index-only `git add` and a working-tree edit are both excluded, and the
verdict is a property of the repository.

Only Socket Firewall Free's ecosystems can fail it -- npm, yarn, pnpm,
pip (including `python -m pip`), uv, cargo. Go, Maven, Gradle, Bundler,
gem, dotnet and NuGet are Enterprise-only; bun, composer, apt, brew and
rustup are proxied at no tier. Both are reported as information, because
failing a maintainer for an ecosystem the free tool cannot protect is a
false positive by construction, and a repository whose only installs are
Enterprise-tier verifies INFO rather than PASS or FAIL.

The unit of judgement is (job, ecosystem), with a local composite action
(`uses: ./...`) spliced into the calling job at the step that uses it,
and the FIRST acquisition of each pair must carry the `sfw` prefix. So
`sfw cargo fetch --locked` followed by a bare `cargo build --locked` is
a PASS -- the crates came through the firewall and the build reads the
filtered cache -- which makes the remedy one step per job instead of a
prefix on every matrix `cargo test`.

Acquiring subcommands are an ALLOWLIST and an unrecognised one is not an
acquisition, so `cargo fmt`, `npm run` and `npm test` are silent and
detection fails open: a missed install is a gap disclosed in the docs,
an invented one sends a maintainer to fix nothing. Recognition reuses
`workflows`'s shell tokeniser rather than string matching, so an install
inside a heredoc, a comment or a quoted string is text, while `sudo`,
`env`, `time` and `FOO=1` hide neither the manager nor the firewall.
`workflows`'s operator gates are deliberately NOT imported: `sfw npm ci
|| true` still ran the install through the firewall, which is the whole
question, so importing them would be a bug rather than an omission.

DEGRADED carries `no-inventory` when workflows parsed and nothing of any
tier was acquired -- nothing verified is not a pass -- and `scan-error`
when HEAD cannot be read. A `workflow_dispatch`-only workflow, one
GitHub would refuse to run, a constant-false job or step, a non-POSIX
`run:` body, a `uses: ./...` that is not a committed composite, and the
second level of a composite chain are each named as NOT EXAMINED rather
than counted as clean. `evidence` is deliberately left empty -- `machine`
asserts every row's `artifacts` equals `workflows::artifacts_for(id)`,
which is empty here -- so the proving files are named in the messages.

Four disclosed misses, in docs/phase-2.md and the changelog:
`SocketDev/action`'s unreleased `shims: true` PATH-shimming will protect
an unprefixed install that this control still reads as unprotected;
indirect acquisition (an `npm run build` whose script installs, a
Makefile target) is not followed; composites are spliced one level deep;
and a PASS proves the workflow SAYS installs go through the firewall,
not that it ran or that `sfw` resolved on PATH.

It ships off by default, which also keeps the directory's `in_scope`
unchanged so no other repository's grade can move. sscsb's own
repository FAILS it today with six findings -- bare `cargo clippy`,
`cargo build --release`, `cargo llvm-cov` and `cargo build --release
--locked --target`, plus `python3 -m pip install` reached twice through
the `setup-sscsb-tools` composite -- and is deliberately not enabled
here, so the gap is visible and fixable as its own change.

Also corrects two now-false statements found while in these files:
docs/phase-2.md said socket-firewall "needs a Socket account" and the
bundled skill filed it under "a paid or unreleased tool". Socket
Firewall Free needs neither an account nor an API key; both controls are
off for the real reason instead -- putting a filtering proxy in front of
every install is a decision about how builds fetch.

Tests: 26 in the new module, written red-first against a stub that
returned Pass (19 of 21 then-existing cases failed). The bare first
acquisition, the prefix licensing the rest of the job, no-inventory,
scan-error, Enterprise and unsupported tiers across fifteen spellings,
heredoc/comment/quote silence, wrapper prefixes both ways, the operator
gates deliberately not applied, unrecognised subcommands, alternate pip
and uv spellings, workflow_dispatch-only, an unrunnable workflow,
constant-false job and step, a non-POSIX shell, HEAD-not-worktree,
composite splicing and its depth-1 boundary, per-job and per-ecosystem
units, the continue-on-error note, message abbreviation, and a source
scan proving this control never reads PATH.

`workflows.rs` changes are visibility only: sixteen items promoted to
`pub(crate)` with no behaviour change, the way `Finding::new` was
promoted for `pinning.rs`.
@p4gs
p4gs force-pushed the feat/socket-firewall-ci branch from 9d06021 to 4c604bd Compare September 11, 2026 16:56
@p4gs
p4gs merged commit 871870f into main Sep 11, 2026
21 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant