Skip to content

Commit ba59091

Browse files
committed
fix(gateway): validate peer synchronization URLs
1 parent 7241c96 commit ba59091

1 file changed

Lines changed: 41 additions & 3 deletions

File tree

dstack/gateway/src/kv/mod.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,20 @@ mod https_client;
3232
mod sync_service;
3333

3434
pub use https_client::{AppIdValidator, HttpsClientConfig};
35-
pub use sync_service::{fetch_peers_from_bootnode, WaveKvSyncService};
35+
pub use sync_service::{WaveKvSyncService, fetch_peers_from_bootnode};
3636
use tracing::warn;
3737

3838
use std::{collections::BTreeMap, net::Ipv4Addr, path::Path, time::Duration};
3939

4040
use aes_gcm::{
41-
aead::{Aead, Payload},
4241
Aes256Gcm, KeyInit, Nonce,
42+
aead::{Aead, Payload},
4343
};
4444
use anyhow::{Context, Result};
4545
use rand::RngCore;
4646
use serde::{Deserialize, Serialize};
4747
use tokio::sync::watch;
48-
use wavekv::{node::NodeState, types::NodeId, Node};
48+
use wavekv::{Node, node::NodeState, types::NodeId};
4949

5050
/// Per-port flags applied by the gateway when proxying to a CVM port.
5151
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
@@ -698,6 +698,8 @@ impl KvStore {
698698
/// This stores the URL in KvStore (for address lookup) and also adds the node
699699
/// to the wavekv peer list (so SyncManager knows to sync with it).
700700
pub fn register_peer_url(&self, node_id: NodeId, url: &str) -> Result<()> {
701+
validate_peer_url(url)?;
702+
701703
// Store URL in persistent KvStore
702704
self.persistent
703705
.write()
@@ -1102,3 +1104,39 @@ impl KvStore {
11021104
self.persistent.watch_prefix(keys::CERT_PREFIX)
11031105
}
11041106
}
1107+
1108+
fn validate_peer_url(url: &str) -> Result<()> {
1109+
let parsed = reqwest::Url::parse(url).context("invalid peer URL")?;
1110+
anyhow::ensure!(
1111+
matches!(parsed.scheme(), "http" | "https"),
1112+
"peer URL scheme must be http or https"
1113+
);
1114+
anyhow::ensure!(parsed.host_str().is_some(), "peer URL must include a host");
1115+
anyhow::ensure!(
1116+
parsed.username().is_empty() && parsed.password().is_none(),
1117+
"peer URL must not contain credentials"
1118+
);
1119+
Ok(())
1120+
}
1121+
1122+
#[cfg(test)]
1123+
mod peer_url_tests {
1124+
use super::validate_peer_url;
1125+
1126+
#[test]
1127+
fn accepts_http_sync_urls() {
1128+
assert!(validate_peer_url("https://gateway.example:8011/sync").is_ok());
1129+
assert!(validate_peer_url("http://127.0.0.1:8011").is_ok());
1130+
}
1131+
1132+
#[test]
1133+
fn rejects_malformed_or_unsafe_sync_urls() {
1134+
for url in [
1135+
"not-a-sync-url",
1136+
"ftp://gateway.example/sync",
1137+
"https://user:secret@gateway.example/sync",
1138+
] {
1139+
assert!(validate_peer_url(url).is_err(), "accepted {url}");
1140+
}
1141+
}
1142+
}

0 commit comments

Comments
 (0)