Skip to content
Merged
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
70 changes: 59 additions & 11 deletions src/cargo/sources/git/known_hosts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -609,31 +609,42 @@ impl KnownHost {
}
for pattern in self.patterns.split(',') {
let pattern = pattern.to_lowercase();
let is_glob = is_glob_pattern(&pattern);

if is_glob {
let (negated, pattern) = match pattern.strip_prefix('!') {
Some(rest) => (true, rest.to_string()),
None => (false, pattern),
};

let matches = if is_glob_pattern(&pattern) && !is_bracketed_with_port(&pattern) {
match glob::Pattern::new(&pattern) {
Ok(glob) => match_found |= glob.matches(&host),
Ok(glob) => glob.matches(&host),
Err(e) => {
tracing::warn!(
"failed to interpret hostname `{pattern}` as glob pattern: {e}"
)
);
false
}
}
}

if let Some(pattern) = pattern.strip_prefix('!') {
if pattern == host {
return false;
}
} else {
match_found |= pattern == host;
pattern == host
};

// if the host is a negation and the rest matches then preemtively return false
if negated && matches {
return false;
}

// note that if a negation does not match then it does not mean that we found a match
match_found |= !negated && matches;
}
match_found
}
}

fn is_bracketed_with_port(pattern: &str) -> bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also have test for this bracketed ports. Also, do you have any examples for this?

Copy link
Contributor Author

@TanmayArya-1p TanmayArya-1p Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A host that is bracketed with ports was already tested here so this works as an example:

assert!(khs[2].host_matches("[example.net]:2222"));

I also noticed another bug in the know_hosts parsing that was not mentioned in the original issue:
Say we have the following know_hosts file:

# known_hosts
[example.net]:2222 ssh-dss AAAAB3N..

e:2222 will successfully match because [example.com]:2222 is treated as a glob. In glob matching syntax, [ ] matches if any single character within the brackets matches.
Take a look at this for reference:
https://www.digitalocean.com/community/tools/glob?comments=true&glob=%5Bexample.net%5D%3A2222&matches=false&tests=e%3A2222

I have also added a test that validates against this behaviour here

Here is the block of code responsible:

let is_glob = is_glob_pattern(&pattern);
if is_glob {
match glob::Pattern::new(&pattern) {
Ok(glob) => match_found |= glob.matches(&host),
Err(e) => {
tracing::warn!(
"failed to interpret hostname `{pattern}` as glob pattern: {e}"
)
}
}
}

Just for my future reference, The OpenBSD manual says this about this format:

A hostname or address may optionally be enclosed within ‘[’ and ‘]’ brackets then followed by ‘:’ and a non standard port number.

I can squash and rearrange the commits into 2 atomic commits if you wish :)

pattern.starts_with('[') && pattern.contains("]:")
}

fn hashed_hostname_matches(host: &str, hashed: &str) -> bool {
let Some((b64_salt, b64_host)) = hashed.split_once('|') else {
return false;
Expand Down Expand Up @@ -963,4 +974,41 @@ mod tests {
_ => panic!("Expected host key to be reject with error HostKeyRevoked."),
}
}

#[test]
fn negated_glob_rejects_match() {
let contents = r#"
*example.com,!*h.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKVYJpa0yUGaNk0NXQTPWa0tHjqRpx+7hl2diReH6DtR
"#;
let kh_path = Path::new("/home/abc/.known_hosts");
let khs = load_hostfile_contents(kh_path, contents);

assert!(khs[0].host_matches("web.example.com"));
assert!(
!khs[0].host_matches("ssh.example.com"),
"negated glob !*.example.com should reject ssh.example.com"
);
}

#[test]
fn validate_bracketed_host_with_port() {
let contents = r#"
[example.com]:2222 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKVYJpa0yUGaNk0NXQTPWa0tHjqRpx+7hl2diReH6DtR
"#;
let kh_path = Path::new("/home/abc/.known_hosts");
let khs = load_hostfile_contents(kh_path, contents);

assert!(
!khs[0].host_matches("e:2222"),
"Bracketed host with port should not be glob matched"
);
assert!(
!khs[0].host_matches("[example.com]:443"),
"Bracketed host with different port should not match"
);
assert!(
khs[0].host_matches("[example.com]:2222"),
"Bracketed host with port should match"
);
}
}