Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

Gem hosted `rollback` / `remove` turn a redirected transitive gem into a top-level exact pin, because the restore looks for a blank line the rewriter never writes

已关闭
#457 3 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
3/5
预计耗时
1-2 天
新手友好度
78/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
ruby, rust
领域
cli, testing

调研方向

Read crates/socket-patch-core/src/patch/redirect/mod.rs:5070 and crates/socket-patch-core/src/patch/redirect/upstream/gem.rs:480,691, then run provably_transitive_needs_a_blank_line_before_the_block. Reproduce rollback and remove with an LF-terminated Gemfile and CHECKSUMS lock; done means both restore the Gemfile and lock byte-identically without creating a direct dependency, while existing blank-line behavior remains covered.

由索引模型根据 Issue 内容生成。

描述

agent:claimed agent:triaged bug bughunt pm:bundler priority:p1

[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).

Summary

When scan --mode hosted redirects a transitive gem, it appends a source "<patch registry>" do … end block to the end of the Gemfile. When the Gemfile already ends in a newline (the normal case), it writes no blank separator line. The upstream restore that rollback and remove share only treats a block as "the rewriter's append for a transitive gem" (Decl::Transitive) when a blank line precedes the block (provably_appended). Because the rewriter's own append never has one, the restore falls through to Decl::Direct. The transitive gem comes back as a new top-level declaration, gem "<name>", "<version>", and the lock gains a DEPENDENCIES entry <name> (= <version>).

This only affects converged locks, which means locks with a CHECKSUMS section (Bundler 2.6+ with checksums enabled, the Bundler 4 default). CHECKSUMS-less locks take the mixed-state path, which reads DEPENDENCIES and gets this right.

Impact

  • After rollback / remove, the project is not back on its upstream registry state, which is what CLI_CONTRACT.md's "Hosted unwind coverage" promises. It has a new direct dependency pinned to exactly the version that had the vulnerability.
  • That pin freezes the vulnerable version. In the repro, bundle update rack keeps rack 3.2.1 after the rollback, while the byte-identical control upgrades to rack 3.2.7. A user who removes the hosted patch to take the upstream fix can't get it until they find and delete a declaration they never wrote.
  • It's silent: status: success, reverted: [pkg:gem/[email protected]], no warning.

Repro (Linux, Ruby 3.3.6, Bundler 4.0.17; real rubygems.org upstream, local mock for the patch API and the patch-registry compact index)

# Project: rack 3.2.1 is transitive via rackup; the Gemfile ends with a declaration line + "\n".
printf 'source "https://rubygems.org"\n\ngem "rackup", "2.2.1"\n' > Gemfile
bundle config set --local path vendor/bundle
bundle install                       # Bundler 4 writes CHECKSUMS
cp Gemfile Gemfile.pristine; cp Gemfile.lock lock.pristine; rm -rf vendor
socket-patch scan --mode hosted --json --yes --api-url $API --org test-org --api-token fake
#   -> redirected 1, rewrittenFiles [Gemfile, Gemfile.lock]; block appended right after `gem "rackup"` (no blank line)
socket-patch rollback --json --yes --patch-server-url $API --api-url $API --org test-org --api-token fake
#   -> status success, hosted.reverted [pkg:gem/[email protected]]
diff Gemfile.pristine Gemfile
#   3a4
#   > gem "rack", "3.2.1"
diff lock.pristine Gemfile.lock
#   12a13
#   >   rack (= 3.2.1)
bundle update rack && grep '    rack (' Gemfile.lock   # rack (3.2.1): stuck on the vulnerable version

Control: the same flow with the Gemfile ending in \n\n (one trailing blank line) restores byte-identically (Gemfile and lock), and bundle update rack moves to 3.2.7.
socket-patch remove pkg:gem/[email protected] gives the same result as rollback.

Expected vs actual

  • Expected (CLI_CONTRACT.md, "Hosted unwind coverage", gem bullet): "the spec moves back into the upstream GEM section …, the source "<patch registry>" do … end block is undone … and the DEPENDENCIES pin loses its !". For a gem the rewriter appended because it was transitive, the code's own intent (Decl::Transitive: "Gone: the block was the rewriter's append for a transitive gem") is that the block and the DEPENDENCIES entry both go away. The documented "comes back as the exact pin" caveat covers a gem that had a declaration, not one the user never declared.
  • Actual: a new gem "rack", "3.2.1" line and a rack (= 3.2.1) DEPENDENCIES entry.

Matrix

OS Ruby Bundler Lock Result
Linux 3.3.6 4.0.17 CHECKSUMS (default) reproduces (rollback twice, remove once)
Linux 3.3.6 2.6.9 bundle lock --add-checksums reproduces
Linux 3.3.6 4.0.17 CHECKSUMS, Gemfile ends with a blank line pass (byte-identical restore)
Linux 3.3.6 2.6.9 no CHECKSUMS (mixed state) n/a: the lock isn't converged, and rollback can't see a Gemfile-only pin (documented)

The logic is plain text processing with no OS dependency, so macOS and Windows should behave the same. I didn't bisect: the upstream restore is new in v5 (#277), and main 2463257 is the first commit that has it.

Suspect code

  • crates/socket-patch-core/src/patch/redirect/mod.rs:5070: the transitive append is let sep = if gf.ends_with('\n') { "" } else { "\n" };, which leaves no blank line before the block.
  • crates/socket-patch-core/src/patch/redirect/upstream/gem.rs:480 (provably_appended) and :691: the restore requires a blank line before the block to choose Decl::Transitive. The unit test provably_transitive_needs_a_blank_line_before_the_block builds its "appended" fixture as gem "puma"\n\n{block}, a shape the rewriter never produces on an LF-terminated Gemfile.

One possible fix: have the rewriter's append write a blank separator, so its output is provably distinguishable from an in-place rewrite (which swallows the preceding blank lines). Hosted Gemfiles written by earlier runs would still need a fallback.

主要语言
Rust
星标
8
派生
0
平均合并
18 小时 4 分钟
30 天内合并 PR
70

环境准备

  • 没有 Dockerfile 或 Docker Compose 文件
  • 没有 Pull Request 模板
  • 阅读贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

SocketDev/socket-patch 的其他 Issue

查看 SocketDev/socket-patch 的全部 Issue

相似的 Issue

更多 Rust Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。