risingwavelabs/risingwave · warning
normalized URL isn't the same as the original one
Error message
normalized URL isn't the same as the original one
What it means
The meta dashboard HTTP proxy builds a URL on https://raw.githubusercontent.com/risingwavelabs/risingwave/dashboard-artifact plus the request path, parses it with url::Url, and then asserts that re-serializing the parsed URL yields the exact original string. If Url normalization changed the string (encoding, dot-segment removal, etc.), it refuses the request with this anyhow error as a safety check against unexpected/malicious paths.
Source
Thrown at src/meta/dashboard/src/proxy.rs:80
uri: Uri,
cache: Arc<Mutex<HashMap<String, CachedResponse>>>,
) -> anyhow::Result<Response> {
let mut path = uri.path().to_owned();
if path.ends_with('/') {
path += "index.html";
}
if let Some(resp) = cache.lock().unwrap().get(&path) {
return Ok(resp.clone().into_response());
}
let url_str = format!(
"https://raw.githubusercontent.com/risingwavelabs/risingwave/dashboard-artifact{}",
path
);
let url = Url::parse(&url_str)?;
if url.to_string() != url_str {
return Err(anyhow!("normalized URL isn't the same as the original one"));
}
tracing::info!("dashboard service: proxying {}", url);
let content = reqwest::get(url.clone()).await?;
let resp = CachedResponse {
code: content.status(),
headers: content.headers().clone(),
body: content.bytes().await?,
uri: url,
};
cache.lock().unwrap().insert(path, resp.clone());
Ok(resp.into_response())
}
View on GitHub (pinned to 6469eb736d)
Solutions
- Check the requested path for invalid characters, `..` segments, or percent-encoding that triggers normalization; request a plain artifact path instead.
- Verify the dashboard/proxy client URL matches the artifact file names published in the risingwave repo's dashboard-artifact branch.
- If the path is legitimate but normalized (e.g. contains spaces), URL-encode it client-side in a way that survives round-tripping, or fix the artifact file name.
- If you hit this as an internal invariant, ensure the `url` crate version is consistent (update Cargo.lock) and compare url.to_string() with the constructed string when debugging.
Example fix
// before: path with characters that get normalized GET /dashboard/v2.0/some dir/index.html // after: properly percent-encoded, round-trip-stable path GET /dashboard/v2.0/some%20dir/index.html
Defensive patterns
Strategy: validation
Validate before calling
// Client side: send only round-trip-stable, percent-encoded paths
const path = encodeURI(rawPath).replaceAll('..', '');
if (!path.startsWith('/') || path.includes('//')) {
throw new Error('invalid artifact path: ' + rawPath);
}
fetch(`/probe/artifact${path}`); Type guard
fn is_round_trip_stable(path: &str, base: &str) -> bool {
match Url::parse(&format!("{}{}", base, path)) {
Ok(url) => url.as_str() == &format!("{}{}", base, path),
Err(_) => false,
}
} Try / catch
// Rust: handle the anyhow error from the proxy handler
match proxy(path).await {
Ok(resp) => resp,
Err(e) if e.to_string().contains("normalized URL") => {
StatusCode::BAD_REQUEST // reject malformed artifact paths
}
Err(e) => { tracing::error!(error = ?e, "proxy failed"); StatusCode::INTERNAL_SERVER_ERROR }
} Prevention
- Only request artifact paths with plain ASCII file names as published in the dashboard-artifact branch.
- Percent-encode paths client-side and avoid `..`, `//`, and trailing slashes.
- Treat this error as a signal of a malicious/invalid probe path — reject with 400, never retry as-is.
- Pin the `url` crate version so parse/serialize normalization behavior stays consistent.
When it happens
Trigger: A GET to the dashboard proxy endpoint whose path, after being appended to the base URL, normalizes differently — e.g. paths containing characters needing percent-encoding, `..` dot segments, empty segments, or a path that escapes the artifact prefix.
Common situations: Requesting a dashboard artifact file whose name contains special characters or encoded sequences; crafted URLs probing path traversal; a version of the `url` crate with different normalization rules; proxying to a branch/artifact path that no longer exists and redirects/normalizes oddly.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- Can't get be host from url
- Url parse error for S3 deltalake location (wrapped error)
- Url parse error for local deltalake location (wrapped error)
- Url parse error for GCS deltalake location (wrapped error)
- path should start with 's3://','s3a://'(s3) ,gs://(gcs) or f
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/3e4d941df102a674.
Report an issue: GitHub.