From ee6075505728022d09562e4d6cb321b85987e1db Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 08:14:31 +0000 Subject: [PATCH 1/2] Use 0755 instead of 0700 for the packages directory 0700 breaks installations that point `WP_CLI_PACKAGES_DIR` at a location shared between users: whichever user first runs `wp package install` owns the directory, and every other user then silently loses their packages, because `IncludePackageAutoloader` only checks `is_readable()` and otherwise falls through to a debug message. The risk being guarded against is another local user *writing* into the packages directory, whose `vendor/autoload.php` is required on every WP-CLI run. Read access was never part of that, so 0755 closes the hole while leaving shared-read setups working. A umask can only clear further bits, so the result is never group- or world-writable. The unit test asserted a literal `0700`, which is umask-dependent. Assert that the group and other write bits are unset instead, which is the invariant that actually matters. Follow-up to #243. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014qPfx7yCg9xCTys3XXXRkU --- src/Package_Command.php | 6 +++++- tests/phpunit/ComposerJsonTest.php | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Package_Command.php b/src/Package_Command.php index 453bf2bd..e6bf5cfe 100644 --- a/src/Package_Command.php +++ b/src/Package_Command.php @@ -1283,7 +1283,11 @@ private function create_default_composer_json( $composer_path ) { $composer_dir = pathinfo( $composer_path, PATHINFO_DIRNAME ); if ( ! is_dir( $composer_dir ) ) { - if ( ! @mkdir( $composer_dir, 0700, true ) ) { // @codingStandardsIgnoreLine + // 0755 rather than 0700: the risk being guarded against is another local user + // *writing* into the packages directory, whose `vendor/autoload.php` is required on + // every WP-CLI run. Read access was never the problem, and revoking it breaks setups + // that point `WP_CLI_PACKAGES_DIR` at a location shared between users. + if ( ! @mkdir( $composer_dir, 0755, true ) ) { // @codingStandardsIgnoreLine $error = error_get_last(); WP_CLI::error( sprintf( "Composer directory '%s' for packages couldn't be created: %s", $composer_dir, $error['message'] ) ); } diff --git a/tests/phpunit/ComposerJsonTest.php b/tests/phpunit/ComposerJsonTest.php index 2f48e91e..17e78a1f 100644 --- a/tests/phpunit/ComposerJsonTest.php +++ b/tests/phpunit/ComposerJsonTest.php @@ -88,7 +88,15 @@ public function test_create_default_composer_json() { $this->assertSame( $this->mac_safe_path( realpath( $expected ) ?: $expected ), $this->mac_safe_path( realpath( $actual ) ?: $actual ) ); $this->assertTrue( false !== strpos( file_get_contents( $actual ), 'wp-cli/wp-cli' ) ); if ( ! Utils\is_windows() ) { - $this->assertSame( '0700', substr( sprintf( '%o', fileperms( dirname( $actual ) ) ), -4 ) ); + // The invariant that matters is that no other local user can write into the packages + // directory, whose `vendor/autoload.php` is required on every WP-CLI run. The exact + // mode depends on the umask, so assert the write bits rather than the literal mode. + $perms = fileperms( dirname( $actual ) ) & 0777; + $this->assertSame( + 0, + $perms & 0022, + sprintf( 'Packages directory must not be group- or world-writable, got %o.', $perms ) + ); } unlink( $actual ); rmdir( dirname( $actual ) ); From 82e004a4200dc0b52d7283fae4cf04226305c57a Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 4 Aug 2026 10:18:02 +0200 Subject: [PATCH 2/2] Remove comments --- src/Package_Command.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Package_Command.php b/src/Package_Command.php index e6bf5cfe..81621306 100644 --- a/src/Package_Command.php +++ b/src/Package_Command.php @@ -1283,10 +1283,6 @@ private function create_default_composer_json( $composer_path ) { $composer_dir = pathinfo( $composer_path, PATHINFO_DIRNAME ); if ( ! is_dir( $composer_dir ) ) { - // 0755 rather than 0700: the risk being guarded against is another local user - // *writing* into the packages directory, whose `vendor/autoload.php` is required on - // every WP-CLI run. Read access was never the problem, and revoking it breaks setups - // that point `WP_CLI_PACKAGES_DIR` at a location shared between users. if ( ! @mkdir( $composer_dir, 0755, true ) ) { // @codingStandardsIgnoreLine $error = error_get_last(); WP_CLI::error( sprintf( "Composer directory '%s' for packages couldn't be created: %s", $composer_dir, $error['message'] ) );