Repository navigation
Fix Composer vendor-dir config cascade (#439, #686) - #1408
Conversation
Assisted-by: Claude Code:claude-opus-5-5
scan -g and agent scans missed Composer installs whose vendor-dir was
set anywhere but the project's composer.json: the global project's
composer.json, the user-level $COMPOSER_HOME/config.json (what
`composer config -g vendor-dir` writes), or COMPOSER_VENDOR_DIR for
`composer global`. They reported "No packages found" and exit 0 while
the installed copies stayed unpatched.
One resolver now mirrors Composer's Config::get('vendor-dir'): env,
project composer.json, user config.json, then vendor, with
Platform::expandPath's ~/, $VAR and %VAR% expansion. The global branch,
local discovery and the reinstall-hint label all use it. Values from
the user's own environment and Composer home are honoured as given;
the scanned project's composer.json keeps the existing relative-subpath
gate, and a ~/ or $VAR value there is now refused instead of resolving
to a literal <project>/~/... directory.
Fixes #439. Refs #686.
Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
Address review findings on the vendor-dir resolver: - A set COMPOSER_HOME is used exactly as Composer uses it, so a missing directory no longer falls back to ~/.composer's config.json, and a relative value resolves against the project. - The user-level config.json is read only when neither the env nor the project's composer.json decided the vendor dir. - Global and local discovery share one platform-default probe. - ~/ and $VAR expansion keep non-UTF-8 bytes. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit fed9863. Configure here.
|
Ready for review (burn-down agent)
Generated by Claude Code |
Final review briefWhat it does: One resolver, Risk: low to medium. It only changes which directory discovery reads from, and the write-boundary rule on untrusted project composer.json is kept. Values from the user's own env and Composer home are trusted and honoured as given, including absolute paths, the same way Look here:
Verified: read the full diff (3 files). Locally: Changes I made: none. Open questions (none of them block this PR):
Auto-merge is armed, so approving sends it straight to the merge queue. Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #439
Refs #686
Summary
scan -gand agent-mode scans missed Composer installs whosevendor-dirwas set anywhere except the project's own composer.json.They reported "No packages found" and exited 0 while the installed copies
stayed unpatched.
Root cause
The Composer crawler implemented only part of Composer's
Config::get('vendor-dir')cascade:-g) hardcoded$COMPOSER_HOME/vendor. It ignored theglobal project's
config.vendor-dir, the user-level$COMPOSER_HOME/config.json, andCOMPOSER_VENDOR_DIR, which Composerresolves against the Composer home.
$COMPOSER_HOME/config.json, thefile
composer config -g vendor-dir libwrites.~/,$VARor%VAR%the way Composer'sPlatform::expandPathdoes.Fix
One resolver,
resolve_vendor_dir, now mirrors Composer's order:COMPOSER_VENDOR_DIR, then the project's composer.json, then theuser-level config.json, then
vendor. It appliesexpandPathand isused by the global branch, local discovery and the reinstall-hint label.
honoured as given, including absolute and
~/values. They have thesame trust level as the
COMPOSER_VENDOR_DIRvalue that was alreadyhonoured as given.
write gate. A
~/or$VARvalue there is now refused instead ofresolving to a literal
<project>/~/...directory, and the hint prints<vendor-dir>instead of a wrong path.composer.It uses
COMPOSER_HOMEexactly as given, otherwise the sameplatform-default probe the global branch falls back to. The user config
is read only when neither the env var nor the project's composer.json
already decides the vendor dir.
Why #686 is
Refs, notFixes#686 is about an absolute or
~/config.vendor-dirin the scannedproject's composer.json. The crawler deliberately refuses that value,
and
absolute_config_vendor_dir_is_refused/config_vendor_dir_escaping_project_is_refusedlock that in. The valuenames the directory apply later writes into, and composer.json is
tamperable. The npm crawler cites the same rule. Fully fixing #686 needs
a maintainer decision between two options:
rule, and
resolve_project_rootwould then boundinstall-pathatthe external vendor dir's parent.
vexwould refuse to attestfrom the lock pin, and agent apply would exit non-zero, when the
project's vendor-dir was refused. This needs plumbing through
vex_sources/vex_consumed.This PR covers the part of #686 that doesn't need that decision:
~/,$HOMEand$VARexpansion for values from the user's env andComposer home. #686 stays open and is labelled
agent:needs-human.Review findings not fixed (/code-review high)
parent. This already happens for an absolute
COMPOSER_VENDOR_DIR.It needs a tampered
installed.jsoninside the user's own vendor tree.The trust level is unchanged.
$COMPOSER_HOME/config.json.This is the same kind of ambient input as
COMPOSER_VENDOR_DIR, whichthese tests already don't isolate. It only matters on a machine where
someone ran
composer config -g vendor-dir.~/expansion withHOMEunset doesn't fall back to passwd. This israre. Expansion returns
None, so the scan finds nothing; it neverresolves a wrong path.
Tests
Before the fix, 5 of the 6 new
crawler_composer_e2etests failed. Theprecedence control passed both before and after. All 6 pass with the fix.
global_vendor_dir_from_global_composer_jsonglobal_vendor_dir_from_user_config_jsonglobal_vendor_dir_env_resolves_against_composer_homelocal_vendor_dir_from_user_config_jsonproject_vendor_dir_outranks_user_config_jsonuser_level_vendor_dir_expands_home_and_env_placeholders, unittest_expand_composer_path,test_resolve_vendor_dir_cascade,test_local_composer_home_honours_set_env, hint label~/depscaseRun locally:
cargo fmt --all -- --check: OKcargo clippy --workspace --all-features -- -D warnings: OKcargo test -p socket-patch-core --all-features: 6322 passed, 4 failed. All 4 failures are read-only-permission tests (copy_tree,vlt_heal,pypi_poetry,pypi_requirements) that can't fail as root in this sandbox (uid 0), and none touch Composer.cargo test -p socket-patch-cli --all-features --libpluse2e_composer,e2e_composer_vendor_dir_hints,e2e_composer_version_identity,e2e_vendor_composer_crlf: OKSOCKET_PATCH_COMPOSER_E2E_REQUIRED=1 … --test e2e_vendor_composer_build --test e2e_redirect_composer_build -- --ignoredagainst a real Composer 2: OKcargo test --workspacedidn't fit in this sandbox's disk allowance, so CI covers the remaining suites.No changes were needed in the npm, PyPI or gem wrappers.
🤖 Generated with Claude Code
https://claude.ai/code/session_015wAxXbPypqoqPcJYcfXYT3
Generated by Claude Code