Repository navigation
Hosted Composer rewrite keeps the entry's transport-options, so Composer sends a private repository's auth headers to the hosted patch URL #399
Description
Activity
- addedbugSomething isn't workingSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agentpm:composerComposerComposer
on Sep 30, 2026 mikolalysenko commented
on Oct 1, 2026 CollaboratorAuthorMore actions[agent] Triage: priority:p2 (Composer). Confirmed on main f6b7fb9: the hosted Composer rewrite never touches
transport-options(the only writer of that key is the vendored path incrates/socket-patch-core/src/vendor/composer_lock.rs, which supersedes it with{"symlink": false}). Not a duplicate, and not covered by open PR #358 (that PR is about nestedextra.distlookup). Treating as a correctness/security bug (credential sent to a third-party origin).
Generated by Claude Code
mikolalysenko commented
on Oct 1, 2026 CollaboratorAuthorMore actions[agent] Re-triage on main
2463257(v5 consolidation, #277): still reproduces. The v5 hosted rewrite (no ledger) still leaves the entry'stransport-optionsin place after it pointsdist.urlat the hosted origin.Lock entry before:
dist.url=https://repo.example.com/dist/acme-tool-1.0.0.zip,"transport-options": {"http": {"header": ["X-Private-Token: s3cret"]}}. Aftersocket-patch scan --mode hosted --ecosystems composer(mock patch API,grantedwith sha1):{"name": "acme/tool", "version": "1.0.0", "dist": {"type": "zip", "url": "https://patch.socket.dev/patch/composer/acme/tool/1.0.0/tok/<uuid>/acme-tool-1.0.0.zip", "reference": "deadbeef", "shasum": "0123…"}, "type": "library", "transport-options": {"http": {"header": ["X-Private-Token: s3cret"]}}}So Composer will still send the private repository's header to
patch.socket.dev. Real-Composer behaviour for that lock shape is as shown in the original report.
Generated by Claude Code
mikolalysenko commented
on Oct 1, 2026 CollaboratorAuthorMore actions[agent] Composer bug-hunt routine (ledger #321): still reproduces on main
61cfb9b, after #358 changed the Composer rewriter. I also found a second impact of the same kepttransport-options. It needs no private repository: on Composer 1, the next install of a hosted-redirected path-repository entry fails outright.Path-repo variant. For a relative
"type": "path"repository, Composer writes"transport-options": {"relative": true}(plus"symlink": …if the repo sets it) on the lock entry. The hosted rewrite turns the entry intodist.type: zipwith the hosted url and shasum, but keeps thosetransport-options. Composer 1 passes a package'stransport-optionsintostream_context_create()for the dist download. These keys aren't stream-wrapper options, so the download dies:# Composer 1.10.28 project: repositories:[{"type":"path","url":"../pkgs/tool"}], require acme/tool 1.0.0 socket-patch scan # bare scan = hosted; redirected: 1, no warning jq '.packages[0] | {dist, "transport-options"}' composer.lock # dist: {type: zip, url: http://<patch-host>/patch/composer/acme/tool/1.0.0/<tok>/<uuid>/tool-1.0.0.zip, reference: …, shasum: …} # transport-options: {relative: true} <- kept rm -rf vendor && php composer-1.10.28.phar install # - Installing acme/tool (1.0.0): Downloading # PHP Fatal error: Uncaught ValueError: Options should have the form ["wrappername"]["optionname"] = $value # in …/composer.phar/src/Composer/Util/StreamContextFactory.php:153 (PHP 8.x, exit 255) # [ErrorException] stream_context_create(): options should have the form … (PHP 7.x, exit 1)Removing only
transport-optionsfrom the rewritten entry makes the same install succeed with the patched bytes.Composer PHP OS Install from the hosted lock (kept) Same lock, transport-optionsstripped1.10.28 7.2 / 7.4 ubuntu fails (ErrorException, exit 1) patched 1.10.28 8.0 / 8.1 / 8.4 ubuntu fails (ValueError, exit 255) patched 1.10.28 8.1 macOS, Windows fails (ValueError, exit 255) patched 1.10.28 8.3 local Linux, real socket-patch scanon61cfb9b(×3) and on v4.0.0fails patched 2.2.30 7.2 / 8.3 ubuntu / local installs patched patched 2.8.12 / 2.10.3 8.3 local installs patched patched - Probe run (it reconstructs the hosted lock shape, with a stripped control): https://github.com/SocketDev/socket-patch/actions/runs/36903408294
- It isn't a regression: v4.0.0 writes the same entry.
Header leak (the original report). Re-checked on
61cfb9bwith atype: composerrepository carryingoptions.http.header. The rewritten entry still hastransport-options: {"http":{"header":["X-Private-Token: s3cret"]}}.Dropping (or refusing)
transport-optionsin the hosted rewrite, as proposed above, fixes both.relativeandsymlinkonly mean anything for apathdist.
Generated by Claude Code
- addedv5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.Must resolve before v5: public interface/migration or ordinary patch-install-undo failure.compatibilityPublic CLI/JSON, saved state, upgrades, or package-manager compatibility.Public CLI/JSON, saved state, upgrades, or package-manager compatibility.and removed
on Oct 8, 2026 mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actionsv5 release blocker (P1). An ordinary hosted Composer rewrite forwards existing private-repository transport credentials to the patch download. Fix the source transition before release; PR #1026 is pending.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
[agent] Found by the scheduled Composer bug-hunt routine (ledger #321).
Summary
Composer copies a repository's
options(for example"options": {"http": {"header": ["X-Private-Token: …"]}}, a common way to authenticate a private Composer repository or Satis) into every package it resolves from that repository, as the lock entry'stransport-options.scan --mode hostedsets the entry'sdist.urlto the hosted patch archive and removessourceanddist.mirrors, but it keepstransport-options. Composer applies a package'stransport-optionsto that package's dist download. So the nextcomposer installsends the private repository's credential header to the hosted patch host.Impact
ssl.local_cert,ssl.verify_peer: false, orhttp.proxy. Those were meant for the private repository only and now apply to the hosted URL.rewrite_lock_entryreplacestransport-optionswith{"symlink": false}.Repro
Everything runs locally: a two-file Composer repository on
127.0.0.1:8765, plus a small mock of the patch API on127.0.0.1:8766(it servespatches/batch,patches/package→ a grantedartifacts[].integrity.sha1,by-package,viewand the zip). This is the same shapee2e_redirect_composer_buildmocks.The install succeeds and the patched bytes land, so there's no visible error. The header is only visible on the hosted server's side.
Expected vs actual
docs/testing/composer-compatibility.md("What each mode writes") says the hosted rewrite retargets the dist to the hosted archive and removes the entry'ssourceanddist.mirrors, because they belong to the original origin. The entry'stransport-optionsalso belong only to the original repository, so the hosted rewrite should drop them too. It could warn, likeredirect_composer_dist_mirrors_removeddoes, and the fragment revert would restore them. The alternative is to refuse. Credentials for the private registry must never be sent to the patch host.transport-optionsstays on the entry verbatim, and Composer sends the header to the hosted URL.redirected: 1with no warning.Matrix (Linux, main
f6b7fb9= 4.0.0)transport-optionsfrom repooptionscomposer installThis is OS-independent: the lock text and Composer's own download options drive it. I didn't bisect: every release that carries the current hosted composer rewriter behaves this way on 4.0.0.
Suspect code
crates/socket-patch-core/src/patch/redirect/composer_source.rs:292(apply_dist_edit). It dropssource(plan_source_drop, line 241) anddist.mirrors, but never looks at the entry'stransport-options.crates/socket-patch-core/src/patch/redirect/mod.rs:4953(rewrite_composer_lock).Backlog review — 2026-10-08
Priority: P2 → P1. Forwarding private repository authorization headers to the hosted patch URL is credential disclosure. Open PR #1026 should receive security-focused review.