สรุปสั้น

Rust 1.100 ซึ่งกำหนดออกวันที่ 12 พฤศจิกายน 2569 จะรองรับฟีเจอร์ dependency cooldown ใน Cargo แบบ stable เป็นครั้งแรก ให้นักพัฒนาสั่งปฏิเสธเวอร์ชันแพ็กเกจที่เพิ่งเผยแพร่จนกว่าจะมีอายุถึงเกณฑ์ขั้นต่ำที่ตั้งไว้ จุดประสงค์คือซื้อเวลาให้นักวิจัย ผู้ดูแล และ registry ตรวจพบเวอร์ชันอันตรายก่อนที่การอัปเดตอัตโนมัติจะกระจายมันเข้าเครื่องนักพัฒนาและ CI pipeline ฟีเจอร์นี้ระบุไว้ในร่าง release notes ของ Rust แล้ว และเป็นแบบ opt-in ค่าอายุขั้นต่ำเริ่มต้นยังเป็นศูนย์ การอัปเกรด Rust อย่างเดียวจึงไม่ได้หน่วงอะไรเลย ต้องตั้งค่าเกณฑ์เองจึงจะได้ผล

เหตุผลด้านความปลอดภัยเห็นชัดจากกรณีเดือนสิงหาคมที่ crate arrayref, internment และ append-only-vec ถูกยึดแล้วฝัง dependency ปลอมชื่อ proc-macro1 ที่ดาวน์โหลดมัลแวร์ตอนคอมไพล์ (ดูโพสต์ 08/129) ซึ่งทีมรับมือเหตุของ Rust ถอดออกหลังอยู่บน crates.io เพียง 86, 90 และ 107 นาที หากโปรเจกต์ตั้ง cooldown ไว้ การ resolve ใหม่ในช่วงนั้นจะไม่เลือกเวอร์ชันอันตราย แต่ cooldown ไม่ได้สแกนโค้ดและไม่ได้พิสูจน์ว่าแพ็กเกจปลอดภัย เวอร์ชันอันตรายที่หลบการตรวจจับได้ก็จะผ่านเกณฑ์อายุไปเองเมื่อเวลาผ่านไป

รายละเอียดข่าว

เว็บไซต์ GBHackers รายงานเมื่อวันที่ 21 ตุลาคม 2569 ตามบทวิเคราะห์ของบริษัท Socket ว่า cooldown ของ Cargo ทำงานที่ขั้นเลือก dependency (resolver) ไม่ใช่การสแกนหาโค้ดอันตราย ช่วงเวลาที่เปิดให้ดาวน์โหลดสั้น ๆ ของกรณี arrayref แสดงให้เห็นปัญหาว่าการ resolve dependency ส่งโค้ดที่ผู้โจมตีควบคุมเข้าเครื่องได้ก่อนที่ผู้ตรวจสอบจะยืนยันและ registry จะถอดออก การตั้งช่วงรอจะกันโปรเจกต์ที่ resolve ใหม่ไม่ให้เลือกเวอร์ชันเหล่านั้นในช่วงดังกล่าว ตราบใดที่เวอร์ชันนั้นยังไม่ถูกล็อกไว้ก่อน

กรณี arrayref ยังมีรายละเอียดที่ส่งผลต่อการออกแบบ คือผู้โจมตี yank เวอร์ชันของแท้ของ arrayref ออกด้วย หากระบบแค่ “เลือกเวอร์ชันเก่ากว่าเป็นหลัก” จะมีทางถอยอันตราย เมื่อเวอร์ชันปลอดภัยหายไปจากตัวเลือก resolver อาจรับเวอร์ชันใหม่ที่เป็นอันตรายแทน Cargo จึงออกแบบให้ปฏิเสธเวอร์ชันที่อายุต่ำกว่าเกณฑ์ และล้มเหลวทันทีหากไม่มีเวอร์ชันที่ผ่านเกณฑ์ตรงกับเงื่อนไข RFC ของฟีเจอร์ยังอธิบายว่าการบังคับใช้ในตัว resolver ครอบคลุมกว่าบอตอัปเดต dependency ภายนอก เพราะบอตคุมการแก้ manifest ในเครื่อง การสั่ง cargo update โดยตรง หรือการเปลี่ยน lockfile โดยปริยายไม่ได้

รายละเอียดเชิงเทคนิค

เปิดใช้ด้วยการเพิ่มค่า global-min-publish-age ใต้ส่วน [registry] ในไฟล์ .cargo/config.toml เช่นหน่วงไว้ 7 วัน:

[registry]
global-min-publish-age = "7 days"

Cargo รองรับหน่วยเวลาวินาที นาที ชั่วโมง วัน สัปดาห์ และเดือน ส่วนค่า resolver.incompatible-publish-age มีค่าเริ่มต้นเป็น "deny" จึงไม่ต้องตั้งเพิ่ม ระหว่าง resolve Cargo จะตัดเวอร์ชันที่อายุน้อยกว่าเกณฑ์ออก แต่ยังเก็บเวอร์ชันเก่าที่ตรงเงื่อนไขของโปรเจกต์ไว้ ข้อความวินิจฉัยจะบอกว่าข้ามเวอร์ชันใหม่ตัวใดและอธิบายสาเหตุที่ resolve ไม่สำเร็จ และองค์กรตั้งช่วงรอต่างกันตาม registry ได้

ข้อจำกัดที่ต้องรู้มีหลายข้อ รายการใน Cargo.lock ที่มีอยู่แล้วยังใช้ได้แม้เวอร์ชันนั้นจะอายุน้อยกว่าเกณฑ์ การเปิดฟีเจอร์จึงไม่ได้ถอด dependency อันตรายที่รับเข้ามาก่อนหน้า ต้องรีวิว lockfile เองจึงจะรู้ขอบเขตที่ครอบคลุมจริง กรณีต้องรับแพตช์ด่วน สามารถข้ามชั่วคราวด้วย CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE=allow cargo update -p some-crate --precise 1.2.3 ซึ่งจะยอมรับ transitive dependency ที่ resolve ใหม่ด้วย นอกจากนี้การตรวจอาศัย timestamp การเผยแพร่ของ registry ทำให้ git dependency, path dependency ในเครื่อง และ registry ที่ไม่มี metadata นี้อยู่นอกการตรวจ และแพตช์ที่ถูกต้องก็ต้องรอตามเกณฑ์เช่นกันหากไม่สั่งข้าม

ผลกระทบ

การโจมตีห่วงโซ่อุปทานผ่าน registry แพ็กเกจส่วนใหญ่อาศัยช่วงเวลาไม่กี่ชั่วโมงก่อนถูกตรวจพบ ฟีเจอร์นี้ปิดช่องดังกล่าวสำหรับโปรเจกต์ Rust ที่เปิดใช้ ซึ่งสอดคล้องกับแนวทางที่ระบบนิเวศอื่นทยอยเพิ่มฟีเจอร์คล้ายกัน แต่เพราะเป็น opt-in และไม่ย้อนตรวจ lockfile ทีมที่ไม่ตั้งค่าจะไม่ได้ประโยชน์ใดเลย และ RFC เตือนชัดว่าไม่ควรใช้เป็นการป้องกันเพียงชั้นเดียวสำหรับ production เพราะมันซื้อเวลาให้การตรวจจับ ไม่ได้ตรวจจับเอง

คำแนะนำ

  • เมื่อ Rust 1.100 ออก ให้ตั้ง global-min-publish-age ใน .cargo/config.toml ของโปรเจกต์และ CI runner โดยเลือกช่วงรอที่สมดุลกับความเร็วในการรับแพตช์ เช่น 3–7 วัน
  • รีวิว Cargo.lock ว่ามี dependency ที่ถูกเพิ่มในช่วงสั้น ๆ ก่อนเปิดฟีเจอร์หรือไม่ เพราะรายการเดิมไม่ถูกตรวจย้อนหลัง
  • กำหนดขั้นตอนอนุมัติการใช้ตัวแปร CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE=allow สำหรับแพตช์ด่วน และตรวจ transitive dependency ที่เข้ามาพร้อมกัน
  • ตรวจ git dependency และ path dependency แยกต่างหาก เพราะอยู่นอกการตรวจของ cooldown
  • ใช้ cooldown ร่วมกับเครื่องมือสแกนแพ็กเกจ การ pin เวอร์ชัน และการเฝ้าดู build script (build.rs) และ proc-macro ที่รันตอนคอมไพล์

แหล่งอ้างอิง