-
-
Notifications
You must be signed in to change notification settings - Fork 164
Fix/security OIDC open redirect #1653
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
93c98e0
1b39db8
0ec18bc
71c94d8
3301320
f0bec66
dd150d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,10 +54,7 @@ impl OpenidConfig { | |
| redirect_to: &str, | ||
| ) -> Result<DiscoveredClient, openid::error::Error> { | ||
| let redirect_uri = match self.origin { | ||
| Origin::Local { socket_addr, https } => { | ||
| let protocol = if https { "https" } else { "http" }; | ||
| url::Url::parse(&format!("{protocol}://{socket_addr}")).expect("valid url") | ||
| } | ||
| Origin::Local { socket_addr, https } => canonical_local_origin(&socket_addr, https), | ||
| Origin::Production(url) => url, | ||
| }; | ||
|
|
||
|
|
@@ -67,6 +64,37 @@ impl OpenidConfig { | |
| } | ||
| } | ||
|
|
||
| pub fn canonical_local_origin(socket_addr: &str, https: bool) -> Url { | ||
| let scheme = if https { "https" } else { "http" }; | ||
| let mut url = Url::parse(&format!("{scheme}://{socket_addr}")) | ||
| .unwrap_or_else(|_| Url::parse(&format!("{scheme}://localhost:8000")).unwrap()); | ||
| if matches!(url.host_str(), Some("0.0.0.0") | Some("::")) { | ||
| url.set_host(Some("localhost")).expect("localhost is valid"); | ||
| } | ||
| url | ||
| } | ||
|
Comment on lines
+67
to
+75
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Test url crate IPv6 parsing with various socket address formats
cat > /tmp/test_ipv6_url.rs << 'EOF'
use url::Url;
fn main() {
let test_cases = vec![
("http://[::1]:8000", "bracketed IPv6 loopback"),
("http://[::]:8000", "bracketed IPv6 wildcard"),
("http://:::8000", "unbracketed IPv6 wildcard with port"),
("http://::1:8000", "unbracketed IPv6 loopback with port"),
("http://0.0.0.0:8000", "IPv4 wildcard"),
];
for (input, desc) in test_cases {
match Url::parse(input) {
Ok(url) => {
println!("✓ {}: parsed", desc);
println!(" host_str: {:?}", url.host_str());
println!(" port: {:?}", url.port());
}
Err(e) => println!("✗ {}: parse error: {}", desc, e),
}
}
}
EOF
# Compile and run the test
rustc --edition 2021 /tmp/test_ipv6_url.rs -o /tmp/test_ipv6_url --extern url=$(fd -t f liburl.*\.rlib | head -1 | sed 's/.*\(\/.*\)/\1/') 2>/dev/null || \
(cd /tmp && cargo init --name test_ipv6 && \
echo 'url = "2.4.0"' >> Cargo.toml && \
cat test_ipv6_url.rs > src/main.rs && \
cargo run --quiet)Repository: parseablehq/parseable Length of output: 312 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "rustc:"; (command -v rustc && rustc --version) || true
echo "cargo:"; (command -v cargo && cargo --version) || true
echo "rustup:"; (command -v rustup && rustup --version) || true
echo
echo "rustup show:"
rustup show || true
echo
echo "Try toolchain-specific versions:"
(rustc +stable --version && cargo +stable --version) || true
(rustc +1.77.0 --version && cargo +1.77.0 --version) || true
(rustc +nightly --version && cargo +nightly --version) || trueRepository: parseablehq/parseable Length of output: 1803 🌐 Web query:
💡 Result: The url crate in Rust does not support unbracketed IPv6 addresses in URLs. According to the URL Standard, which the crate implements, IPv6 literals must be enclosed in square brackets (e.g., [::1]) [1][2][3]. Attempting to parse an invalid format like::1:8000 or:::8000 using Url::parse will result in a ParseError::InvalidIpv6Address [1][4]. The documentation and examples explicitly state that IPv6 addresses are required to be given between '[' and ']' brackets [2][4][5]. For example: assert!(Url::parse("http://[:::1]") == Err(ParseError::InvalidIpv6Address)); [1] If you need to handle IPv6 addresses, ensure they are properly bracketed, such as [2001:db8::1]:8080/path [3]. Citations:
🌐 Web query:
💡 Result: In the Rust Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
# Run with explicit toolchain since rustup has no default configured.
TOOLCHAIN="+1.95.0"
cat > /tmp/test_ipv6_url.rs <<'EOF'
use url::Url;
fn main() {
let test_cases = vec![
("http://[::1]:8000", "bracketed IPv6 loopback"),
("http://[::]:8000", "bracketed IPv6 wildcard"),
("http://:::8000", "unbracketed IPv6 wildcard with port"),
("http://::1:8000", "unbracketed IPv6 loopback with port"),
("http://0.0.0.0:8000", "IPv4 wildcard"),
];
for (input, desc) in test_cases {
match Url::parse(input) {
Ok(url) => {
println!("✓ {}: parsed => host_str={:?} host={:?} port={:?}", desc, url.host_str(), url.host(), url.port());
}
Err(e) => println!("✗ {}: parse error: {}", desc, e),
}
}
}
EOF
# Use cargo to fetch the url crate (no manual rlib path hunting).
rm -rf /tmp/test_ipv6_url_proj
mkdir -p /tmp/test_ipv6_url_proj/src
cp /tmp/test_ipv6_url.rs /tmp/test_ipv6_url_proj/src/main.rs
cat > /tmp/test_ipv6_url_proj/Cargo.toml <<'EOF'
[package]
name = "test_ipv6_url"
version = "0.1.0"
edition = "2021"
[dependencies]
url = "2.5.2"
EOF
(cd /tmp/test_ipv6_url_proj && cargo $TOOLCHAIN run --quiet)Repository: parseablehq/parseable Length of output: 667 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '1,140p' src/oidc.rs | nl -ba | sed -n '40,110p'Repository: parseablehq/parseable Length of output: 108 🏁 Script executed: #!/bin/bash
set -euo pipefail
cat -n src/oidc.rs | sed -n '40,120p'Repository: parseablehq/parseable Length of output: 2716 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n "Origin::Local|socket_addr" srcRepository: parseablehq/parseable Length of output: 710 🏁 Script executed: #!/bin/bash
set -euo pipefail
cat -n src/option.rs | sed -n '90,170p'Repository: parseablehq/parseable Length of output: 3116 🏁 Script executed: #!/bin/bash
set -euo pipefail
cat -n src/cli.rs | sed -n '640,760p'Repository: parseablehq/parseable Length of output: 5048 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n "address:|validation::socket_addr|P_.*ADDRESS" src/cli.rsRepository: parseablehq/parseable Length of output: 239 🏁 Script executed: #!/bin/bash
set -euo pipefail
cat -n src/cli.rs | sed -n '110,240p'Repository: parseablehq/parseable Length of output: 4405 🏁 Script executed: #!/bin/bash
set -euo pipefail
cat > /tmp/test_to_socket_addrs.rs <<'EOF'
use std::net::ToSocketAddrs;
fn main() {
let cases = vec![
("0.0.0.0:8000", "ipv4 wildcard"),
("[::]:8000", "ipv6 wildcard bracketed"),
("[::1]:8000", "ipv6 loopback bracketed"),
("::1:8000", "ipv6 loopback unbracketed"),
(":::8000", "ipv6 wildcard+port unbracketed"),
];
for (s, desc) in cases {
let ok = s.to_socket_addrs().is_ok();
println!("{} => {}", desc, ok);
}
}
EOF
rm -rf /tmp/test_to_socket_addrs_proj
mkdir -p /tmp/test_to_socket_addrs_proj/src
cp /tmp/test_to_socket_addrs.rs /tmp/test_to_socket_addrs_proj/src/main.rs
cat > /tmp/test_to_socket_addrs_proj/Cargo.toml <<'EOF'
[package]
name = "test_to_socket_addrs"
version = "0.1.0"
edition = "2021"
[dependencies]
EOF
(cd /tmp/test_to_socket_addrs_proj && cargo +1.95.0 run --quiet)Repository: parseablehq/parseable Length of output: 224 Fix IPv6 wildcard + parsing behavior in
🤖 Prompt for AI Agents |
||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn wildcard_ipv4_origin_uses_localhost_with_configured_port() { | ||
| assert_eq!( | ||
| canonical_local_origin("0.0.0.0:8000", false).as_str(), | ||
| "http://localhost:8000/" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn loopback_ipv4_origin_is_preserved() { | ||
| assert_eq!( | ||
| canonical_local_origin("127.0.0.1:9000", false).as_str(), | ||
| "http://127.0.0.1:9000/" | ||
| ); | ||
| } | ||
| } | ||
|
Comment on lines
+77
to
+96
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add test coverage for IPv6 wildcard and fallback behavior. The test suite has the following gaps:
🧪 Suggested test additions#[test]
fn wildcard_ipv6_origin_uses_localhost_with_configured_port() {
// Test :: wildcard with port
assert_eq!(
canonical_local_origin(":::8000", false).as_str(),
"http://localhost:8000/"
);
}
#[test]
fn invalid_socket_addr_falls_back_to_localhost_8000() {
assert_eq!(
canonical_local_origin("not_a_valid_address", false).as_str(),
"http://localhost:8000/"
);
}
#[test]
fn https_scheme_is_respected() {
assert_eq!(
canonical_local_origin("127.0.0.1:9000", true).as_str(),
"https://127.0.0.1:9000/"
);
}🤖 Prompt for AI Agents |
||
|
|
||
| #[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] | ||
| pub struct Claims { | ||
| #[serde(flatten)] | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.