commit 1f8e5728f4815961d7dac0545c2b03464991b592
parent fbe3e9da789b48f37c733acc00582d2d8b220373
Author: triesap <tyson@radroots.org>
Date: Mon, 10 Aug 2026 04:25:17 +0000
security: remove the vulnerable redundant transport edge
- route profile relay fetches through the already-pinned rust-nostr SDK
- preserve domain-owned relay policy and bounded partial-success semantics
- remove the redundant Radroots transport dependency chain and stale lock entries
- verify both build lanes, relay integration coverage, licenses, and fresh advisories
Diffstat:
4 files changed, 65 insertions(+), 386 deletions(-)
diff --git a/core/Cargo.lock b/core/Cargo.lock
@@ -1094,9 +1094,9 @@ dependencies = [
"harvestcircle_product",
"harvestcircle_runtime",
"harvestcircle_storage",
- "nostr 0.44.1",
+ "nostr",
"nostr-relay-builder",
- "nostr-sdk 0.44.0",
+ "nostr-sdk",
"quote",
"sha2",
"syn 2.0.119",
@@ -1111,12 +1111,10 @@ version = "0.1.0-alpha"
dependencies = [
"harvestcircle_application",
"harvestcircle_domain",
- "nostr 0.44.1",
+ "nostr",
"nostr-relay-builder",
- "nostr-sdk 0.44.0",
+ "nostr-sdk",
"radroots_identity",
- "radroots_transport",
- "radroots_transport_nostr",
"tokio",
]
@@ -1135,9 +1133,9 @@ dependencies = [
"harvestcircle_domain",
"harvestcircle_nostr",
"harvestcircle_storage",
- "nostr 0.44.1",
+ "nostr",
"nostr-relay-builder",
- "nostr-sdk 0.44.0",
+ "nostr-sdk",
"tempfile",
"tokio",
"uuid",
@@ -1347,16 +1345,6 @@ dependencies = [
[[package]]
name = "idna"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
-dependencies = [
- "unicode-bidi",
- "unicode-normalization",
-]
-
-[[package]]
-name = "idna"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
@@ -1428,12 +1416,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
-name = "jiff-tzdb"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e"
-
-[[package]]
name = "js-sys"
version = "0.3.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1526,12 +1508,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
[[package]]
-name = "mediatype"
-version = "0.21.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "120fa187be19d9962f0926633453784691731018a2bf936ddb4e29101b79c4a7"
-
-[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1612,67 +1588,21 @@ dependencies = [
]
[[package]]
-name = "nostr"
-version = "0.44.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40ff7b77ef428b40aa2834a6acbae38a0e104c98b306208ca4b87a420d579a4b"
-dependencies = [
- "aes",
- "base64",
- "bech32",
- "bip39",
- "bitcoin_hashes",
- "cbc",
- "chacha20",
- "chacha20poly1305",
- "getrandom 0.2.17",
- "hex",
- "instant",
- "scrypt",
- "secp256k1",
- "serde",
- "serde_json",
- "unicode-normalization",
- "url",
- "url-fork",
-]
-
-[[package]]
-name = "nostr-database"
-version = "0.44.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7462c9d8ae5ef6a28d66a192d399ad2530f1f2130b13186296dbb11bdef5b3d1"
-dependencies = [
- "lru",
- "nostr 0.44.8",
- "tokio",
-]
-
-[[package]]
name = "nostr-database"
version = "0.44.0"
source = "git+https://github.com/rust-nostr/nostr.git?rev=5bba5163eb77107f82c4a8262cf29d7f33a73219#5bba5163eb77107f82c4a8262cf29d7f33a73219"
dependencies = [
"lru",
- "nostr 0.44.1",
+ "nostr",
"tokio",
]
[[package]]
name = "nostr-gossip"
version = "0.44.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ade30de16869618919c6b5efc8258f47b654a98b51541eb77f85e8ec5e3c83a6"
-dependencies = [
- "nostr 0.44.8",
-]
-
-[[package]]
-name = "nostr-gossip"
-version = "0.44.0"
source = "git+https://github.com/rust-nostr/nostr.git?rev=5bba5163eb77107f82c4a8262cf29d7f33a73219#5bba5163eb77107f82c4a8262cf29d7f33a73219"
dependencies = [
- "nostr 0.44.1",
+ "nostr",
]
[[package]]
@@ -1685,8 +1615,8 @@ dependencies = [
"atomic-destructor",
"hex",
"negentropy",
- "nostr 0.44.1",
- "nostr-database 0.44.0 (git+https://github.com/rust-nostr/nostr.git?rev=5bba5163eb77107f82c4a8262cf29d7f33a73219)",
+ "nostr",
+ "nostr-database",
"tokio",
"tracing",
]
@@ -1702,26 +1632,8 @@ dependencies = [
"hex",
"lru",
"negentropy",
- "nostr 0.44.1",
- "nostr-database 0.44.0 (git+https://github.com/rust-nostr/nostr.git?rev=5bba5163eb77107f82c4a8262cf29d7f33a73219)",
- "tokio",
- "tracing",
-]
-
-[[package]]
-name = "nostr-relay-pool"
-version = "0.44.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c85c54d6ca9aae4ae2bf19a7663ba9db5f45f783f1d24aff55f006386b8b99a1"
-dependencies = [
- "async-utility",
- "async-wsocket",
- "atomic-destructor",
- "hex",
- "lru",
- "negentropy",
- "nostr 0.44.8",
- "nostr-database 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "nostr",
+ "nostr-database",
"tokio",
"tracing",
]
@@ -1732,25 +1644,10 @@ version = "0.44.0"
source = "git+https://github.com/rust-nostr/nostr.git?rev=5bba5163eb77107f82c4a8262cf29d7f33a73219#5bba5163eb77107f82c4a8262cf29d7f33a73219"
dependencies = [
"async-utility",
- "nostr 0.44.1",
- "nostr-database 0.44.0 (git+https://github.com/rust-nostr/nostr.git?rev=5bba5163eb77107f82c4a8262cf29d7f33a73219)",
- "nostr-gossip 0.44.0 (git+https://github.com/rust-nostr/nostr.git?rev=5bba5163eb77107f82c4a8262cf29d7f33a73219)",
- "nostr-relay-pool 0.44.0",
- "tokio",
- "tracing",
-]
-
-[[package]]
-name = "nostr-sdk"
-version = "0.44.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "471732576710e779b64f04c55e3f8b5292f865fea228436daf19694f0bf70393"
-dependencies = [
- "async-utility",
- "nostr 0.44.8",
- "nostr-database 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "nostr-gossip 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "nostr-relay-pool 0.44.3",
+ "nostr",
+ "nostr-database",
+ "nostr-gossip",
+ "nostr-relay-pool",
"tokio",
"tracing",
]
@@ -2021,123 +1918,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
-name = "radroots_blossom"
-version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib?rev=09065a610d95e57acdc895a14c07580fa099e7c3#09065a610d95e57acdc895a14c07580fa099e7c3"
-dependencies = [
- "mediatype",
- "serde",
- "sha2",
- "unicode-general-category",
- "url",
-]
-
-[[package]]
-name = "radroots_core"
-version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib?rev=09065a610d95e57acdc895a14c07580fa099e7c3#09065a610d95e57acdc895a14c07580fa099e7c3"
-dependencies = [
- "rust_decimal",
- "serde",
-]
-
-[[package]]
-name = "radroots_event"
-version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib?rev=09065a610d95e57acdc895a14c07580fa099e7c3#09065a610d95e57acdc895a14c07580fa099e7c3"
-dependencies = [
- "hex",
- "jiff-tzdb",
- "radroots_blossom",
- "radroots_core",
- "radroots_identity",
- "radroots_protocol",
- "serde",
- "serde_json",
- "sha2",
- "unicode-general-category",
- "url",
-]
-
-[[package]]
-name = "radroots_event_codec"
-version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib?rev=09065a610d95e57acdc895a14c07580fa099e7c3#09065a610d95e57acdc895a14c07580fa099e7c3"
-dependencies = [
- "hex",
- "radroots_blossom",
- "radroots_core",
- "radroots_event",
- "radroots_identity",
- "radroots_protocol",
- "secp256k1",
- "serde",
- "serde_json",
- "sha2",
-]
-
-[[package]]
name = "radroots_identity"
version = "0.1.0-alpha"
source = "git+https://github.com/radrootslabs/lib?rev=09065a610d95e57acdc895a14c07580fa099e7c3#09065a610d95e57acdc895a14c07580fa099e7c3"
dependencies = [
"k256",
- "serde",
- "thiserror 2.0.20",
-]
-
-[[package]]
-name = "radroots_nostr"
-version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib?rev=09065a610d95e57acdc895a14c07580fa099e7c3#09065a610d95e57acdc895a14c07580fa099e7c3"
-dependencies = [
- "nostr 0.44.8",
- "radroots_event",
- "radroots_event_codec",
- "radroots_identity",
- "serde",
- "serde_json",
"thiserror 2.0.20",
]
[[package]]
-name = "radroots_protocol"
-version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib?rev=09065a610d95e57acdc895a14c07580fa099e7c3#09065a610d95e57acdc895a14c07580fa099e7c3"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "radroots_transport"
-version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib?rev=09065a610d95e57acdc895a14c07580fa099e7c3#09065a610d95e57acdc895a14c07580fa099e7c3"
-dependencies = [
- "radroots_event",
- "radroots_identity",
- "radroots_protocol",
- "sha2",
-]
-
-[[package]]
-name = "radroots_transport_nostr"
-version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib?rev=09065a610d95e57acdc895a14c07580fa099e7c3#09065a610d95e57acdc895a14c07580fa099e7c3"
-dependencies = [
- "async-wsocket",
- "nostr-relay-pool 0.44.3",
- "nostr-sdk 0.44.1",
- "radroots_event_codec",
- "radroots_nostr",
- "radroots_protocol",
- "radroots_transport",
- "serde_json",
- "tokio",
- "tokio-tungstenite",
- "url",
-]
-
-[[package]]
name = "rand"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2317,18 +2106,6 @@ dependencies = [
]
[[package]]
-name = "rust_decimal"
-version = "1.42.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be2a24f50780bc85f09cc6ac299bdf1424302742d77221106859c9d8b102126a"
-dependencies = [
- "arrayvec",
- "num-traits",
- "serde",
- "wasm-bindgen",
-]
-
-[[package]]
name = "rustc-demangle"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3044,18 +2821,6 @@ dependencies = [
]
[[package]]
-name = "unicode-bidi"
-version = "0.3.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
-
-[[package]]
-name = "unicode-general-category"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f"
-
-[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3214,25 +2979,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
dependencies = [
"form_urlencoded",
- "idna 1.1.0",
+ "idna",
"percent-encoding",
"serde",
"serde_derive",
]
[[package]]
-name = "url-fork"
-version = "3.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fa3323c39b8e786154d3000b70ae9af0e9bd746c9791456da0d4a1f68ad89d6"
-dependencies = [
- "form_urlencoded",
- "idna 0.5.0",
- "percent-encoding",
- "serde",
-]
-
-[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3302,7 +3055,6 @@ dependencies = [
"cfg-if",
"once_cell",
"rustversion",
- "serde",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
diff --git a/core/Cargo.toml b/core/Cargo.toml
@@ -42,8 +42,6 @@ harvestcircle_runtime = { path = "crates/harvestcircle_runtime", version = "=0.1
harvestcircle_storage = { path = "crates/harvestcircle_storage", version = "=0.1.0-alpha" }
harvestcircle_uniffi_bindgen = { path = "crates/harvestcircle_uniffi_bindgen", version = "=0.1.0-alpha" }
radroots_identity = { git = "https://github.com/radrootslabs/lib", rev = "09065a610d95e57acdc895a14c07580fa099e7c3", version = "=0.1.0-alpha", default-features = false }
-radroots_transport = { git = "https://github.com/radrootslabs/lib", rev = "09065a610d95e57acdc895a14c07580fa099e7c3", version = "=0.1.0-alpha", default-features = false }
-radroots_transport_nostr = { git = "https://github.com/radrootslabs/lib", rev = "09065a610d95e57acdc895a14c07580fa099e7c3", version = "=0.1.0-alpha", default-features = false }
getrandom = { version = "0.2", default-features = false }
hmac = { version = "0.12", default-features = false }
quote = { version = "1" }
diff --git a/core/crates/harvestcircle_nostr/Cargo.toml b/core/crates/harvestcircle_nostr/Cargo.toml
@@ -17,8 +17,6 @@ nostr-sdk = { git = "https://github.com/rust-nostr/nostr.git", rev = "5bba5163eb
harvestcircle_application.workspace = true
harvestcircle_domain.workspace = true
radroots_identity.workspace = true
-radroots_transport.workspace = true
-radroots_transport_nostr.workspace = true
tokio = { version = "=1.47.1", features = ["sync", "time"] }
[dev-dependencies]
diff --git a/core/crates/harvestcircle_nostr/src/client.rs b/core/crates/harvestcircle_nostr/src/client.rs
@@ -1,15 +1,10 @@
-use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
+use std::time::{Duration, Instant};
use harvestcircle_domain::{
- PublicKey, RelayDestinationPolicy, RelayUrl, SafeError, SafeErrorCode, SafeMessage,
- select_latest_kind0,
+ PublicKey, RelayUrl, SafeError, SafeErrorCode, SafeMessage, select_latest_kind0,
};
-use radroots_transport::{
- EventSource, FetchRequest, Target, TargetSet,
- outcome::FetchTargetState,
- source::{FetchBounds, FetchSelector},
-};
-use radroots_transport_nostr::{Config, NostrTransport, RelayUrlPolicy};
+use nostr::{Filter, JsonUtil, Kind, PublicKey as NostrPublicKey};
+use nostr_sdk::Client;
use harvestcircle_application::{
BoxFuture, MAX_CONFIGURED_RELAYS, NostrClient, ProfileFetchResult,
@@ -43,75 +38,46 @@ impl NostrClient for SdkNostrClient {
return Err(invalid_relay_configuration());
}
- let author = public_key.canonical();
+ let author = NostrPublicKey::from_slice(public_key.as_bytes())
+ .map_err(|_| invalid_relay_configuration())?;
let deadline = deadline.min(Instant::now() + self.timeout);
let mut candidates = Vec::new();
let mut successful_relays = 0usize;
- for policy in [
- RelayDestinationPolicy::Public,
- RelayDestinationPolicy::Local,
- RelayDestinationPolicy::PrivateNetwork,
- ] {
- let policy_relays = relays
- .iter()
- .filter(|relay| relay.policy() == policy)
- .collect::<Vec<_>>();
- if policy_relays.is_empty() {
- continue;
+ let filter = Filter::new()
+ .author(author)
+ .kind(Kind::Metadata)
+ .limit(MAX_PROFILE_EVENTS_PER_RELAY);
+ for relay in relays {
+ let remaining = deadline.saturating_duration_since(Instant::now());
+ if remaining.is_zero() {
+ break;
}
- let config = Config::new(
- canonical_policy(policy),
- policy_relays.iter().map(|relay| relay.as_str()),
- )
- .and_then(|config| {
- let timeout_ms =
- timeout_millis(deadline.saturating_duration_since(Instant::now()));
- config.with_timeouts(timeout_ms, timeout_ms, timeout_ms)
+ let client = Client::default();
+ let result = match tokio::time::timeout_at(deadline.into(), async {
+ client
+ .add_relay(relay.as_str())
+ .await
+ .map_err(|_| relay_connection_failed())?;
+ client
+ .try_connect_relay(relay.as_str(), remaining)
+ .await
+ .map_err(|_| relay_connection_failed())?;
+ client
+ .fetch_events_from([relay.as_str()], filter.clone(), remaining)
+ .await
+ .map_err(|_| relay_connection_failed())
})
- .map_err(|_| invalid_relay_configuration())?;
- let targets = policy_relays
- .iter()
- .map(|relay| Target::nostr_relay(relay.as_str()))
- .collect::<Result<Vec<_>, _>>()
- .map_err(|_| invalid_relay_configuration())?;
- let request = FetchRequest::new(
- format!("harvestcircle-profile-{policy:?}"),
- TargetSet::new(targets).map_err(|_| invalid_relay_configuration())?,
- FetchBounds::new(
- MAX_PROFILE_EVENTS_PER_RELAY as u16,
- unix_deadline(deadline.saturating_duration_since(Instant::now()))?,
- )
- .map_err(|_| invalid_relay_configuration())?,
- )
- .map_err(|_| invalid_relay_configuration())?
- .with_selector(
- FetchSelector::all()
- .with_kinds(vec![0])
- .and_then(|selector| selector.with_authors(vec![author]))
- .map_err(|_| invalid_relay_configuration())?,
- );
- let page = tokio::time::timeout_at(
- deadline.into(),
- NostrTransport::new(config).fetch(request),
- )
.await
- .map_err(|_| relay_connection_failed())?
- .map_err(|_| relay_connection_failed())?;
- successful_relays += page
- .target_outcomes()
- .iter()
- .filter(|outcome| {
- matches!(
- outcome.state(),
- FetchTargetState::Complete | FetchTargetState::Partial
- )
- })
- .count();
- for observed in page.events() {
- candidates.push(crate::parse_verified_kind0(
- observed.event().raw_json(),
- public_key,
- )?);
+ {
+ Ok(result) => result,
+ Err(_) => Err(relay_connection_failed()),
+ };
+ client.shutdown().await;
+ if let Ok(events) = result {
+ successful_relays += 1;
+ for event in events {
+ candidates.push(crate::parse_verified_kind0(&event.as_json(), public_key)?);
+ }
}
}
if successful_relays == 0 {
@@ -127,33 +93,6 @@ impl NostrClient for SdkNostrClient {
}
}
-fn unix_deadline(timeout: Duration) -> Result<u64, SafeError> {
- let now = SystemTime::now()
- .duration_since(UNIX_EPOCH)
- .map_err(|_| relay_connection_failed())?;
- let deadline = now
- .checked_add(timeout)
- .ok_or_else(relay_connection_failed)?;
- u64::try_from(deadline.as_millis())
- .ok()
- .filter(|deadline| *deadline > 0)
- .ok_or_else(relay_connection_failed)
-}
-
-fn timeout_millis(timeout: Duration) -> u64 {
- u64::try_from(timeout.as_millis())
- .unwrap_or(u64::MAX)
- .clamp(1, 120_000)
-}
-
-const fn canonical_policy(policy: RelayDestinationPolicy) -> RelayUrlPolicy {
- match policy {
- RelayDestinationPolicy::Public => RelayUrlPolicy::Public,
- RelayDestinationPolicy::Local => RelayUrlPolicy::Local,
- RelayDestinationPolicy::PrivateNetwork => RelayUrlPolicy::PrivateNetwork,
- }
-}
-
const fn invalid_relay_configuration() -> SafeError {
SafeError::new(
SafeErrorCode::InvalidRelayConfiguration,
@@ -308,25 +247,17 @@ mod tests {
}
#[test]
- fn harvestcircle_policy_maps_exactly_to_the_canonical_transport_policy() {
- assert_eq!(
- super::canonical_policy(RelayDestinationPolicy::Public),
- radroots_transport_nostr::RelayUrlPolicy::Public
- );
- assert_eq!(
- super::canonical_policy(RelayDestinationPolicy::Local),
- radroots_transport_nostr::RelayUrlPolicy::Local
- );
- assert_eq!(
- super::canonical_policy(RelayDestinationPolicy::PrivateNetwork),
- radroots_transport_nostr::RelayUrlPolicy::PrivateNetwork
- );
- assert_eq!(super::timeout_millis(Duration::ZERO), 1);
- assert_eq!(
- super::timeout_millis(Duration::from_secs(1_000_000)),
- 120_000
+ fn harvestcircle_relay_policy_remains_domain_owned_and_fail_closed() {
+ assert!(RelayUrl::parse("wss://relay.example", RelayDestinationPolicy::Public).is_ok());
+ assert!(RelayUrl::parse("ws://127.0.0.1:7777", RelayDestinationPolicy::Local).is_ok());
+ assert!(
+ RelayUrl::parse(
+ "wss://10.0.0.1:7777",
+ RelayDestinationPolicy::PrivateNetwork
+ )
+ .is_ok()
);
- assert!(super::unix_deadline(Duration::from_secs(1)).is_ok());
+ assert!(RelayUrl::parse("ws://relay.example", RelayDestinationPolicy::Public).is_err());
assert_eq!(
super::invalid_relay_configuration().code(),
SafeErrorCode::InvalidRelayConfiguration