Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,13 @@ pub fn connect_to(
/// `localhost`, `127.0.0.1`/`127.x.x.x`, or `::1` — a mock server's or an
/// egress proxy's loopback listener never leaves the machine, so plain HTTP
/// there costs nothing a caller could intercept.
fn is_secure_base_url(base_url: &str) -> bool {
///
/// `pub(crate)` because [`vercel::Vercel::with_base_url`] applies the same
/// check: it is a second, lower-level way to reach the same client
/// [`connect_to`] builds, and skipping the check there would just move the
/// cleartext-credential exposure this guards against to a different entry
/// point rather than closing it.
pub(crate) fn is_secure_base_url(base_url: &str) -> bool {
if let Some(rest) = base_url.strip_prefix("https://") {
return !rest.is_empty();
}
Expand Down
12 changes: 11 additions & 1 deletion src/providers/vercel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,18 @@ impl Vercel {
///
/// # Errors
///
/// Returns [`Error::Transport`] when the HTTP client cannot be built.
/// Returns [`Error::InsecureBaseUrl`] when `base_url` is `http://` against a
/// non-loopback host — this constructor sends the bearer credential to
/// every request `base_url` produces, the same as
/// [`connect_to`](crate::providers::connect_to), and applies the same
/// check. Returns [`Error::Transport`] when the HTTP client cannot be
/// built.
pub fn with_base_url(credentials: Credentials, base_url: impl Into<String>) -> Result<Self> {
let base_url = base_url.into();
if !crate::providers::is_secure_base_url(&base_url) {
return Err(Error::InsecureBaseUrl { base_url });
}

Ok(Self {
http: Http::new(credentials, base_url)?,
})
Expand Down
19 changes: 17 additions & 2 deletions src/providers/vercel/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1130,15 +1130,30 @@ async fn a_team_scope_is_applied_to_every_request() {

#[test]
fn the_client_reports_itself_without_its_token() {
let host =
Vercel::with_base_url(Credentials::new("super-secret").unwrap(), "http://x").unwrap();
let host = Vercel::with_base_url(
Credentials::new("super-secret").unwrap(),
"http://127.0.0.1:1",
)
.unwrap();
let rendered = format!("{host:?}");

assert!(!rendered.contains("super-secret"), "{rendered}");
assert!(rendered.contains("<redacted>"), "{rendered}");
assert_eq!(host.kind(), ProviderKind::Vercel);
}

#[test]
fn with_base_url_rejects_plain_http_against_a_non_loopback_host() {
let error = Vercel::with_base_url(Credentials::new("token").unwrap(), "http://x").unwrap_err();

assert_eq!(
error,
Error::InsecureBaseUrl {
base_url: "http://x".to_owned()
}
);
}

#[test]
fn the_digest_is_sha_1() {
// The published SHA-1 of "abc". Vercel's `x-vercel-digest` defines the
Expand Down
Loading