Skip to main content

bitwarden_core/client/
gov_mode.rs

1use crate::client::internal::InternalClient;
2
3/// Returns true when the client is in Gov Mode.
4///
5/// Today, this is inferred from the configured API URL host. Uses URL parsing
6/// rather than substring matching so that paths, queries,
7/// and adversarial subdomain spoofs (e.g. api.bitwarden-gov.com.evil.example)
8/// cannot accidentally classify as Gov. Host matching is case-insensitive and
9/// allows any subdomain under bitwarden-gov.com.
10///
11/// PM-36520 will refactor this to read from stored /config state, adding
12/// support for self-hosted Gov installs where URL inference cannot work
13/// because self-hosted URLs are customer-chosen.
14pub(crate) fn is_gov_mode(internal: &InternalClient) -> bool {
15    url::Url::parse(&internal.api_configurations.api_config.base_path)
16        .ok()
17        .and_then(|u| u.host_str().map(str::to_lowercase))
18        .map(|h| h == "bitwarden-gov.com" || h.ends_with(".bitwarden-gov.com"))
19        .unwrap_or(false)
20}
21
22#[cfg(test)]
23mod tests {
24    use crate::{Client, ClientSettings};
25
26    fn gov_mode_for(url: &str) -> bool {
27        Client::new(Some(ClientSettings {
28            api_url: url.into(),
29            ..Default::default()
30        }))
31        .gov_mode()
32    }
33
34    #[test]
35    fn true_for_canonical_gov_host() {
36        assert!(gov_mode_for("https://api.bitwarden-gov.com"));
37    }
38
39    #[test]
40    fn true_for_alternate_subdomain_on_gov_domain() {
41        assert!(gov_mode_for("https://vault.bitwarden-gov.com"));
42    }
43
44    #[test]
45    fn false_for_us_and_eu_hosts() {
46        assert!(!gov_mode_for("https://api.bitwarden.com"));
47        assert!(!gov_mode_for("https://api.bitwarden.eu"));
48    }
49
50    #[test]
51    fn false_for_self_hosted_with_bitwarden_in_name() {
52        assert!(!gov_mode_for("https://bitwarden.example.com/api"));
53        assert!(!gov_mode_for("https://my-bitwarden.eu/api"));
54    }
55
56    #[test]
57    fn false_for_spoofed_subdomain() {
58        assert!(!gov_mode_for("https://api.bitwarden-gov.com.evil.example/"));
59        assert!(!gov_mode_for("https://bitwarden-gov.com.evil.example/"));
60    }
61
62    #[test]
63    fn false_for_path_only_match() {
64        assert!(!gov_mode_for("https://example.com/bitwarden-gov.com/api"));
65    }
66
67    #[test]
68    fn case_insensitive() {
69        assert!(gov_mode_for("https://API.BITWARDEN-GOV.COM"));
70    }
71
72    #[test]
73    fn false_for_invalid_url() {
74        assert!(!gov_mode_for("not a url"));
75        assert!(!gov_mode_for(""));
76    }
77}