Skip to content

fix(vex): honest machine diagnostics, stale-doc removal, out-of-sync disclosure; offline proxy-notice suppression - #208

Merged
Mikola Lysenko (mikolalysenko) merged 2 commits into
mainfrom
fix/vex-diagnostics-offline-notice
Aug 19, 2026
Merged

fix(vex): honest machine diagnostics, stale-doc removal, out-of-sync disclosure; offline proxy-notice suppression#208
Mikola Lysenko (mikolalysenko) merged 2 commits into
mainfrom
fix/vex-diagnostics-offline-notice

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Problems (found by the 2026-08-18 pnpm e2e campaign's vex/offline probes; all ecosystem-independent)

  1. Machine-invisible Property-7 drops: a byte-verified applied patch omitted from the VEX doc only because its ecosystem has no install hook (and no setup.manual) made vex --json exit 1 with events: [] and the factually wrong error "No applied patches with vulnerability metadata to attest." — indistinguishable from "not patched". The explanatory note was human-mode-only.
  2. Stale attestations: a failed vex run left the previous run's fully-valid document byte-untouched at --output — a pipeline reusing one path could ship yesterday's attestation for a now-unpatched tree.
  3. --output write failures reported a bare OS error with no path; --product accepted arbitrary non-IRI strings verbatim into the OpenVEX product @id; a healthy vendored attestation said nothing when the live installed tree was out of sync with the patched bytes.
  4. apply/vendor/rollback --offline runs printed the "using the public patch API proxy" notice — network-implying output under strict airgap — and the SOCKET_OFFLINE parse was duplicated between the client and telemetry.

Fixes

  • Setup-filter drops now emit per-purl skipped events with errorCode ecosystem_not_setup; when all omissions are filter drops, the error message names the filter and both remediations.
  • Failed runs remove a recognizably-OpenVEX file at the output path (JSON @context naming openvex.dev) — unrelated files are never touched; the contract is pinned by tests both ways.
  • write_failed names the path; a non-purl/non-IRI product override warns (product_not_iri, honored verbatim); vendored attestations gain a vendored_tree_out_of_sync warning when the present installed tree hash-mismatches (attestation basis unchanged — the committed artifact is the product; the warning says to reinstall).
  • The offline gate suppresses the proxy notice; is_offline_env() is hoisted to utils/env_compat and shared by the client (both gates) and telemetry.

The core attestation invariant is untouched and was verified healthy throughout the probes: verified: true only ever appears when on-disk bytes hash to afterHash; every tamper flipped the attestation. The stable (vendored)/(redirected) impact-marker strings are unchanged (contract-pinned).

Verification

New tests: stale-doc removal + non-OpenVEX preservation, write-failure path naming, setup-filter skipped events + specific message, product_not_iri warn + purl control, out-of-sync warn with absent-tree and pristine controls (core), offline proxy-notice suppression. Suites: cli_parse_vex 7, e2e_embedded_vex 12, e2e_vex_vendor 13, cli_apply_silent 5, core vex 241 — all green; verified against main in an isolated worktree before opening.

🤖 Generated with Claude Code


Note

Medium Risk
Changes VEX JSON contracts, failure cleanup at output paths, and offline messaging—high impact for CI pipelines consuming envelopes, but core attestation rules (hash-to-afterHash, vendored precedence) are preserved and heavily tested.

Overview
VEX --json and envelopes now expose ecosystem-setup (Property 7) drops as per-purl skipped events with errorCode ecosystem_not_setup, with clearer top-level errors when every omission is that filter—not the generic “nothing to attest.” Run-level warnings[] covers non-IRI --product overrides (product_not_iri, still emitted verbatim) and vendored patches whose live node_modules tree hash-mismatches the committed artifact (vendored_tree_out_of_sync; attestation still based on .socket/vendor).

Failed runs delete a recognizably OpenVEX file at --output/--vex so CI cannot ship a prior not_affected doc; unrelated paths are untouched. write_failed names the path and operation.

Core verification adds VerifyOutcome.vendored_out_of_sync when a healthy vendored attestation sees a present installed tree with wrong bytes.

Offline / airgap: tokenless “public patch API proxy” stderr is suppressed when SOCKET_OFFLINE is set; is_offline_env() is shared across API client and telemetry instead of duplicated parses.

Reviewed by Cursor Bugbot for commit dfdc94b. Configure here.

…disclosure; suppress the proxy notice under --offline

Findings from the 2026-08-18 pnpm e2e campaign's vex probe (real-pnpm
projects; the core attestation invariant — verified:true only on matching
bytes — was healthy throughout and is untouched):

- Property-7 setup-filter drops were invisible in --json: a byte-verified
  applied patch omitted only because its ecosystem has no install hook
  (and no setup.manual) produced events:[] and the factually wrong
  "No applied patches with vulnerability metadata to attest." Drops now
  emit per-purl skipped events (errorCode ecosystem_not_setup) and the
  all-drops error message names the filter and both remediations.
- A failed vex run left a stale prior attestation byte-untouched at
  --output. Failed runs now remove a recognizably-OpenVEX file at the
  path (JSON @context naming openvex.dev — unrelated files never touched).
- write_failed errors now name the path; a non-IRI --product override
  warns (product_not_iri) instead of silently landing in the OpenVEX
  product @id; the vendored attestation gains a vendored_tree_out_of_sync
  warning when the PRESENT installed tree hash-mismatches the patched
  bytes (attestation basis — the committed artifact — unchanged).
- apply/vendor/rollback --offline runs no longer print the
  "using the public patch API proxy" notice (network-implying under
  strict airgap); the SOCKET_OFFLINE parse is hoisted to a shared
  is_offline_env() used by the client and telemetry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issues.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit dfdc94b. Configure here.

Comment thread crates/socket-patch-cli/src/commands/vex.rs
Comment thread crates/socket-patch-cli/tests/cli_apply_silent.rs Outdated
Mikola Lysenko (mikolalysenko) added a commit that referenced this pull request Aug 19, 2026
…ouch trustLockfile, takeover reconciliation, revert guards (#213)

* feat(pnpm): full pnpm 7-12 vendor+hosted support — legacy lock grammars, zero-touch trustLockfile, takeover reconciliation, revert guards

Built and verified against real corepack-pinned pnpm 7.33.5 / 8.15.9 /
9.15.9 / 10.34.5 / 11.22.0 / 12.0.0-rc.7 (plus legacy 1-6 probes) in the
2026-08-18 e2e campaign.

HOSTED, pnpm 7/8: the v5.4/v6.0 refusal is replaced by a real rewrite —
every instance key of a dep is spliced (v5 /name/ver and _peer-suffixed,
v6 (peer)-parenthesized; each owns its resolution), one ledger edit per
instance; a post-splice residual detector refuses the dep set-wide if any
instance shape the splice regex cannot claim remains (no partial
rewrites). Frozen installs from empty stores land patched bytes on both
majors; tamper fails ERR_PNPM_TARBALL_INTEGRITY.

HOSTED, pnpm 11/12 zero-touch: rewriting a v9 root lock now auto-writes
trustLockfile: true into pnpm-workspace.yaml (create with scaffold or
byte-preserving append; ledger-recorded as redirect_pnpm_workspace_trust;
--no-trust-lockfile-config opt-out; only ErrorKind::NotFound creates —
an unreadable existing file falls back to guidance, never overwrite;
re-scans heal a missing config on already-redirected locks). pnpm 11.22
and 12-rc frozen installs succeed with no flags and no CI changes; 9/10
ignore the key (verified); the sha512 pin still fails closed under trust.
Warnings name the actual spliced host (userinfo stripped) and both
per-major error codes, and pre-empt pnpm 12's own rebuild-the-lock advice
that silently unpatches.

VENDOR, pnpm 7/8: new pnpm-legacy backend (flavor-stamped so older
binaries fail closed) — package.json pnpm.overrides + legacy lock surgery
emitting exactly what those majors serialize (byte-stable under pnpm's
own re-lock). pnpm <= 8 absolutizes file: specifiers, so frozen installs
are path-bound: surfaced as vendor_pnpm_legacy_absolute_specifier, with
plain `pnpm install --offline` as the moved-checkout path (marker bytes
verified). Windows-shaped canonical paths are normalized (verbatim prefix
stripped, forward slashes).

CONVERSIONS + SAFETY: vendoring over a hosted-redirected npm-family purl
now reconciles the redirect ledger (artifact-uuid-anchored matching —
version-exact, v5 underscore keys claimed; degraded ledgers keep edits
fail-closed), firing vendor_supersedes_redirect exactly once; vendor
--revert byte-restores the hosted lock from the wiring originals. All six
npm-family vendor backends refuse to delete an artifact the live lock
still references when a repair-reconstructed entry has no wiring
(vendor_wiring_unknown_revert_blocked — the revert-brick fix); repair
stamps detected flavors and preserves corrupt artifacts when no rebuild
source exists. Legacy-era diagnostics: shrinkwrap.yaml projects get
pnpm-flavored no-lockfile guidance and join the lockfile-only supplement;
vendored lock entries get redirect_pnpm_entry_vendored instead of
entry-not-found.

Tests: e2e_redirect_pnpm_build.rs (new hosted capstone: pnpm 7-11 real
corepack legs incl. the zero-touch pnpm 11 proof, tamper negative,
hermetic v5/v6 legs), e2e_vendor_pnpm_build.rs ladder (@9/@10/@11 + real
pnpm 7/8 lifecycle legs), takeover/reconciliation/guard/heal unit + e2e
suites — all RED-verified where behavior changed. CLI_CONTRACT.md and
docs/ecosystems.md updated.

Stacked on #203 (lock-inventory legacy grammars); trivially overlapping
test hunks with #204/#208 carry identical content.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(pnpm-legacy): moved-checkout recovery needs --no-frozen-lockfile (pnpm defaults frozen on under CI)

CI caught what local runs could not: pnpm turns --frozen-lockfile ON when
CI=true, and the pnpm <= 8 moved-checkout recovery works precisely by
re-resolving the path-bound absolute specifier — frozen semantics skip
that re-resolution (pnpm 8: ERR_PNPM_OUTDATED_LOCKFILE; pnpm 7:
stale-path install). The lifecycle legs' recovery step now passes
--no-frozen-lockfile explicitly, and the vendor_pnpm_legacy_absolute_
specifier remedy (warning text, module doc, CLI_CONTRACT.md,
docs/ecosystems.md) recommends `pnpm install --offline
--no-frozen-lockfile` so real CI users get working advice.

Verified: the full capstone (10 legs incl. real pnpm 7/8 lifecycles)
passes under CI=true locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(pnpm-legacy): lock oracle uses the real path normalizer (Windows byte-exactness)

Windows CI proved the production normalizer right and the test oracle
wrong: the hermetic splice legs built their expected absolute specifier
with raw canonicalize().display() — the \\?\C:\ verbatim form the
normalizer exists to strip. normalize_canonical_root is now pub and the
oracle consumes it at both assertion sites, so the expected string is
built by the same transformation the backend writes and cannot drift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(pnpm-legacy): in-file oracles also use the shared root normalizer

Windows CI surfaced the same oracle-drift bug in the module's own unit
tests: the fixture helper handed raw canonicalize().display() (verbatim
\\?\C:\ form) to the ROOT_TOKEN substitution, the no-leak contains probe,
and the moved-checkout fixture builder. All three now go through a
canon_root_str() helper built on normalize_canonical_root, so every
oracle spells the root exactly as the splice writes it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… expect-messages in new tests

Review feedback, two fixes:

1. note_warning silences stderr under --json and stores advisories in
   VexWriteSummary.warnings, but only the standalone vex envelope copied
   them out — embedded apply/scan/vendor --json --vex runs swallowed
   product_not_iri and vendored_tree_out_of_sync entirely on the machine
   channel this change targets. VexSummary gains an additive warnings
   field (skip_serializing_if empty, same RunWarning shape as the
   standalone envelope's warnings[]), populated by the apply host here;
   the scan/vendor hosts ship with the feature branch that owns those
   files. Pinned by apply_json_vex_warnings_ride_in_envelope
   (RED-verified: failed with the population removed) plus a
   clean-product control pinning the key absent.

2. Newly-added tests' bare .unwrap() fixture steps converted to
   .expect("step description") in cli_apply_silent.rs, e2e_vex_vendor.rs,
   and e2e_embedded_vex.rs (new tests only; long-standing tests
   untouched).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikolalysenko
Mikola Lysenko (mikolalysenko) force-pushed the fix/vex-diagnostics-offline-notice branch from cccb8ec to a787a5e Compare August 19, 2026 15:07
@mikolalysenko
Mikola Lysenko (mikolalysenko) merged commit cea7195 into main Aug 19, 2026
42 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the fix/vex-diagnostics-offline-notice branch August 19, 2026 15:14
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.

2 participants