From c0920c68967ad998ab15aaf0cd0df76d914bf7a8 Mon Sep 17 00:00:00 2001 From: Julien Danjou Date: Tue, 19 May 2026 17:14:39 +0200 Subject: [PATCH] refactor(ci): swap uuid for getrandom in the GHA heredoc delimiter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ci queue-info::write_github_output` formatted a unique `ghadelimiter_` to guard against a metadata payload that happens to contain its own heredoc delimiter. The actual contract is "32 unpredictable hex chars", not "a UUID per RFC 4122" — the delimiter is never parsed by anyone, only matched as a string. Pull 16 random bytes straight from `getrandom::fill` and hex-encode them. Drops `uuid` from the direct deps (it stays unreferenced and disappears from `Cargo.lock`), with `getrandom` taking its place — which `uuid` was already pulling in transitively, so the net add is zero new code shipped to the binary. The local helper is six lines. Same blast radius for a maintainer-attack story, smaller surface to read. Co-Authored-By: Claude Opus 4.7 Change-Id: Ib6599e9b6fca49281186b726a63e4641fa32596e --- Cargo.lock | 13 +------------ crates/mergify-ci/Cargo.toml | 2 +- crates/mergify-ci/src/queue_info.rs | 20 +++++++++++++++++++- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c13a6c9..3b7db81c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1063,6 +1063,7 @@ name = "mergify-ci" version = "0.0.0" dependencies = [ "chrono", + "getrandom 0.3.4", "mergify-core", "mergify-test-support", "serde", @@ -1072,7 +1073,6 @@ dependencies = [ "tempfile", "tokio", "url", - "uuid", "wiremock", ] @@ -2191,17 +2191,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "uuid" -version = "1.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" -dependencies = [ - "getrandom 0.4.2", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "uuid-simd" version = "0.8.0" diff --git a/crates/mergify-ci/Cargo.toml b/crates/mergify-ci/Cargo.toml index 27552aef..d6ff6fbc 100644 --- a/crates/mergify-ci/Cargo.toml +++ b/crates/mergify-ci/Cargo.toml @@ -12,12 +12,12 @@ publish = false [dependencies] chrono = { version = "0.4", default-features = false, features = ["clock", "serde", "std"] } mergify-core = { path = "../mergify-core" } +getrandom = "0.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_yaml_ng = "0.10" tokio = { version = "1", default-features = false, features = ["rt"] } url = "2" -uuid = { version = "1", features = ["v4"] } [dev-dependencies] mergify-test-support = { path = "../mergify-test-support" } diff --git a/crates/mergify-ci/src/queue_info.rs b/crates/mergify-ci/src/queue_info.rs index f3308ff2..864ef85b 100644 --- a/crates/mergify-ci/src/queue_info.rs +++ b/crates/mergify-ci/src/queue_info.rs @@ -48,7 +48,7 @@ fn write_github_output(metadata: &MergeQueueMetadata) -> Result<(), CliError> { let Some(path) = env::var("GITHUB_OUTPUT").ok().filter(|s| !s.is_empty()) else { return Ok(()); }; - let delimiter = format!("ghadelimiter_{}", uuid::Uuid::new_v4()); + let delimiter = format!("ghadelimiter_{}", random_delimiter_suffix()?); let compact = serde_json::to_string(metadata) .map_err(|e| CliError::Generic(format!("failed to serialize queue metadata: {e}")))?; let mut file = OpenOptions::new() @@ -61,6 +61,24 @@ fn write_github_output(metadata: &MergeQueueMetadata) -> Result<(), CliError> { Ok(()) } +/// 16 random bytes rendered as 32 lowercase hex chars — enough +/// entropy to be unguessable inside one GitHub Actions step, which +/// is all the heredoc delimiter needs (it just has to be absent +/// from the metadata payload). `getrandom` reads from the OS RNG +/// directly; we don't need the UUID parsing/formatting plumbing +/// that `uuid` adds on top. +fn random_delimiter_suffix() -> Result { + let mut buf = [0u8; 16]; + getrandom::fill(&mut buf) + .map_err(|e| CliError::Generic(format!("OS random source unavailable: {e}")))?; + let mut hex = String::with_capacity(buf.len() * 2); + for b in buf { + use std::fmt::Write as _; + write!(hex, "{b:02x}").expect("writing to String is infallible"); + } + Ok(hex) +} + #[cfg(test)] mod tests { use mergify_core::ExitCode;