From 35d29235638eae064ab5aa7a88aba209a3bf13d1 Mon Sep 17 00:00:00 2001 From: lcian <17258265+lcian@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:32:54 +0200 Subject: [PATCH 1/5] feat(snapshots): Support dedicated objectstore auth token Bump objectstore-client to 0.1.6 and use the dedicated auth token from ObjectstoreUploadOptions when available, instead of always reusing the Sentry auth token. The Sentry token is now passed as a Bearer authorization header via configure_reqwest for request verification. This resolves the TODO in the previous implementation and decouples objectstore authentication from Sentry authentication. Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.lock | 32 +++++--------------------------- Cargo.toml | 2 +- src/api/mod.rs | 1 + src/commands/build/snapshots.rs | 26 ++++++++++++++++++-------- 4 files changed, 25 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b9fbaa3d0f..d3face8cf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -206,28 +206,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "atomic-waker" version = "1.1.2" @@ -2322,12 +2300,11 @@ dependencies = [ [[package]] name = "objectstore-client" -version = "0.1.2" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "033eedf125e31b30962c0172842e964fc9983bbccd99d9ff033e7e413946861c" +checksum = "f99caa869461b61decd1985c029d7e1462d1bde8e2448040db27d567165fcc1e" dependencies = [ "async-compression", - "async-stream", "bytes", "futures-util", "infer", @@ -2346,9 +2323,9 @@ dependencies = [ [[package]] name = "objectstore-types" -version = "0.1.2" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956cbdef3971ea108a15e5248625d6229870da3a3c637b6e7aada213526f8014" +checksum = "c375bccef8773d1739eabb7e2b1bbd7e9a9071c8f9557e3b50dce7220e8c8736" dependencies = [ "http", "humantime", @@ -4142,6 +4119,7 @@ dependencies = [ "bytes", "futures-core", "futures-sink", + "futures-util", "pin-project-lite", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 12b7dffead..609ff748a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ java-properties = "2.0.0" lazy_static = "1.4.0" libc = "0.2.139" log = { version = "0.4.17", features = ["std"] } -objectstore-client = { version = "0.1.2" , default-features = false, features = ["native-tls"] } +objectstore-client = { version = "0.1.6" , default-features = false, features = ["native-tls"] } open = "3.2.0" parking_lot = "0.12.1" percent-encoding = "2.2.0" diff --git a/src/api/mod.rs b/src/api/mod.rs index 03f3ec5460..e61cd4edbd 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -2065,5 +2065,6 @@ pub struct SnapshotsUploadOptions { pub struct ObjectstoreUploadOptions { pub url: String, pub scopes: Vec<(String, String)>, + pub auth_token: Option, pub expiration_policy: String, } diff --git a/src/commands/build/snapshots.rs b/src/commands/build/snapshots.rs index 04bea69fa4..b0deb4f6cd 100644 --- a/src/commands/build/snapshots.rs +++ b/src/commands/build/snapshots.rs @@ -295,15 +295,25 @@ fn upload_images( let expiration = ExpirationPolicy::from_str(&options.objectstore.expiration_policy) .context("Failed to parse expiration policy from upload options")?; - let client = ClientBuilder::new(options.objectstore.url) - .token({ - // TODO: replace with auth from `ObjectstoreUploadOptions` when appropriate - let auth = match authenticated_api.auth() { - Auth::Token(token) => token.raw().expose_secret().to_owned(), - }; - auth + let mut builder = ClientBuilder::new(options.objectstore.url); + if let Some(token) = options.objectstore.auth_token { + builder = builder.token(token); + } + + let sentry_token = match authenticated_api.auth() { + Auth::Token(token) => token.raw().expose_secret().to_owned(), + }; + let sentry_token = format!("Bearer {sentry_token}") + .parse() + // Ignore original error to avoid leaking the token (even though it's invalid) + .map_err(|_| anyhow::anyhow!("Invalid auth token"))?; + let client = builder + .configure_reqwest(|r| { + let mut headers = http::HeaderMap::new(); + headers.insert(http::header::AUTHORIZATION, sentry_token); + r.connect_timeout(Duration::from_secs(10)) + .default_headers(headers) }) - .configure_reqwest(|r| r.connect_timeout(Duration::from_secs(10))) .build()?; let mut scope = Usecase::new("preprod").scope(); From 0dbd02bcc02051553148568c71cdbae7ae0d600e Mon Sep 17 00:00:00 2001 From: lcian <17258265+lcian@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:36:40 +0200 Subject: [PATCH 2/5] fix(snapshots): Await send() before calling error_for_failures The objectstore-client 0.1.6 upgrade changed send() to return a Future, so it needs to be awaited before calling error_for_failures(). Also add changelog entry for the PR. Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 4 ++++ src/commands/build/snapshots.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d63809dc6..fd472ec2da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- (snapshots) Support dedicated objectstore auth token ([#3258](https://github.com/getsentry/sentry-cli/pull/3258)) + ### Fixes - Replace `eprintln!` with `log::info!` for progress bar completion messages when the progress bar is disabled (e.g. in CI). This avoids spurious stderr output that some CI systems treat as errors ([#3223](https://github.com/getsentry/sentry-cli/pull/3223)). diff --git a/src/commands/build/snapshots.rs b/src/commands/build/snapshots.rs index b0deb4f6cd..18a2041849 100644 --- a/src/commands/build/snapshots.rs +++ b/src/commands/build/snapshots.rs @@ -410,7 +410,7 @@ fn upload_images( warn!("Some images share identical file names. Only the first occurrence of each is included:{details}"); } - let result = runtime.block_on(async { many_builder.send().error_for_failures().await }); + let result = runtime.block_on(async { many_builder.send().await.error_for_failures().await }); let uploaded_count = manifest_entries.len(); From d40bd129d286a543f7873e08a994a1f706928142 Mon Sep 17 00:00:00 2001 From: lcian <17258265+lcian@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:37:48 +0200 Subject: [PATCH 3/5] ref: Remove changelog entry Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd472ec2da..2d63809dc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,6 @@ ## Unreleased -### Features - -- (snapshots) Support dedicated objectstore auth token ([#3258](https://github.com/getsentry/sentry-cli/pull/3258)) - ### Fixes - Replace `eprintln!` with `log::info!` for progress bar completion messages when the progress bar is disabled (e.g. in CI). This avoids spurious stderr output that some CI systems treat as errors ([#3223](https://github.com/getsentry/sentry-cli/pull/3223)). From b0b0a6e71fcc0fcebed497ae44a0e0b1c2dedfa6 Mon Sep 17 00:00:00 2001 From: lcian <17258265+lcian@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:49:00 +0200 Subject: [PATCH 4/5] ref(api): Use SecretString for objectstore auth token Enable the serde feature on secrecy and use SecretString instead of plain String for the auth_token field in ObjectstoreUploadOptions. This prevents accidental logging of the token. Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.lock | 1 + Cargo.toml | 2 +- src/api/mod.rs | 2 +- src/commands/build/snapshots.rs | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3face8cf9..7810dcc3b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3322,6 +3322,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" dependencies = [ + "serde", "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index 609ff748a1..eb0d4bf774 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,7 @@ zip = "2.4.2" data-encoding = "2.3.3" magic_string = "0.3.4" chrono-tz = "0.8.4" -secrecy = "0.8.0" +secrecy = { version = "0.8.0", features = ["serde"] } lru = "0.16.3" backon = { version = "1.5.2", features = ["std", "std-blocking-sleep"] } diff --git a/src/api/mod.rs b/src/api/mod.rs index e61cd4edbd..e93052f2a1 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -2065,6 +2065,6 @@ pub struct SnapshotsUploadOptions { pub struct ObjectstoreUploadOptions { pub url: String, pub scopes: Vec<(String, String)>, - pub auth_token: Option, + pub auth_token: Option, pub expiration_policy: String, } diff --git a/src/commands/build/snapshots.rs b/src/commands/build/snapshots.rs index 18a2041849..ec247e540d 100644 --- a/src/commands/build/snapshots.rs +++ b/src/commands/build/snapshots.rs @@ -297,7 +297,7 @@ fn upload_images( let mut builder = ClientBuilder::new(options.objectstore.url); if let Some(token) = options.objectstore.auth_token { - builder = builder.token(token); + builder = builder.token(token.expose_secret().to_owned()); } let sentry_token = match authenticated_api.auth() { From 96ab303054f7fa174decef947f7d7e94e51aa2f6 Mon Sep 17 00:00:00 2001 From: lcian <17258265+lcian@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:50:28 +0200 Subject: [PATCH 5/5] style(api): Import SecretString instead of using inline path Co-Authored-By: Claude Opus 4.6 (1M context) --- src/api/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index e93052f2a1..ea3d5eb279 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -33,7 +33,7 @@ use lazy_static::lazy_static; use log::{debug, info, warn}; use parking_lot::Mutex; use regex::{Captures, Regex}; -use secrecy::ExposeSecret as _; +use secrecy::{ExposeSecret as _, SecretString}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use sha1_smol::Digest; @@ -2065,6 +2065,6 @@ pub struct SnapshotsUploadOptions { pub struct ObjectstoreUploadOptions { pub url: String, pub scopes: Vec<(String, String)>, - pub auth_token: Option, + pub auth_token: Option, pub expiration_policy: String, }