Compatibility Considerations When Using Official Bitwarden Clients with Vaultwarden
Vaultwarden parses the Bitwarden-Client-Version header from every request to gate features, ensuring older official clients receive filtered API responses while newer clients access advanced capabilities like SSH key support.
Vaultwarden implements the Bitwarden Client API as a drop-in replacement for the official service, maintaining seamless compatibility through version detection and adaptive response filtering. Understanding these compatibility considerations when using official Bitwarden clients with Vaultwarden helps administrators troubleshoot client behavior and predict feature availability across different client versions.
Client Version Detection
Vaultwarden extracts the client version from every incoming request to determine which features the client can safely handle. In src/auth.rs, the server implements a FromRequest guard that parses the Bitwarden-Client-Version header into a ClientVersion struct using semantic versioning:
// src/auth.rs
#[rocket::async_trait]
impl<'r> FromRequest<'r> for ClientVersion {
type Error = &'static str;
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let headers = request.headers();
// The official Bitwarden apps always send this header.
let Some(version) = headers.get_one("Bitwarden-Client-Version") else {
err_handler!("No Bitwarden-Client-Version header provided")
};
// Validate it follows semver.
let Ok(version) = semver::Version::parse(version) else {
err_handler!("Invalid Bitwarden-Client-Version header provided")
};
Outcome::Success(ClientVersion(version))
}
}
The extracted ClientVersion is passed as an optional parameter to route handlers throughout the codebase, including sync, identity, and cipher-related endpoints. This allows Vaultwarden to adjust its behavior dynamically based on the client's reported version.
Version-Gated Feature Support
To prevent JSON parsing errors on older clients, Vaultwarden hides features that exceed a client's understood schema. The most prominent example occurs in the /sync endpoint within src/api/core/ciphers.rs, where SSH keys are filtered out for clients older than version 2024.12.0:
// src/api/core/ciphers.rs
#[get("/sync?<data..>")]
async fn sync(
data: SyncData,
headers: Headers,
client_version: Option<ClientVersion>,
conn: DbConn,
) -> JsonResult {
// ...
// Filter out SSH keys if the client version is less than 2024.12.0
let show_ssh_keys = if let Some(client_version) = client_version {
let ver_match = semver::VersionReq::parse(">=2024.12.0").unwrap();
ver_match.matches(&client_version.0)
} else {
false
};
if !show_ssh_keys {
ciphers.retain(|c| c.atype != 5); // 5 = SSH key type
}
// ...
}
Clients reporting versions prior to 2024.12.0 receive sync responses that omit all cipher objects where "type": 5, effectively hiding SSH key support without breaking the client's vault decryption or sync process.
Experimental Client Feature Flags
Administrators can toggle experimental features via the EXPERIMENTAL_CLIENT_FEATURE_FLAGS environment variable. In src/util.rs, the parse_experimental_client_feature_flags function processes these flags while filtering out deprecated values to maintain backward compatibility with legacy installations:
// src/util.rs
/// Parses the experimental client feature flags string into a HashMap.
pub fn parse_experimental_client_feature_flags(
experimental_client_feature_flags: &str,
) -> HashMap<String, bool> {
// Deprecated flags are filtered out to keep old installations from breaking.
const DEPRECATED_FLAGS: &[&str] = &[
"autofill-overlay",
"autofill-v2",
"browser-fileless-import",
"extension-refresh",
"fido2-vault-credentials",
];
experimental_client_feature_flags
.split(',')
.filter_map(|f| {
let flag = f.trim();
if !flag.is_empty() && !DEPRECATED_FLAGS.contains(&flag) {
return Some((flag.to_owned(), true));
}
None
})
.collect()
}
The resulting flag map is consulted in src/api/core/mod.rs and other locations to conditionally enable behavior without exposing unstable features to incompatible clients.
Legacy Protocol Support
Beyond version gating, Vaultwarden maintains compatibility through protocol-specific adaptations:
U2F Legacy Support: In src/api/core/two_factor/webauthn.rs, the server adds an appid field only when necessary to support legacy U2F hardware keys, ensuring older authenticators continue to function with modern WebAuthn flows.
Identity Flow Adaptation: The src/api/identity.rs handlers propagate client_version through login and two-factor authentication processes, influencing SSO redirect behavior and two-factor method availability based on the client's capabilities.
Icon Download Compatibility: Special handling in src/util.rs addresses edge cases where specific clients, such as the Bitwarden Desktop application, fail to download icons under certain conditions, preventing 404 errors that would otherwise disrupt the user experience.
Graceful Degradation Strategy
When Vaultwarden encounters a feature request from an incompatible client, it employs field omission rather than error emission. The server simply excludes unsupported fields from JSON payloads, maintaining the seamless experience expected from the official Bitwarden service. No error codes are returned for version mismatches.
For truly unsupported API routes or malformed requests, Vaultwarden returns identical HTTP status codes (404, 400, 401, etc.) to those returned by the official Bitwarden servers, ensuring client-side error handling behaves identically whether connected to Vaultwarden or the official cloud service.
Testing Client Compatibility
To verify version-specific behavior, you can simulate different client versions using the Bitwarden-Client-Version header:
# Request sync as Android client v2024.12.0 (receives SSH keys)
curl -X GET "https://my-vaultwarden-instance.com/api/sync" \
-H "Authorization: Bearer <access_token>" \
-H "Bitwarden-Client-Version: 2024.12.0"
# Request sync as older client v2024.10.0 (excludes SSH keys)
curl -X GET "https://my-vaultwarden-instance.com/api/sync" \
-H "Authorization: Bearer <access_token>" \
-H "Bitwarden-Client-Version: 2024.10.0"
To enable experimental features for testing:
# docker-compose.yml
services:
vaultwarden:
image: vaultwarden/server:latest
environment:
- EXPERIMENTAL_CLIENT_FEATURE_FLAGS=new-password-generator
Summary
- Version Extraction: Vaultwarden reads the
Bitwarden-Client-Versionheader insrc/auth.rsto determine client capabilities using semantic versioning. - Feature Gating: Advanced features like SSH keys (type 5) are filtered from responses when
client_versionreports versions older than 2024.12.0. - Flag Management: The
EXPERIMENTAL_CLIENT_FEATURE_FLAGSenvironment variable controls beta features, with deprecated flags automatically filtered insrc/util.rs. - Silent Compatibility: Unsupported features are omitted from JSON responses rather than returning errors, preserving the official Bitwarden client experience.
- Protocol Adaptation: Legacy U2F
appidfields and icon download workarounds ensure older hardware and specific clients function correctly.
Frequently Asked Questions
How does Vaultwarden handle outdated Bitwarden clients?
Vaultwarden inspects the Bitwarden-Client-Version header and filters response payloads to exclude features the client cannot parse. For example, clients older than version 2024.12.0 receive sync data with SSH keys removed, preventing JSON deserialization errors while allowing the vault to function normally.
Can I force-enable features for older clients?
No. While you can set EXPERIMENTAL_CLIENT_FEATURE_FLAGS to enable beta functionality, Vaultwarden's core compatibility logic—such as the SSH key filtering in src/api/core/ciphers.rs—uses hardcoded version requirements to prevent breaking older clients. Feature flags are intended for testing new Bitwarden features, not bypassing version checks.
What happens if the Bitwarden-Client-Version header is missing?
The ClientVersion extractor in src/auth.rs returns an error outcome if the header is absent or contains an invalid semver string. However, all official Bitwarden clients (desktop, mobile, browser extensions, and CLI) reliably send this header, so missing headers typically indicate non-standard or automated API requests rather than legitimate client connections.
Are there compatibility differences between Bitwarden Desktop and mobile clients?
Yes. Specific edge cases exist for different client types. For instance, src/util.rs contains special handling for Bitwarden Desktop icon downloads, and the WebAuthn implementation in src/api/core/two_factor/webauthn.rs adds legacy U2F appid fields for clients that require them. These adaptations ensure consistent functionality across the diverse Bitwarden client ecosystem.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →