This is an automated email from the ASF dual-hosted git repository.
hgruszecki pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/master by this push:
new cea62a95d fix(security): replace SecretString with String for API
response tokens (#3008)
cea62a95d is described below
commit cea62a95d111231932f2b7deaceba6a96959e222
Author: Piotr Gankiewicz <[email protected]>
AuthorDate: Wed Mar 25 21:59:23 2026 +0100
fix(security): replace SecretString with String for API response tokens
(#3008)
---
Cargo.lock | 6 +++---
Cargo.toml | 6 +++---
DEPENDENCIES.md | 6 +++---
bdd/python/uv.lock | 2 +-
core/binary_protocol/Cargo.toml | 2 +-
.../create_personal_access_token.rs | 7 +++----
core/cli/src/commands/binary_system/login.rs | 3 +--
core/common/Cargo.toml | 2 +-
core/common/src/traits/binary_mapper.rs | 5 +----
.../src/types/permissions/personal_access_token.rs | 16 ++--------------
core/common/src/types/user/user_identity_info.rs | 17 ++---------------
.../cli/personal_access_token/test_pat_login_options.rs | 3 +--
.../verify_no_plaintext_credentials_on_disk.rs | 3 +--
core/integration/tests/mcp/mod.rs | 4 +---
.../tests/server/scenarios/authentication_scenario.rs | 3 +--
.../server/scenarios/cross_protocol_pat_scenario.rs | 5 ++---
.../integration/tests/server/scenarios/user_scenario.rs | 9 ++++-----
core/sdk/Cargo.toml | 2 +-
core/sdk/src/http/http_client.rs | 3 +--
core/server/src/http/mapper.rs | 3 +--
core/server/src/http/personal_access_tokens.rs | 6 ++----
examples/python/uv.lock | 2 +-
foreign/python/Cargo.toml | 2 +-
23 files changed, 38 insertions(+), 79 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index e69629314..02b353962 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5195,7 +5195,7 @@ checksum =
"cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb"
[[package]]
name = "iggy"
-version = "0.9.3-edge.1"
+version = "0.9.4-edge.1"
dependencies = [
"async-broadcast",
"async-dropper",
@@ -5396,7 +5396,7 @@ dependencies = [
[[package]]
name = "iggy_binary_protocol"
-version = "0.9.3-edge.1"
+version = "0.9.4-edge.1"
dependencies = [
"bytemuck",
"bytes",
@@ -5406,7 +5406,7 @@ dependencies = [
[[package]]
name = "iggy_common"
-version = "0.9.3-edge.1"
+version = "0.9.4-edge.1"
dependencies = [
"aes-gcm",
"ahash 0.8.12",
diff --git a/Cargo.toml b/Cargo.toml
index 6ac198684..0f8142ef2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -161,10 +161,10 @@ hwlocality = "1.0.0-alpha.11"
iceberg = "0.9.0"
iceberg-catalog-rest = "0.9.0"
iceberg-storage-opendal = "0.9.0"
-iggy = { path = "core/sdk", version = "0.9.3-edge.1" }
+iggy = { path = "core/sdk", version = "0.9.4-edge.1" }
iggy-cli = { path = "core/cli", version = "0.11.3-edge.1" }
-iggy_binary_protocol = { path = "core/binary_protocol", version =
"0.9.3-edge.1" }
-iggy_common = { path = "core/common", version = "0.9.3-edge.1" }
+iggy_binary_protocol = { path = "core/binary_protocol", version =
"0.9.4-edge.1" }
+iggy_common = { path = "core/common", version = "0.9.4-edge.1" }
iggy_connector_sdk = { path = "core/connectors/sdk", version = "0.2.1-edge.1" }
integration = { path = "core/integration" }
journal = { path = "core/journal" }
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
index e89e01db6..83d9349ad 100644
--- a/DEPENDENCIES.md
+++ b/DEPENDENCIES.md
@@ -452,14 +452,14 @@ ident_case: 1.0.1, "Apache-2.0 OR MIT",
idna: 1.1.0, "Apache-2.0 OR MIT",
idna_adapter: 1.2.1, "Apache-2.0 OR MIT",
if_chain: 1.0.3, "Apache-2.0 OR MIT",
-iggy: 0.9.3-edge.1, "Apache-2.0",
+iggy: 0.9.4-edge.1, "Apache-2.0",
iggy-bench: 0.4.1-edge.1, "Apache-2.0",
iggy-bench-dashboard-server: 0.6.3-edge.1, "Apache-2.0",
iggy-cli: 0.11.3-edge.1, "Apache-2.0",
iggy-connectors: 0.3.2-edge.1, "Apache-2.0",
iggy-mcp: 0.3.2-edge.1, "Apache-2.0",
-iggy_binary_protocol: 0.9.3-edge.1, "Apache-2.0",
-iggy_common: 0.9.3-edge.1, "Apache-2.0",
+iggy_binary_protocol: 0.9.4-edge.1, "Apache-2.0",
+iggy_common: 0.9.4-edge.1, "Apache-2.0",
iggy_connector_elasticsearch_sink: 0.3.2-edge.1, "Apache-2.0",
iggy_connector_elasticsearch_source: 0.3.2-edge.1, "Apache-2.0",
iggy_connector_iceberg_sink: 0.3.2-edge.1, "Apache-2.0",
diff --git a/bdd/python/uv.lock b/bdd/python/uv.lock
index e3d0aa275..26ea99b5c 100644
--- a/bdd/python/uv.lock
+++ b/bdd/python/uv.lock
@@ -4,7 +4,7 @@ requires-python = ">=3.10"
[[package]]
name = "apache-iggy"
-version = "0.7.3.dev1"
+version = "0.7.4.dev1"
source = { directory = "../../foreign/python" }
[package.metadata]
diff --git a/core/binary_protocol/Cargo.toml b/core/binary_protocol/Cargo.toml
index e3ec5ecdd..b479ecf09 100644
--- a/core/binary_protocol/Cargo.toml
+++ b/core/binary_protocol/Cargo.toml
@@ -17,7 +17,7 @@
[package]
name = "iggy_binary_protocol"
-version = "0.9.3-edge.1"
+version = "0.9.4-edge.1"
description = "Wire protocol types and codec for the Iggy binary protocol.
Shared between server and SDK."
edition = "2024"
license = "Apache-2.0"
diff --git
a/core/cli/src/commands/binary_personal_access_tokens/create_personal_access_token.rs
b/core/cli/src/commands/binary_personal_access_tokens/create_personal_access_token.rs
index bf0aef95f..e6f87e2cd 100644
---
a/core/cli/src/commands/binary_personal_access_tokens/create_personal_access_token.rs
+++
b/core/cli/src/commands/binary_personal_access_tokens/create_personal_access_token.rs
@@ -23,7 +23,6 @@ use iggy_common::Client;
use iggy_common::PersonalAccessTokenExpiry;
use iggy_common::create_personal_access_token::CreatePersonalAccessToken;
use keyring::Entry;
-use secrecy::ExposeSecret;
use tracing::{Level, event};
pub struct CreatePersonalAccessTokenCmd {
@@ -85,7 +84,7 @@ impl CliCommand for CreatePersonalAccessTokenCmd {
if self.store_token {
let server_address = format!("iggy:{}", self.server_address);
let entry = Entry::new(&server_address, &self.create_token.name)?;
- entry.set_password(token.token.expose_secret())?;
+ entry.set_password(&token.token)?;
event!(target: PRINT_TARGET, Level::DEBUG,"Stored token under
service: {} and name: {}", server_address,
self.create_token.name);
event!(target: PRINT_TARGET, Level::INFO,
@@ -97,7 +96,7 @@ impl CliCommand for CreatePersonalAccessTokenCmd {
},
);
} else if self.quiet_mode {
- println!("{}", token.token.expose_secret());
+ println!("{}", &token.token);
} else {
event!(target: PRINT_TARGET, Level::INFO,
"Personal access token with name: {} and {} created",
@@ -108,7 +107,7 @@ impl CliCommand for CreatePersonalAccessTokenCmd {
},
);
event!(target: PRINT_TARGET, Level::INFO,"Token: {}",
- token.token.expose_secret());
+ &token.token);
}
Ok(())
diff --git a/core/cli/src/commands/binary_system/login.rs
b/core/cli/src/commands/binary_system/login.rs
index efac41632..7c99c1db9 100644
--- a/core/cli/src/commands/binary_system/login.rs
+++ b/core/cli/src/commands/binary_system/login.rs
@@ -23,7 +23,6 @@ use anyhow::Context;
use async_trait::async_trait;
use iggy_common::Client;
use iggy_common::SEC_IN_MICRO;
-use secrecy::ExposeSecret;
use tracing::{Level, event};
const DEFAULT_LOGIN_SESSION_TIMEOUT: u64 = SEC_IN_MICRO * 15 * 60;
@@ -95,7 +94,7 @@ impl CliCommand for LoginCmd {
)
})?;
- self.server_session.store(token.token.expose_secret())?;
+ self.server_session.store(&token.token)?;
event!(target: PRINT_TARGET, Level::INFO,
"Successfully logged into Iggy server {}",
diff --git a/core/common/Cargo.toml b/core/common/Cargo.toml
index 3af8fa87f..56e99742f 100644
--- a/core/common/Cargo.toml
+++ b/core/common/Cargo.toml
@@ -16,7 +16,7 @@
# under the License.
[package]
name = "iggy_common"
-version = "0.9.3-edge.1"
+version = "0.9.4-edge.1"
description = "Iggy is the persistent message streaming platform written in
Rust, supporting QUIC, TCP and HTTP transport protocols, capable of processing
millions of messages per second."
edition = "2024"
license = "Apache-2.0"
diff --git a/core/common/src/traits/binary_mapper.rs
b/core/common/src/traits/binary_mapper.rs
index 52b3e6056..891dbc76f 100644
--- a/core/common/src/traits/binary_mapper.rs
+++ b/core/common/src/traits/binary_mapper.rs
@@ -24,7 +24,6 @@ use crate::{
Stream, StreamDetails, Topic, TopicDetails, UserInfo, UserInfoDetails,
UserStatus,
};
use bytes::Bytes;
-use secrecy::SecretString;
use std::collections::HashMap;
use std::str::from_utf8;
@@ -508,9 +507,7 @@ pub fn map_raw_pat(payload: Bytes) ->
Result<RawPersonalAccessToken, IggyError>
let token = from_utf8(&payload[1..1 + token_length as usize])
.map_err(|_| IggyError::InvalidUtf8)?
.to_string();
- Ok(RawPersonalAccessToken {
- token: SecretString::from(token),
- })
+ Ok(RawPersonalAccessToken { token })
}
pub fn map_client(payload: Bytes) -> Result<ClientInfoDetails, IggyError> {
diff --git a/core/common/src/types/permissions/personal_access_token.rs
b/core/common/src/types/permissions/personal_access_token.rs
index 6fc5be45c..0837ba17c 100644
--- a/core/common/src/types/permissions/personal_access_token.rs
+++ b/core/common/src/types/permissions/personal_access_token.rs
@@ -16,28 +16,16 @@
* under the License.
*/
-use crate::utils::serde_secret::serialize_secret;
use crate::utils::timestamp::IggyTimestamp;
-use secrecy::SecretString;
use serde::{Deserialize, Serialize};
-use std::fmt;
/// `RawPersonalAccessToken` represents the raw personal access token - the
secured token which is returned only once during the creation.
/// It consists of the following fields:
/// - `token`: the unique token that should be securely stored by the user and
can be used for authentication.
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
pub struct RawPersonalAccessToken {
/// The unique token that should be securely stored by the user and can be
used for authentication.
- #[serde(serialize_with = "serialize_secret")]
- pub token: SecretString,
-}
-
-impl fmt::Debug for RawPersonalAccessToken {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_struct("RawPersonalAccessToken")
- .field("token", &"[REDACTED]")
- .finish()
- }
+ pub token: String,
}
/// `PersonalAccessToken` represents the personal access token. It does not
contain the token itself, but the information about the token.
diff --git a/core/common/src/types/user/user_identity_info.rs
b/core/common/src/types/user/user_identity_info.rs
index fef4946d3..64005eec2 100644
--- a/core/common/src/types/user/user_identity_info.rs
+++ b/core/common/src/types/user/user_identity_info.rs
@@ -17,10 +17,7 @@
*/
use crate::UserId;
-use crate::utils::serde_secret::serialize_secret;
-use secrecy::SecretString;
use serde::{Deserialize, Serialize};
-use std::fmt;
/// `IdentityInfo` represents the information about an identity.
/// It consists of the following fields:
@@ -38,20 +35,10 @@ pub struct IdentityInfo {
/// It consists of the following fields:
/// - `token`: the value of token.
/// - `expiry`: the expiry of token.
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
pub struct TokenInfo {
/// The value of token.
- #[serde(serialize_with = "serialize_secret")]
- pub token: SecretString,
+ pub token: String,
/// The expiry of token.
pub expiry: u64,
}
-
-impl fmt::Debug for TokenInfo {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_struct("TokenInfo")
- .field("token", &"[REDACTED]")
- .field("expiry", &self.expiry)
- .finish()
- }
-}
diff --git
a/core/integration/tests/cli/personal_access_token/test_pat_login_options.rs
b/core/integration/tests/cli/personal_access_token/test_pat_login_options.rs
index 2709c5254..536d86f60 100644
--- a/core/integration/tests/cli/personal_access_token/test_pat_login_options.rs
+++ b/core/integration/tests/cli/personal_access_token/test_pat_login_options.rs
@@ -23,7 +23,6 @@ use iggy::prelude::Client;
use iggy::prelude::PersonalAccessTokenExpiry;
use keyring::Entry;
use predicates::str::{contains, starts_with};
-use secrecy::ExposeSecret;
use serial_test::parallel;
use std::fmt::{Display, Formatter, Result};
@@ -89,7 +88,7 @@ impl IggyCmdTestCase for TestLoginOptions {
.await;
assert!(token.is_ok());
let token = token.unwrap();
- let token_value = token.token.expose_secret().to_owned();
+ let token_value = token.token.clone();
self.keyring
.set_password(&token_value)
.expect("Failed to set token");
diff --git
a/core/integration/tests/data_integrity/verify_no_plaintext_credentials_on_disk.rs
b/core/integration/tests/data_integrity/verify_no_plaintext_credentials_on_disk.rs
index 2307e00eb..851f4eb90 100644
---
a/core/integration/tests/data_integrity/verify_no_plaintext_credentials_on_disk.rs
+++
b/core/integration/tests/data_integrity/verify_no_plaintext_credentials_on_disk.rs
@@ -18,7 +18,6 @@
use iggy::prelude::*;
use integration::iggy_harness;
-use secrecy::ExposeSecret;
use std::fs;
use std::path::{Path, PathBuf};
@@ -38,7 +37,7 @@ async fn
should_not_persist_plaintext_password_or_pat_to_disk(harness: &mut Test
.create_personal_access_token(PAT_NAME, IggyExpiry::NeverExpire)
.await
.unwrap();
- let raw_pat_token = raw_pat.token.expose_secret();
+ let raw_pat_token = &raw_pat.token;
assert!(!raw_pat_token.is_empty(), "Expected non-empty PAT value");
diff --git a/core/integration/tests/mcp/mod.rs
b/core/integration/tests/mcp/mod.rs
index 61560bb8c..ed8ade4b8 100644
--- a/core/integration/tests/mcp/mod.rs
+++ b/core/integration/tests/mcp/mod.rs
@@ -31,8 +31,6 @@ use rmcp::{
serde::de::DeserializeOwned,
serde_json::{self, json},
};
-use secrecy::ExposeSecret;
-
async fn invoke<T: DeserializeOwned>(
client: &McpClient,
method: &str,
@@ -513,7 +511,7 @@ async fn should_create_personal_access_token(harness:
&TestHarness) {
)
.await;
- assert!(!token.token.expose_secret().is_empty());
+ assert!(!token.token.is_empty());
}
#[iggy_harness(server(mcp), seed = seeds::mcp_standard)]
diff --git a/core/integration/tests/server/scenarios/authentication_scenario.rs
b/core/integration/tests/server/scenarios/authentication_scenario.rs
index f7b08b520..4dc440c4f 100644
--- a/core/integration/tests/server/scenarios/authentication_scenario.rs
+++ b/core/integration/tests/server/scenarios/authentication_scenario.rs
@@ -30,7 +30,6 @@ use crate::server::scenarios::create_client;
use bytes::Bytes;
use iggy::prelude::*;
use integration::harness::{TestHarness, login_root};
-use secrecy::ExposeSecret;
use server::binary::command::ServerCommand;
use strum::IntoEnumIterator;
@@ -91,7 +90,7 @@ pub async fn run(harness: &TestHarness) {
);
let identity = client
- .login_with_personal_access_token(raw_pat.token.expose_secret())
+ .login_with_personal_access_token(&raw_pat.token)
.await
.expect("PAT login should work");
assert_eq!(identity.user_id, 0, "PAT should authenticate as root");
diff --git
a/core/integration/tests/server/scenarios/cross_protocol_pat_scenario.rs
b/core/integration/tests/server/scenarios/cross_protocol_pat_scenario.rs
index 130c557a6..bedfcfe67 100644
--- a/core/integration/tests/server/scenarios/cross_protocol_pat_scenario.rs
+++ b/core/integration/tests/server/scenarios/cross_protocol_pat_scenario.rs
@@ -24,7 +24,6 @@ use iggy::prelude::*;
use iggy_common::TransportProtocol;
use integration::harness::TestHarness;
use integration::iggy_harness;
-use secrecy::ExposeSecret;
const PAT_NAME: &str = "cross-protocol-test-pat";
const TCP_CLIENT_COUNT: usize = 20;
@@ -49,7 +48,7 @@ pub async fn
should_see_pat_created_via_http_when_listing_via_tcp(harness: &Test
.await
.expect("Failed to create PAT via HTTP");
- assert!(!created_pat.token.expose_secret().is_empty());
+ assert!(!created_pat.token.is_empty());
let http_pats = http_client
.get_personal_access_tokens()
@@ -108,7 +107,7 @@ pub async fn
should_see_pat_created_via_tcp_when_listing_via_http(harness: &Test
.await
.expect("Failed to create PAT via TCP");
- assert!(!created_pat.token.expose_secret().is_empty());
+ assert!(!created_pat.token.is_empty());
let http_client = create_root_client(harness,
TransportProtocol::Http).await;
diff --git a/core/integration/tests/server/scenarios/user_scenario.rs
b/core/integration/tests/server/scenarios/user_scenario.rs
index d1904d6c1..f582711b3 100644
--- a/core/integration/tests/server/scenarios/user_scenario.rs
+++ b/core/integration/tests/server/scenarios/user_scenario.rs
@@ -24,7 +24,6 @@ use iggy::prelude::defaults::DEFAULT_ROOT_USERNAME;
use iggy::prelude::{GlobalPermissions, Permissions};
use iggy::prelude::{PersonalAccessTokenClient, SEC_IN_MICRO, SystemClient,
UserClient};
use integration::harness::{TestHarness, assert_clean_system, login_root};
-use secrecy::ExposeSecret;
pub async fn run(harness: &TestHarness) {
let client = create_client(harness).await;
@@ -147,14 +146,14 @@ pub async fn run(harness: &TestHarness) {
.await
.unwrap();
- assert!(!raw_pat1.token.expose_secret().is_empty());
+ assert!(!raw_pat1.token.is_empty());
let raw_pat2 = client
.create_personal_access_token(pat_name2,
PersonalAccessTokenExpiry::NeverExpire)
.await
.unwrap();
- assert!(!raw_pat2.token.expose_secret().is_empty());
+ assert!(!raw_pat2.token.is_empty());
// 14. Get personal access tokens and verify that the token is there
let personal_access_tokens =
client.get_personal_access_tokens().await.unwrap();
@@ -165,14 +164,14 @@ pub async fn run(harness: &TestHarness) {
// 16. Login with the personal access tokens
let identity_info = client
- .login_with_personal_access_token(raw_pat1.token.expose_secret())
+ .login_with_personal_access_token(&raw_pat1.token)
.await
.unwrap();
assert_eq!(identity_info.user_id, 1);
let identity_info = client
- .login_with_personal_access_token(raw_pat2.token.expose_secret())
+ .login_with_personal_access_token(&raw_pat2.token)
.await
.unwrap();
diff --git a/core/sdk/Cargo.toml b/core/sdk/Cargo.toml
index ab8953b24..cbd405065 100644
--- a/core/sdk/Cargo.toml
+++ b/core/sdk/Cargo.toml
@@ -17,7 +17,7 @@
[package]
name = "iggy"
-version = "0.9.3-edge.1"
+version = "0.9.4-edge.1"
description = "Iggy is the persistent message streaming platform written in
Rust, supporting QUIC, TCP and HTTP transport protocols, capable of processing
millions of messages per second."
edition = "2024"
license = "Apache-2.0"
diff --git a/core/sdk/src/http/http_client.rs b/core/sdk/src/http/http_client.rs
index e2142f4f5..a6ddcaa65 100644
--- a/core/sdk/src/http/http_client.rs
+++ b/core/sdk/src/http/http_client.rs
@@ -29,7 +29,6 @@ use reqwest::{Response, StatusCode, Url};
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use reqwest_retry::{RetryTransientMiddleware, policies::ExponentialBackoff};
use reqwest_tracing::{SpanBackendWithUrl, TracingMiddleware};
-use secrecy::ExposeSecret;
use serde::Serialize;
use std::ops::Deref;
use std::str::FromStr;
@@ -250,7 +249,7 @@ impl HttpTransport for HttpClient {
}
let access_token = identity.access_token.as_ref().unwrap();
-
self.set_access_token(Some(access_token.token.expose_secret().to_owned()))
+ self.set_access_token(Some(access_token.token.clone()))
.await;
Ok(())
}
diff --git a/core/server/src/http/mapper.rs b/core/server/src/http/mapper.rs
index 04bc16396..879d98338 100644
--- a/core/server/src/http/mapper.rs
+++ b/core/server/src/http/mapper.rs
@@ -24,7 +24,6 @@ use iggy_common::PersonalAccessToken;
use iggy_common::{ConsumerGroupDetails, ConsumerGroupInfo,
ConsumerGroupMember, IggyByteSize};
use iggy_common::{IdentityInfo, PersonalAccessTokenInfo, TokenInfo,
TopicDetails};
use iggy_common::{UserInfo, UserInfoDetails};
-use secrecy::SecretString;
pub fn map_user(user: &User) -> UserInfoDetails {
UserInfoDetails {
@@ -106,7 +105,7 @@ pub fn map_generated_access_token_to_identity_info(token:
GeneratedToken) -> Ide
IdentityInfo {
user_id: token.user_id,
access_token: Some(TokenInfo {
- token: SecretString::from(token.access_token),
+ token: token.access_token,
expiry: token.access_token_expiry,
}),
}
diff --git a/core/server/src/http/personal_access_tokens.rs
b/core/server/src/http/personal_access_tokens.rs
index 1ff235ddb..fbdf7ab3f 100644
--- a/core/server/src/http/personal_access_tokens.rs
+++ b/core/server/src/http/personal_access_tokens.rs
@@ -36,7 +36,7 @@ use
iggy_common::create_personal_access_token::CreatePersonalAccessToken;
use iggy_common::delete_personal_access_token::DeletePersonalAccessToken;
use
iggy_common::login_with_personal_access_token::LoginWithPersonalAccessToken;
use iggy_common::{IggyError, RawPersonalAccessToken};
-use secrecy::{ExposeSecret, SecretString};
+use secrecy::ExposeSecret;
use std::sync::Arc;
use tracing::instrument;
@@ -93,9 +93,7 @@ async fn create_personal_access_token(
match state.shard.send_to_control_plane(request).await? {
ShardResponse::CreatePersonalAccessTokenResponse(_, token) => {
- Ok(Json(RawPersonalAccessToken {
- token: SecretString::from(token),
- }))
+ Ok(Json(RawPersonalAccessToken { token }))
}
ShardResponse::ErrorResponse(err) => Err(err.into()),
_ => unreachable!("Expected CreatePersonalAccessTokenResponse"),
diff --git a/examples/python/uv.lock b/examples/python/uv.lock
index 9151f6941..d04b1d2bc 100644
--- a/examples/python/uv.lock
+++ b/examples/python/uv.lock
@@ -4,7 +4,7 @@ requires-python = ">=3.10"
[[package]]
name = "apache-iggy"
-version = "0.7.3.dev1"
+version = "0.7.4.dev1"
source = { directory = "../../foreign/python" }
[package.metadata]
diff --git a/foreign/python/Cargo.toml b/foreign/python/Cargo.toml
index 827b38c3a..9ce0d5265 100644
--- a/foreign/python/Cargo.toml
+++ b/foreign/python/Cargo.toml
@@ -28,7 +28,7 @@ repository = "https://github.com/apache/iggy"
[dependencies]
bytes = "1.11.1"
futures = "0.3.32"
-iggy = { path = "../../core/sdk", version = "0.9.3-edge.1" }
+iggy = { path = "../../core/sdk", version = "0.9.4-edge.1" }
pyo3 = "0.28.2"
pyo3-async-runtimes = { version = "0.28.0", features = [
"attributes",