Gem `vendor --dry-run` eject previews `eject_planned` (exit 0) for a hosted gem declared in a `group` block, but the real `vendor` refuses `gemfile_declaration_not_editable` (exit 1)
维护者通常 1 天内回复
已经有一个关联 PR 被合并了。
- #1762 来自 @mikolalysenko —— 已合并
评估
调研方向
Start in crates/socket-patch-cli/src/commands/vendor.rs at the run_eject preview loop (around line 1840), which calls takeover_dry_refusal, and compare it with the per-purl path that calls gem_takeover_refusal before the restore (around line 3307). The regression test is the Driver::ScanVexGroupBlock case in e2e_redirect_gem_build.rs. Done means vendor --dry-run reports a failed event with gemfile_declaration_not_editable and exits 1, matching the wet run. A linked pull request (#1762) is already open, so check it before starting.
由索引模型根据 Issue 内容生成。
描述
[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).
Summary
In a hosted Bundler project whose patched gem is declared inside a group … do block, socket-patch vendor --dry-run (the hosted-pins eject) reports the gem as applied with eject_planned and exits 0. The real vendor then refuses the same gem with gemfile_declaration_not_editable, rolls back (eject_rolled_back), and exits 1. scan --mode vendored --dry-run and get <uuid> --mode vendored --dry-run on the same project correctly preview the refusal (#775). Only the plain vendor eject preview misses it.
Impact
CI or a user that gates on vendor --dry-run is told the eject will succeed, but it can never succeed for this project. The wet run is fail-safe: the project stays hosted and the files are byte-identical. So this is a wrong preview, not data loss.
Expected vs actual
- Expected (CLI_CONTRACT.md, "Eject a hosted project (v5.0)", step 2): "Backend refusals are also previewed against the restored text in memory … (3)
--dry-runstops here". The gem declaration refusal is a backend refusal, so the dry run should report afailedevent withgemfile_declaration_not_editableanderror.code: "eject_refused"(exit 1), as the wet run's outcome does.scan/get --mode vendoredalready preview it this way. - Actual: the dry run returns
status: success, oneappliedevent witherrorCode: "eject_planned", and exit 0. The wet run returnspartialFailurewithgemfile_declaration_not_editableplus theeject_rolled_backwarning, and exit 1.
Repro
Use the e2e_redirect_gem_build.rs fixture Driver::ScanVexGroupBlock (real gem build, wiremock patch API with mount_vendored_grant). The project's Gemfile looks like this after the hosted scan:
source "https://rubygems.org"
group :default do
source "<patch-registry>/gem/<org>/<uuid>/" do
gem "vuln-gem", "1.0.0"
end
end
The lock is converged, either through CHECKSUMS (Bundler 2.6+) or through a real unfrozen bundle install on a CHECKSUMS-less lock. Then run:
socket-patch vendor --dry-run --json --yes --api-url $API --org org --api-token fake --patch-server-url $API
# exit 0, events: [{action: applied, errorCode: eject_planned}]
socket-patch vendor --json --yes --api-url $API --org org --api-token fake --patch-server-url $API
# exit 1, events: [{action: failed, errorCode: gemfile_declaration_not_editable}], warning eject_rolled_back
socket-patch scan --mode vendored --dry-run --json ...
# previews gemfile_declaration_not_editable (correct)
A minimal scratch test, appended to e2e_redirect_gem_build.rs, reproduced this on each run, using the helpers already in that file:
let fx = redirect_scanned_project("g", Spelling::Gemfile, false, true, None, Driver::ScanVexGroupBlock).await.unwrap();
mount_vendored_grant(&fx).await;
let (conv, install) = fresh_checkout_bundle_install(&fx); // converge the lock
assert!(install.status.success());
// copy Gemfile + Gemfile.lock + .bundle to a new dir, then run `vendor --dry-run` and `vendor` there
Matrix (Linux, Ruby 3.3.6)
| Bundler | Lock shape | vendor --dry-run |
vendor |
scan --mode vendored --dry-run |
|---|---|---|---|---|
| 4.0.18 | CHECKSUMS (rubygems.org upstream via SOCKET_RUBYGEMS_URL) |
eject_planned, exit 0 ❌ |
refused, exit 1, rolled back | — |
| 4.0.18 | converged by an unfrozen bundle install |
eject_planned, exit 0 ❌ |
refused, exit 1, rolled back | refusal previewed ✅ |
| 2.5.22 | converged by an unfrozen bundle install |
eject_planned, exit 0 ❌ |
refused, exit 1, rolled back | refusal previewed ✅ |
The logic doesn't depend on the OS, so macOS and Windows weren't probed. For comparison, a gems.rb twin is correctly refused in both the dry run and the wet run (gemfile_not_loaded), and a top-level declaration ejects, installs frozen with the patched bytes, and vendor --reverts cleanly.
First bad commit
It isn't new in #1648. The parent commit 8244b94 behaves the same way, but its contract said the dry run stops before any backend preview. #1648 (b787454) added the "backend refusals are also previewed" promise and the takeover_dry_refusal preview loop, and gems fall through that loop.
Suspect code
crates/socket-patch-cli/src/commands/vendor.rs:1840(therun_ejectpreview loop) callstakeover_dry_refusal→dispatch_vendor_onewithdry_run = true. The gem backend's dry run doesn't evaluate the Gemfile declaration gate, so the outcome isn'tRefused. A debug print showedreverted=["Gemfile","Gemfile.lock"], all staged, and an outcome that isn'tRefused.- The per-purl takeover handles this case by calling
gem_takeover_refusal(manifest gate plusgem_vendor_target_preflight) before the restore (vendor.rs:3307), andscan/getusegem_takeover_preview_refusals.run_ejectnever callsgem_takeover_refusals_foron its pins.
No probe runs: the logic is OS-independent.
- 主要语言
- Rust
- 星标
- 8
- 派生
- 2
- 平均合并
- 19 小时 19 分钟
- 30 天内合并 PR
- 697
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
SocketDev/socket-patch 的其他 Issue
-
agent:triaged arch-audit priority:p3 refactor
难度 2/5 1-3 小时 新手友好度 83/100
SocketDev/socket-patch#1860 ·
维护者通常 1 天内回复
-
agent:triaged arch-audit priority:p3 refactor
难度 2/5 1-3 小时 新手友好度 78/100
SocketDev/socket-patch#1838 ·
维护者通常 1 天内回复
-
Share one scoped env-var test guard: EnvGuard is copy-pasted 11 times across core unit and integration tests可能已有人在做 @mikolalysenko 于 1 天前认领。 未关闭agent:claimed agent:triaged arch-audit priority:p3 refactor
难度 4/5 3-5 天 新手友好度 55/100
SocketDev/socket-patch#1889 · 2 条评论 ·
维护者通常 1 天内回复
-
Memoize env-keyed subprocess probes through one helper: composer, python and ruby each define the same env key and drifted on when an answer is kept可能已有人在做 @mikolalysenko 于 1 天前认领。 未关闭agent:claimed agent:triaged arch-audit priority:p1 refactor
难度 4/5 1-2 天 新手友好度 25/100
SocketDev/socket-patch#1888 · 3 条评论 ·
维护者通常 1 天内回复
-
agent:triaged arch-audit priority:p3 refactor
难度 4/5 1-2 天 新手友好度 55/100
SocketDev/socket-patch#1887 · 1 条评论 ·
维护者通常 1 天内回复
查看 SocketDev/socket-patch 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 85/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 85/100
维护者通常 1 天内回复
-
bug
难度 2/5 1-3 小时 新手友好度 77/100
yunfie-twitter/Palleria#474 ·
维护者通常 1 天内回复
-
good first issue help wanted
难度 2/5 1-3 小时 新手友好度 72/100
servo/rust-smallvec#827 ·
维护者通常 1 天内回复
-
bug tests
难度 2/5 1-3 小时 新手友好度 85/100
维护者通常 1 天内回复