Match Composer patch versions like Composer does - #270
Draft
Mikola Lysenko (mikolalysenko) wants to merge 15 commits into
Draft
Mikola Lysenko (mikolalysenko) wants to merge 15 commits into
Mikola Lysenko (mikolalysenko) wants to merge 15 commits into
Conversation
A composer patch's base purl can carry the padded version Socket's SBOM ingestion stores (pkg:composer/psr/log@3.0.2.0), while the project's installed.json and composer.lock say 3.0.2 or v3.0.2. The CLI compared those as strings after stripping a leading v, so such a patch was "not found" by apply and vendor, refused by the hosted redirect as a version mismatch, rejected by VEX discovery, and deleted by scan --prune. Compare composer versions the way Composer does. The new utils::composer_version ports composer/semver 3.4.4 VersionParser::normalize (padding, v tags, stability spellings such as -rc.1 and RC1, +build, x-dev branches); a spelling Composer rejects only matches itself. purl_eq, the crawler, the vendor lock lookup, the hosted lock rewriter, the redirect ledger, VEX discovery and sources, scan discovery and the prune step now use it for composer purls. Stored spellings (manifest keys, vendored leaf dirs, ledger keys) are unchanged. The shared vector file tests/fixtures/composer-version-vectors.json is generated from real Composer 2.10.3 and is byte-identical to depscan's copy, so the CLI and the server agree on every case; the port also matches Composer on 12,129 fuzzed inputs. New tests cover the crawler, lock lookup, ledger, VEX leaf, prune, a padded-version redirect golden, and apply, vendor (with VEX) and hosted runs against a mock API that serves only the padded spelling. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Composer patching was only proven on Ubuntu against three floating releases, so a regression on macOS, Windows, or a specific 2.x line (2.9 still falls back to the git source; 2.10 does not) went unseen. composer-compatibility.yml runs the real vendored and hosted capstones plus the composer VEX cells against checksum-pinned 1.10.28, 2.0.14, 2.1.14, 2.2.30, 2.5.8, 2.8.12, 2.9.8 and 2.10.3 phars on Ubuntu, and the 1.10/2.2/2.9/2.10 lines on Windows and macOS. A Docker job runs the vendored Docker capstone on exact 2.2.30 and 2.10.3 images. The capstones can now run a given composer.phar through php, which is also what makes them work on Windows. Dockerfile.composer installs an exact, checksum-verified Composer instead of the latest one, and the ci.yml comment on the git-source fallback is corrected. Assisted-by: Claude Code:claude-opus-5-5
Hosted redirects now remove a patched package's source wherever it sits in its composer.lock entry, and strip dist mirrors. Before, a failed or skipped hosted download let Composer 1 through 2.9 quietly install the unpatched upstream code from git. Locks that an older CLI or the GitHub app already redirected are healed on the next run. Vendored copies no longer lose files when installed. Composer's path mirror skipped anything matched by the copy's .gitignore (Composer 1 to 2.1), .hgignore (Composer 1) or .gitattributes export-ignore rules (every version), so a patched file could silently go missing. Those rules are now neutralized in the copy, and re-runs heal copies vendored earlier. A patch that edits one of those files is refused. vendor, scan and get now tell users to run composer install, and to remove the package directory first on Composer 1, which does not reinstall a changed package. Real-Composer tests cover the new cases on 1.10 through 2.10, and docs/testing/composer-compatibility.md records what each version does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vendoring a Composer patch (vendor, scan --mode vendored, get --mode vendored) rewrote a CRLF composer.lock, as a Windows or core.autocrlf checkout has, with LF line endings. The commit diff covered every line instead of the patched entry, and vendor --revert also wrote LF, so it did not restore the original bytes. Both writes now keep the line endings of the lock they replace, as the hosted mode already did. A CRLF lock now round-trips byte for byte through vendor and vendor --revert. Unit tests and a binary e2e cover vendor, scan --vendor and get --mode vendored on a CRLF lock. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Socket's SBOM pads every numeric Composer version to four parts, so a date release locked as 20231001 reaches the CLI as 20231001.0.0.0. Composer rejects that spelling (a 6+ digit major is only valid as a date), so the version key fell back to the raw text and never matched the lock: apply, vendor, hosted redirect and VEX reported the patch as not found for its own package. A 2-4 part numeric spelling Composer rejects is now normalized with its trailing .0 parts dropped. The shared vector file, copied from depscan, gains the padded date cases checked against Composer 2.10.3. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A dist that lists mirrors before its url had the mirrors' url rewritten instead of the dist's own, and a dist whose only url is a mirror is now refused with redirect_composer_no_dist_url. Also drop the dead composer_source_before_dist helper. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vendoring rewrote the whole composer.lock through serde_json, so a lock with \/ escapes, \uXXXX escapes or mixed line endings did not revert byte for byte. Splice only the patched entry's text, keeping the lock's indentation, line endings and escaping. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vendored ownership checks compared composer purls as exact strings, so a ledger keyed pkg:composer/psr/log@3.0.2.0 never covered the lock's @3.0.2 and apply, rollback, gc and scan treated a live vendored patch as foreign or dead. Match composer purls by release identity. The vendor hint now reads composer.lock only when this run wired composer, and never blocks on a special file. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Composer installs from source first when preferred-install resolves to source for the package, or when it is auto with a dev version on a Composer 1 or 2.0 lock. A leftover source entry then means pristine bytes, so VEX no longer reports those entries as fixed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
After a hosted redirect that removed no source entry, Composer 2 keeps an existing vendor/ copy, so the hint now names the vendor directory to remove on every Composer version. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The composer compatibility workflow now also runs for vendor, patch, manifest, lockfile and command changes. Docker composer tests fail instead of skipping when SOCKET_PATCH_DOCKER_E2E_REQUIRED=1, and the source-fallback test disables autocrlf so Windows compares LF bytes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The source-install veto read composer.json directly, which the VEX discovery rules forbid: every extractor reads through DiscoverCtx so unreadable files are diagnosed and Socket identities are recognized. Also add the golden entries for the dist-mirrors-before-url fixture. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The in-place composer.lock rewrite oracle now also generates locks with dist mirrors listed before the url or as the only url, a source member placed before name, and patched versions spelled padded or v-prefixed. Every randomized lock is also checked to revert byte for byte when each recorded fragment is undone, newest first, the way the ledger reverts it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…anges The Composer crawler is now a directory module with its equivalence oracle, and vendored Composer lock writes go through the per-run group commit and the durable writer, so changes to any of them run the exact release matrix too. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
force-pushed
the
fix/composer-patch-annotations
branch
from
September 27, 2026 12:46
791848d to
68c8c82
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
LLM Description written by Claude Code:claude-opus-5-5
The CLI now matches Composer patches by release version the way Composer does. Composer patches stay installed, and VEX reports them fixed, on every Composer version from 1.10 to 2.10 on Linux, macOS and Windows.
The depscan counterpart is SocketDev/depscan#26854.
Version matching
A Composer patch's base purl can carry the padded version Socket's SBOM ingestion stores (
pkg:composer/psr/log@3.0.2.0), whilecomposer.lockandinstalled.jsonsay3.0.2orv3.0.2. The CLI compared the two as strings after strippingv, so it failed in four ways:applyandvendorreported the patch "not found";scan --prunedeleted it.What changed:
utils::composer_versionports composer/semver 3.4.4VersionParser::normalize.purl_eq, vendor lock lookup, hosted rewriter, redirect ledger, VEX discovery and sources, scan discovery, prune and gc, and the vendored ownership checks in apply, rollback and scan.tests/fixtures/composer-version-vectors.jsonwas generated from real Composer 2.10.3 and is byte-identical to depscan's copy.Making sure patches are actually installed
sourcewherever it sits in the entry, not only right beforedist, because Composer 1.x–2.9 silently fall back to the pristine git source;dist.mirrors, including a list placed beforeurl;.gitignoreandexport-ignorerules, which make Composer's path mirror drop files. Vendoring refuses when a patched file would be dropped.composer.lockis edited. CRLF, mixed line endings,\/escapes and indentation survive, andvendor --revertis byte-identical.source. That happens whenpreferred-installresolves to source, or when it'sautowith a dev version on a 1.x/2.0 lock.composer install, with Composer 1 and dist-only reinstall guidance naming thevendor/dir to remove.CI
composer-compatibility.ymlcovers Composer 1.10.28, 2.0.14, 2.1.14, 2.2.30, 2.5.8, 2.8.12, 2.9.8 and 2.10.3.Dockerfile.composerinstalls an exact, checksum-verified Composer.ci.ymlcomment on the git-source fallback is corrected.Rebased onto main after #257
Rebased onto
8c381ecf(#257's concurrent, parallel-crawl, single-pass rewriters). Every #270 behavior sits on #257's paths:composer_versions_equivalentis used at every call site, including Speed up hosted and vendored scans: concurrent API requests, parallel crawl, single-pass rewriters #257's composer crawler oracle (crawlers/composer_crawler/oracle.rs) and the hosted rewriter oracle (patch/redirect/composer_equivalence_tests.rs). Both had encoded the oldv-strip compare, so they now use Match Composer patch versions like Composer does #270's semantics.sourceand mirrors: the rewriter keeps Speed up hosted and vendored scans: concurrent API requests, parallel crawl, single-pass rewriters #257's walk and in-place splice: test the name first, walk bytes,replace_rangeper edit.composer_source::apply_dist_editdoes the splice, and its recorded edit spans the dist plus the removedsourcewherever it sits, so the fragment revert stays byte-exact. Speed up hosted and vendored scans: concurrent API requests, parallel crawl, single-pass rewriters #257's source-before-dist block (composer_source_before_dist,redirect_composer_source_keptfor a non-adjacent source) is gone.redirect_composer_no_dist_url),sourcebeforename, and padded orv-prefixed patch versions.lock_text::replace_entryoutput goes throughatomic_write_bytes_preserving_mode, which is both the per-run group commit and the durable writer. The parse memo is re-seeded only when the written bytes equal the doc's canonical render; otherwise it is invalidated. A unit test covers this.ComposerPreludenow carries the lock text it parsed.composer.jsonthroughctx.read_text.purl_keys_coverand friends are merged with Speed up hosted and vendored scans: concurrent API requests, parallel crawl, single-pass rewriters #257's concurrent scan, discovery and vendor imports.composer-compatibility.ymlalso triggers oncrawlers/composer_crawler/**,utils/group_commit.rsandutils/durability.rs.composer-version-vectors.jsonis byte-identical (cmp) to depscan'sworkspaces/lib/src/composer/patch-identity-vectors.jsonon SocketDev/depscan#26854.🤖 Generated with Claude Code