Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
981b44e
Lead the CLI help with the v5 workflow
mikolalysenko Sep 27, 2026
d891d3b
Prefer the newest merged patch when choosing one per package
mikolalysenko Sep 27, 2026
b0a2ce6
Make scan default to hosted mode and never prompt
mikolalysenko Sep 27, 2026
c1ee18f
Detect hosted patch updates from the lockfile's own pins
mikolalysenko Sep 27, 2026
da1ccc5
Share the crawler-options and ecosystem-scope helpers across commands
mikolalysenko Sep 27, 2026
62f07c7
Let hosted and vendored scans take project directories
mikolalysenko Sep 27, 2026
6091833
Rank [UPDATE] detection by the same rule scan installs by
mikolalysenko Sep 27, 2026
5e5f5ed
Default get to hosted mode, like scan
mikolalysenko Sep 27, 2026
462a35b
Rewrite the README, CLI contract and docs for v5
mikolalysenko Sep 27, 2026
d6438a3
Clean up stale and narrative comments across the tree
mikolalysenko Sep 27, 2026
962acc4
Fix follow-ups from the stale-doc sweep
mikolalysenko Sep 27, 2026
8ae7dc3
Add the v5 plan: decisions and workstreams
mikolalysenko Sep 27, 2026
686e5fb
v5 WS1+WS2: ledger-free hosted mode, upstream-restore rollback, vendo…
mikolalysenko Sep 28, 2026
06437d2
WS5: one VendoredBackend for vendored apply/revert/repair; cut repair…
mikolalysenko Sep 28, 2026
c7aa5a5
v5 fix: unblock the e2e tier on the v5 base (#288)
mikolalysenko Sep 28, 2026
c02ccf8
Add the NuGet vendoring design (docs only) (#285)
mikolalysenko Sep 28, 2026
388eea3
docs: keep v5 waste review and repacking design (#289)
mikolalysenko Sep 28, 2026
0f2de18
v5 design: staged patch rollout (socket.yml + scan limit) (#290)
mikolalysenko Sep 28, 2026
28cebf7
Attribute vlt pins and uv overrides in ledger-free hosted rollback
mikolalysenko Sep 28, 2026
73c0c4f
WS3: one lockfile model per ecosystem (#281)
mikolalysenko Sep 28, 2026
f6bdad5
v5: remove `setup` (WS7) + patch UI streamlining (WS8) (#279)
mikolalysenko Sep 28, 2026
14a9cb0
v5 WS4/WS6: one hosted engine for disk + memory; unified Ledgers view…
mikolalysenko Sep 28, 2026
f9cb7e1
v5 CI: build e2e binaries once and tier the PM matrix (#291)
mikolalysenko Sep 28, 2026
a7b0d00
v5: fix partial-stage repair bug, cut redundant downloads (#292)
mikolalysenko Sep 29, 2026
1e3ace6
v5: remove dead code and v3 compatibility shims (#296)
mikolalysenko Sep 29, 2026
b97a1c2
v5 tests: one suite per command, retire #257 oracles (#297)
mikolalysenko Sep 29, 2026
b9e106d
v5: socket.yml patch rollout config and filtering (#293)
mikolalysenko Sep 29, 2026
180f10f
v5: cap new patches per scan, most critical first (#294)
mikolalysenko Sep 29, 2026
d381e29
Merge main into release/v5-prerelease
mikolalysenko Sep 30, 2026
909c08c
ci: stabilize v5 compatibility tests and macOS patch host resolution …
mikolalysenko Sep 30, 2026
31c0ae4
chore(v5): simplify distributions to binaries, Cargo, and npm (#298)
mikolalysenko Sep 30, 2026
785fbb0
docs(v5): consolidate guides and remove stale research (#299)
mikolalysenko Sep 30, 2026
ae775fb
feat(v5): implement vendored Maven reactors and Gradle builds (#287)
mikolalysenko Sep 30, 2026
40957fb
Require server artifacts and exact redownload for vendoring (#300)
mikolalysenko Sep 30, 2026
e47663c
Fix composer vendor build after #300 squash
claude Sep 30, 2026
a350536
Count replay requests before replying
claude Sep 30, 2026
bd5caf5
Refuse --vex with more than one scan directory
claude Sep 30, 2026
83b958e
ci: run v5 landings through the PR event, not a release-branch push
claude Sep 30, 2026
086fd31
fix(ci): require TLS 1.2 in the socket host pin handshake
claude Sep 30, 2026
fb47c56
fix(vendor): drop the extracted composer copy when inventory fails
claude Sep 30, 2026
df9fe07
test(yarn-classic): expect the ledger refusal only where yarn installs
claude Sep 30, 2026
bec319b
fix(vendor): reuse and pin committed pypi server sdists
claude Sep 30, 2026
6618bc9
fix(vendor): recognize vendored pypi sdists in the leaf table
claude Sep 30, 2026
1c044c5
fix(vendor): record no inventory past the cap instead of refusing
claude Sep 30, 2026
efbbe31
fix(cargo): refuse a non-crates.io lock source on dry runs too
claude Sep 30, 2026
924951a
fix(vendor): classify vlt package dirs by layout, not a .zip suffix
claude Sep 30, 2026
b389a0f
fix(vendor): rebuild a dir copy that has no inventory on re-vendor
claude Sep 30, 2026
8eb0406
fix(vendor): let the backend rewrite a missing Bun workspace mirror
claude Sep 30, 2026
e9f6154
fix(vendor): let the ledger pick the variant of an uninstalled package
claude Sep 30, 2026
c44db2b
fix(vendor): recover a reused Yarn Berry checksum from our lock entry
claude Sep 30, 2026
fbb1cd2
fix(vendor): name the remedy that works in redownload refusals
claude Sep 30, 2026
20c48ad
fix(vendor): ask the service for a Berry checksum before our lock
claude Sep 30, 2026
2eb7d9d
test: stop printing patch uuids in assertion messages
claude Sep 30, 2026
911b71d
Keep Composer redirects on package downloads
mikolalysenko Sep 30, 2026
8494033
Restore Composer patches from direct members
mikolalysenko Sep 30, 2026
db1343a
Merge main into codex/composer-dist-boundary
Oct 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions crates/socket-patch-core/src/formats/composer/hosted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ use std::sync::LazyLock;

use regex::Regex;

use crate::utils::composer_version::composer_versions_equivalent;
use super::source as composer_source;
use crate::patch::redirect::{
artifact_url_present, full_name, DepOverride, RewriteResult, RewriteWarning,
};
use crate::utils::composer_version::composer_versions_equivalent;

/// Byte offset of the `}` closing the JSON object that CONTAINS `from`, which
/// must be a position inside that object. Brace counting skips string literals,
Expand Down Expand Up @@ -111,6 +111,17 @@ pub(crate) fn find_composer_entry(content: &str, pkg: &str, version: &str) -> Co
}
}

pub(crate) fn find_composer_member(
content: &str,
entry: (usize, usize),
key: &str,
) -> Option<composer_source::Member> {
let object_open = composer_source::entry_object_start(content, entry.0)?;
composer_source::top_level_members(content, object_open, entry.1)
.into_iter()
.find(|member| member.key == key)
}

/// Append `"shasum": "<sha1>"` as the last key of a `"dist": { … }` block,
/// indented like the keys already in it. VCS/zipball dists omit `shasum`
/// entirely; redirecting such a block without inserting the pin left the hosted
Expand Down Expand Up @@ -163,7 +174,6 @@ pub(crate) fn rewrite_composer_lock(
});
return;
}
const DIST_KEY: &str = "\"dist\": {";
let mut content = files["composer.lock"].clone();
let type_re: &Regex = &COMPOSER_DIST_TYPE_RE;
let url_re: &Regex = &COMPOSER_DIST_URL_RE;
Expand Down Expand Up @@ -202,28 +212,17 @@ pub(crate) fn rewrite_composer_lock(
continue;
}
};
// The dist block MUST belong to the located entry. Scanning forward
// from the name for the next `"dist": {` walked into the FOLLOWING
// package whenever the target was installed from source, repointing a
// bystander's url + shasum — a checksum-clean install of the wrong
// code. A target with no dist of its own pins nothing: fail closed.
let Some(dist_start) = content[entry_start..=entry_end]
.find(DIST_KEY)
.map(|offset| entry_start + offset)
let Some(dist_member) = find_composer_member(&content, (entry_start, entry_end), "dist")
.filter(|member| content.as_bytes()[member.value_start] == b'{')
else {
result.warnings.push(RewriteWarning {
code: "redirect_composer_no_dist".into(),
detail: format!("{composer_name} has no dist block"),
});
continue;
};
let Some(dist_end) = json_object_end_from(&content, dist_start + DIST_KEY.len()) else {
result.warnings.push(RewriteWarning {
code: "redirect_composer_lock_malformed".into(),
detail: format!("{composer_name}'s dist block is unterminated"),
});
continue;
};
let dist_start = dist_member.key_start;
let dist_end = dist_member.value_end;
Comment thread
mikolalysenko marked this conversation as resolved.
// The dist's own members only: a `mirrors` entry listed before the
// dist `url` would otherwise take the redirected url and then be
// dropped with the mirrors, leaving the upstream url pinned to the
Expand Down
16 changes: 7 additions & 9 deletions crates/socket-patch-core/src/patch/redirect/upstream/composer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@ use std::collections::BTreeMap;

use serde_json::Value;

use crate::formats::composer::hosted::{
find_composer_entry, json_object_end_from, json_string_field, ComposerEntry,
};
use super::{Ctx, FormatResult, HostedPin, View};
use crate::crawlers::composer_crawler::normalize_version;
use crate::formats::composer::hosted::{
find_composer_entry, find_composer_member, json_string_field, ComposerEntry,
};

const COMPOSER_LOCK: &str = "composer.lock";
const DIST_KEY: &str = "\"dist\": {";
const SOURCE_KEY: &str = "\"source\": {";
/// The `notification-url` composer records for packagist packages.
const PACKAGIST_NOTIFY: &str = "https://packagist.org/downloads/";

Expand All @@ -61,9 +59,9 @@ fn is_dev_version(version: &str) -> bool {

/// The `[start, end]` byte range of the entry's `"dist": {…}` object.
fn dist_range(content: &str, entry: (usize, usize)) -> Option<(usize, usize)> {
let start = entry.0 + content[entry.0..=entry.1].find(DIST_KEY)?;
let end = json_object_end_from(content, start + DIST_KEY.len())?;
Some((start, end))
let member = find_composer_member(content, entry, "dist")
.filter(|member| content.as_bytes()[member.value_start] == b'{')?;
Some((member.key_start, member.value_end))
}

/// A JSON string literal in the lock's style: `\/` when the lock escapes
Expand Down Expand Up @@ -337,7 +335,7 @@ pub(crate) async fn restore(
// Re-insert the source the rewriter dropped — unless the entry
// still carries one (a lock redirected before the drop, or a
// source it could not remove).
let has_source = content[start..d_start].contains(SOURCE_KEY);
let has_source = find_composer_member(&content, (start, end), "source").is_some();
let source_text = match upstream.get("source").and_then(Value::as_object) {
Some(source) if !has_source => {
match render_block(
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
[]
[
{
"path": "composer.lock",
"kind": "redirect_composer_dist",
"action": "rewritten",
"key": "monolog/monolog",
"original": "\"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/Seldaek/monolog/zipball/abc123\",\n \"reference\": \"abc123def456\",\n \"shasum\": \"\"\n },\n \"name\": \"monolog/monolog\",\n \"version\": \"2.0.0\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Seldaek/monolog.git\",\n \"reference\": \"abc123def456\"\n }",
"new": "\"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://patch.socket.dev/patch/composer/monolog/monolog/2.0.0/11111111-1111-1111-1111-111111111111/44444444-4444-4444-4444-444444444444/monolog-2.0.0.zip\",\n \"reference\": \"abc123def456\",\n \"shasum\": \"abcdef0123456789abcdef0123456789abcdef01\"\n },\n \"name\": \"monolog/monolog\",\n \"version\": \"2.0.0\""
}
]
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
[
"redirect_composer_no_dist"
]
[]

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"path": "composer.lock",
"kind": "redirect_composer_dist",
"action": "rewritten",
"key": "monolog/monolog",
"original": "\"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Seldaek/monolog.git\",\n \"reference\": \"abc123def456\"\n },\n \"extra\": {\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://example.com/plugin-metadata.zip\",\n \"shasum\": \"\"\n }\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/Seldaek/monolog/zipball/abc123\",\n \"reference\": \"abc123def456\",\n \"shasum\": \"\"\n }",
"new": "\"extra\": {\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://example.com/plugin-metadata.zip\",\n \"shasum\": \"\"\n }\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://patch.socket.dev/patch/composer/monolog/monolog/2.0.0/11111111-1111-1111-1111-111111111111/44444444-4444-4444-4444-444444444444/monolog-2.0.0.zip\",\n \"reference\": \"abc123def456\",\n \"shasum\": \"abcdef0123456789abcdef0123456789abcdef01\"\n }"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
"ecosystem": "composer",
"name": "monolog",
"namespace": "monolog",
"version": "2.0.0",
"token": "11111111-1111-1111-1111-111111111111",
"patchUuid": "44444444-4444-4444-4444-444444444444",
"artifactUrl": "https://patch.socket.dev/patch/composer/monolog/monolog/2.0.0/11111111-1111-1111-1111-111111111111/44444444-4444-4444-4444-444444444444/monolog-2.0.0.zip",
"integrity": {
"sha1": "abcdef0123456789abcdef0123456789abcdef01"
}
}
]

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
"redirect_composer_no_dist"
]

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
"ecosystem": "composer",
"name": "monolog",
"namespace": "monolog",
"version": "2.0.0",
"token": "11111111-1111-1111-1111-111111111111",
"patchUuid": "44444444-4444-4444-4444-444444444444",
"artifactUrl": "https://patch.socket.dev/patch/composer/monolog/monolog/2.0.0/11111111-1111-1111-1111-111111111111/44444444-4444-4444-4444-444444444444/monolog-2.0.0.zip",
"integrity": {
"sha1": "abcdef0123456789abcdef0123456789abcdef01"
}
}
]
Loading
Loading