Skip to content

fix: neutralize host PHPRC contamination (LocalWP cURL-77 bug)#20

Merged
jtsternberg merged 3 commits into
masterfrom
fix/phprc-contamination
May 27, 2026
Merged

fix: neutralize host PHPRC contamination (LocalWP cURL-77 bug)#20
jtsternberg merged 3 commits into
masterfrom
fix/phprc-contamination

Conversation

@jtsternberg

Copy link
Copy Markdown
Owner

Problem

When buddy ran from a shell that had PHPRC set to a LocalWP site's per-site PHP config — the default for any terminal launched from LocalWP — every HTTPS request died with:

cURL error 77: error setting certificate verify locations:
  CAfile: /Users/<user>/Sites/<site>/app/public/wp-includes/certificates/ca-bundle.crt
  CApath: none

LocalWP's php.ini pins openssl.cafile / curl.cainfo to a wp-includes CA bundle scoped to one WP site. From any other working directory that path doesn't exist, OpenSSL/cURL refuses to initialize TLS, and Guzzle aborts before any request leaves the process.

bin/buddy was a PHP shebang script, so the contaminated PHPRC was consumed by PHP at interpreter startup — before any application code ran. A PHP-level fix is impossible (ini_set on these directives is a no-op at runtime; the openssl/curl modules are already initialized by the time we'd see them).

Fix

bin/buddy is now a thin POSIX sh launcher that unset PHPRC and then execs PHP on the real entry script. The original PHP bootstrap moved verbatim to bin/buddy.php.

The wrapper walks the symlink chain manually (POSIX — macOS readlink lacks -f) before computing dirname, so both install paths resolve buddy.php correctly:

  • buddy self:install → symlinks bin/buddy into ~/.local/bin/.
  • composer global require and consumer projects → Composer's BinaryInstaller detects the non-PHP shebang and generates a sh proxy (not the phpvfscomposer:// PHP proxy used for PHP bins), which then execs our sh wrapper.

Verification

Tested under a contaminated LocalWP shell (PHPRC=/Users/JT/Library/Application Support/Local/run/V-dp05VsH/conf/php):

Path Result
naked php -r '...HTTPS...' (sanity) FAILED (contamination confirmed real)
Standalone buddy via ~/.local/bin symlink OK
vendor/bin/buddy (Composer sh proxy → wrapper → buddy.php) OK — HTTPS request reaches the API
php vendor/.../buddy.php direct (bypass wrappers, contrast) cURL error 77 (confirms wrapper neutralizes the contamination)
Real monorepo ./bin/buddy executions:list ... after composer update onto this branch Returned live execution data, no cURL-77

Full phpunit suite still passes (270 tests / 657 assertions).

Compatibility

  • Public CLI surface unchanged.
  • The bin's file type changed from PHP script to sh script. For any Composer consumer this is invisible — Composer regenerates the proxy on composer update. Anyone invoking bin/buddy directly as php bin/buddy … (no longer valid; that was always undocumented) should switch to bin/buddy … or php bin/buddy.php ….
  • Tests that previously invoked php bin/buddy … were updated to php bin/buddy.php ….

Commits

  • 61a51fa — convert bin/buddy to sh wrapper, move PHP bootstrap to bin/buddy.php, update tests + AGENTS.md
  • 214537c — walk symlink chain in the wrapper so install-via-symlink works (caught by a code-review pass)

Bd issue (filed retroactively for audit trail): beads_buddy_cli-s77.

LocalWP-launched shells export PHPRC pointing at a per-site php.ini that
pins openssl.cafile / curl.cainfo to a wp-includes ca-bundle.crt scoped
to one WP install. When buddy ran in that environment, PHP picked up
the host php.ini at interpreter startup and every HTTPS request died
with "cURL error 77: error setting certificate verify locations".

bin/buddy is now a thin sh wrapper that 'unset PHPRC' before execing
PHP on the real entry script (bin/buddy.php). buddy-cli has no reason
to honor a host-app php.ini, and the failure mode (silent TLS death
before any request leaves the process) is worse than ignoring it.

- bin/buddy: new sh launcher
- bin/buddy.php: renamed from old bin/buddy (PHP bootstrap unchanged)
- tests: invoke php bin/buddy.php directly (the wrapper isn't php)
- AGENTS.md: document the two-file layout

Repro:
  PHPRC="/path/to/localwp/conf/php" buddy executions:list ...
  # before: cURL error 77
  # after:  works
- .beads/metadata.json: switch from server mode to embedded mode
- .beads/metadata.json: rename database from "beads_buddy-cli" to "beads_buddy_cli"
Code review caught that `dirname "$0"` returns the symlink's directory,
not the package's bin/. `buddy self:install` symlinks bin/buddy into
~/.local/bin/ and `composer global require` symlinks into
~/.composer/vendor/bin/ — both paths break with the naive wrapper
because there's no buddy.php next to the symlink.

Walk the symlink chain manually (POSIX; macOS readlink lacks -f) to
find the real launcher, then dirname that. Tested with direct, single
symlink, and chained symlink invocations.
Copilot AI review requested due to automatic review settings May 27, 2026 18:12
@jtsternberg jtsternberg merged commit b8c8c70 into master May 27, 2026
1 check failed
@jtsternberg jtsternberg deleted the fix/phprc-contamination branch May 27, 2026 18:14
@jtsternberg jtsternberg removed the request for review from Copilot May 27, 2026 18:33
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