Picks up @Timtech4u's PR #33, which has been waiting since April. The design (CLI flag + threading through BrowserContext + http client) is theirs and is correct; this is a clean rewrite on top of current main because repeated rebase rounds had been reverting unrelated work that landed in the meantime. Issue #4 blocked all loopback / RFC1918 / link-local fetches to prevent SSRF. The only escape was OBSCURA_ALLOW_PRIVATE_NETWORK=1, which is process-wide and clunky in pipelines. Multiple users (@Timtech4u in the original PR, then @duartemvix and @necrogami in the comment thread) reported they cannot use obscura for development against http://localhost:N without it. Adds --allow-private-network as a global CLI flag, works on fetch, serve, and scrape. Threads through BrowserContext::with_storage_and_network and the http client's validate_url. CdpContext gets a new_full builder and obscura_cdp::start_with_full_serve_options entry point so 'obscura serve --allow-private-network' lets Puppeteer/Playwright navigate to localhost too. Default remains blocked. Backward compat: the env var still works (OR'd with the flag inside validate_url). All existing constructors and entry points unchanged. Verified end-to-end against a local http.server on 127.0.0.1: fetch and CDP Page.navigate both blocked by default, both succeed with the flag. Public URLs unaffected. Integration suite 28/28, full workspace cargo test all green.
This commit is contained in:
1 parent
1a1253118f
commit
e7bb0c676d
7 files changed
+140
-40
No files matched your search
@@ -72,8 +72,24 @@ pub enum ResourceType {
|
||||
pub type RequestCallback = Arc<dyn Fn(&RequestInfo) + Send + Sync>;
|
||||
pub type ResponseCallback = Arc<dyn Fn(&RequestInfo, &Response) + Send + Sync>;
|
||||
|
||||
fn validate_url(url: &Url) -> Result<(), ObscuraNetError> {
|
||||
let allow_private_network = std::env::var_os("OBSCURA_ALLOW_PRIVATE_NETWORK").is_some();
|
||||
/// Process-wide opt-in via env var. Older flow that issue #4 introduced. The
|
||||
/// new `--allow-private-network` CLI flag (issue #33) sets a per-client field
|
||||
/// that is OR'd with this so existing scripts and Docker setups that pin the
|
||||
/// env var keep working unchanged.
|
||||
pub fn env_allows_private_network() -> bool {
|
||||
matches!(
|
||||
std::env::var("OBSCURA_ALLOW_PRIVATE_NETWORK")
|
||||
.ok()
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.map(str::to_ascii_lowercase)
|
||||
.as_deref(),
|
||||
Some("1") | Some("true") | Some("yes") | Some("on")
|
||||
)
|
||||
}
|
||||
|
||||
fn validate_url(url: &Url, allow_private_network: bool) -> Result<(), ObscuraNetError> {
|
||||
let allow_private_network = allow_private_network || env_allows_private_network();
|
||||
let scheme = url.scheme();
|
||||
if scheme != "http" && scheme != "https" && scheme != "file" {
|
||||
return Err(ObscuraNetError::Network(format!(
|
||||
@@ -175,6 +191,10 @@ pub struct ObscuraHttpClient {
|
||||
pub timeout: Duration,
|
||||
pub in_flight: Arc<std::sync::atomic::AtomicU32>,
|
||||
pub block_trackers: bool,
|
||||
/// When true, `validate_url` lets localhost / RFC1918 / link-local addresses
|
||||
/// through in addition to the `OBSCURA_ALLOW_PRIVATE_NETWORK` env var.
|
||||
/// Set via `--allow-private-network` on the CLI (issue #33).
|
||||
pub allow_private_network: bool,
|
||||
}
|
||||
|
||||
impl ObscuraHttpClient {
|
||||
@@ -187,6 +207,14 @@ impl ObscuraHttpClient {
|
||||
}
|
||||
|
||||
pub fn with_options(cookie_jar: Arc<CookieJar>, proxy_url: Option<&str>) -> Self {
|
||||
Self::with_full_options(cookie_jar, proxy_url, false)
|
||||
}
|
||||
|
||||
pub fn with_full_options(
|
||||
cookie_jar: Arc<CookieJar>,
|
||||
proxy_url: Option<&str>,
|
||||
allow_private_network: bool,
|
||||
) -> Self {
|
||||
ObscuraHttpClient {
|
||||
client: tokio::sync::OnceCell::new(),
|
||||
proxy_url: proxy_url.map(|s| s.to_string()),
|
||||
@@ -201,6 +229,7 @@ impl ObscuraHttpClient {
|
||||
in_flight: Arc::new(std::sync::atomic::AtomicU32::new(0)),
|
||||
timeout: Duration::from_secs(30),
|
||||
block_trackers: false,
|
||||
allow_private_network,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +273,7 @@ impl ObscuraHttpClient {
|
||||
url: &Url,
|
||||
initial_body: Option<Vec<u8>>,
|
||||
) -> Result<Response, ObscuraNetError> {
|
||||
validate_url(url)?;
|
||||
validate_url(url, self.allow_private_network)?;
|
||||
|
||||
if url.scheme() == "file" {
|
||||
return fetch_file_url(url).await;
|
||||
@@ -427,7 +456,7 @@ impl ObscuraHttpClient {
|
||||
let next_url = current_url.join(location_str).map_err(|e| {
|
||||
ObscuraNetError::Network(format!("Invalid redirect URL: {}", e))
|
||||
})?;
|
||||
validate_url(&next_url)?;
|
||||
validate_url(&next_url, self.allow_private_network)?;
|
||||
redirects.push(current_url.clone());
|
||||
current_url = next_url;
|
||||
if status == reqwest::StatusCode::MOVED_PERMANENTLY
|
||||
|
||||
@@ -7,7 +7,10 @@ pub mod blocklist;
|
||||
#[cfg(feature = "stealth")]
|
||||
pub mod wreq_client;
|
||||
|
||||
pub use client::{ObscuraHttpClient, ObscuraNetError, RequestInfo, ResourceType, Response};
|
||||
pub use client::{
|
||||
env_allows_private_network, ObscuraHttpClient, ObscuraNetError, RequestInfo, ResourceType,
|
||||
Response,
|
||||
};
|
||||
pub use cookies::{CookieInfo, CookieJar};
|
||||
pub use encoding::{decode_non_html, decode_response};
|
||||
pub use robots::RobotsCache;
|
||||
|
||||
Reference in new issue
Block a user