From de808c5ad90ad02a4b754e782c698a0c806df9ad Mon Sep 17 00:00:00 2001 From: Timshel Date: Mon, 25 Aug 2025 17:59:55 +0200 Subject: [PATCH 01/12] Fix Playwright docker (#6206) --- playwright/README.md | 16 ++++++++++++---- playwright/compose/warden/Dockerfile | 6 +++--- playwright/compose/warden/build.sh | 1 - playwright/playwright.config.ts | 6 +++--- playwright/test.env | 4 ++++ playwright/tests/setups/sso.ts | 19 +++++-------------- playwright/tests/sso_login.spec.ts | 21 ++++++--------------- playwright/tests/sso_organization.spec.ts | 2 +- 8 files changed, 34 insertions(+), 41 deletions(-) diff --git a/playwright/README.md b/playwright/README.md index 249108b9..c8bcceda 100644 --- a/playwright/README.md +++ b/playwright/README.md @@ -91,17 +91,25 @@ When creating new scenario use the recorder to more easily identify elements This does not start the server, you will need to start it manually. ```bash -npx playwright codegen "http://127.0.0.1:8000" +DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env up Vaultwarden +npx playwright codegen "http://127.0.0.1:8003" ``` ## Override web-vault It is possible to change the `web-vault` used by referencing a different `bw_web_builds` commit. +Simplest is to set and uncomment `PW_WV_REPO_URL` and `PW_WV_COMMIT_HASH` in the `test.env`. +Ensure that the image is built with: + +```bash +DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env build Vaultwarden +``` + +You can check the result running: + ```bash -export PW_WV_REPO_URL=https://github.com/Timshel/oidc_web_builds.git -export PW_WV_COMMIT_HASH=8707dc76df3f0cceef2be5bfae37bb29bd17fae6 -DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env build Playwright +DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env up Vaultwarden ``` # OpenID Connect test setup diff --git a/playwright/compose/warden/Dockerfile b/playwright/compose/warden/Dockerfile index afa33858..e472d207 100644 --- a/playwright/compose/warden/Dockerfile +++ b/playwright/compose/warden/Dockerfile @@ -1,6 +1,6 @@ FROM playwright_oidc_vaultwarden_prebuilt AS prebuilt -FROM node:22-bookworm AS build +FROM node:22-trixie AS build ARG REPO_URL ARG COMMIT_HASH @@ -14,7 +14,7 @@ COPY build.sh /build.sh RUN /build.sh ######################## RUNTIME IMAGE ######################## -FROM docker.io/library/debian:bookworm-slim +FROM docker.io/library/debian:trixie-slim ENV DEBIAN_FRONTEND=noninteractive @@ -24,7 +24,7 @@ RUN mkdir /data && \ --no-install-recommends \ ca-certificates \ curl \ - libmariadb-dev-compat \ + libmariadb-dev \ libpq5 \ openssl && \ rm -rf /var/lib/apt/lists/* diff --git a/playwright/compose/warden/build.sh b/playwright/compose/warden/build.sh index da354112..a29067c8 100755 --- a/playwright/compose/warden/build.sh +++ b/playwright/compose/warden/build.sh @@ -16,7 +16,6 @@ if [[ ! -z "$REPO_URL" ]] && [[ ! -z "$COMMIT_HASH" ]] ; then export VAULT_VERSION=$(cat Dockerfile | grep "ARG VAULT_VERSION" | cut -d "=" -f2) ./scripts/checkout_web_vault.sh - ./scripts/patch_web_vault.sh ./scripts/build_web_vault.sh printf '{"version":"%s"}' "$COMMIT_HASH" > ./web-vault/apps/web/build/vw-version.json diff --git a/playwright/playwright.config.ts b/playwright/playwright.config.ts index 0f0df9c2..de721aa3 100644 --- a/playwright/playwright.config.ts +++ b/playwright/playwright.config.ts @@ -26,9 +26,9 @@ export default defineConfig({ * But short action/nav/expect timeouts to fail on specific step (raise locally if not enough). */ timeout: 120 * 1000, - actionTimeout: 10 * 1000, - navigationTimeout: 10 * 1000, - expect: { timeout: 10 * 1000 }, + actionTimeout: 20 * 1000, + navigationTimeout: 20 * 1000, + expect: { timeout: 20 * 1000 }, /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { diff --git a/playwright/test.env b/playwright/test.env index 300e0d55..d97c08d1 100644 --- a/playwright/test.env +++ b/playwright/test.env @@ -66,6 +66,10 @@ SSO_CLIENT_SECRET=warden SSO_AUTHORITY=http://${KC_HTTP_HOST}:${KC_HTTP_PORT}/realms/${TEST_REALM} SSO_DEBUG_TOKENS=true +# Custom web-vault build +# PW_WV_REPO_URL=https://github.com/dani-garcia/bw_web_builds.git +# PW_WV_COMMIT_HASH=a5f5390895516bce2f48b7baadb6dc399e5fe75a + ########################### # Docker MariaDb container# ########################### diff --git a/playwright/tests/setups/sso.ts b/playwright/tests/setups/sso.ts index 65e337f3..8998b6c7 100644 --- a/playwright/tests/setups/sso.ts +++ b/playwright/tests/setups/sso.ts @@ -12,7 +12,7 @@ export async function logNewUser( test: Test, page: Page, user: { email: string, name: string, password: string }, - options: { mailBuffer?: MailBuffer, override?: boolean } = {} + options: { mailBuffer?: MailBuffer } = {} ) { await test.step(`Create user ${user.name}`, async () => { await page.context().clearCookies(); @@ -20,12 +20,8 @@ export async function logNewUser( await test.step('Landing page', async () => { await utils.cleanLanding(page); - if( options.override ) { - await page.getByRole('button', { name: 'Continue' }).click(); - } else { - await page.getByLabel(/Email address/).fill(user.email); - await page.getByRole('button', { name: /Use single sign-on/ }).click(); - } + await page.locator("input[type=email].vw-email-sso").fill(user.email); + await page.getByRole('button', { name: /Use single sign-on/ }).click(); }); await test.step('Keycloak login', async () => { @@ -69,7 +65,6 @@ export async function logUser( user: { email: string, password: string }, options: { mailBuffer ?: MailBuffer, - override?: boolean, totp?: OTPAuth.TOTP, mail2fa?: boolean, } = {} @@ -82,12 +77,8 @@ export async function logUser( await test.step('Landing page', async () => { await utils.cleanLanding(page); - if( options.override ) { - await page.getByRole('button', { name: 'Continue' }).click(); - } else { - await page.getByLabel(/Email address/).fill(user.email); - await page.getByRole('button', { name: /Use single sign-on/ }).click(); - } + await page.locator("input[type=email].vw-email-sso").fill(user.email); + await page.getByRole('button', { name: /Use single sign-on/ }).click(); }); await test.step('Keycloak login', async () => { diff --git a/playwright/tests/sso_login.spec.ts b/playwright/tests/sso_login.spec.ts index b4817bed..8a1bb9ab 100644 --- a/playwright/tests/sso_login.spec.ts +++ b/playwright/tests/sso_login.spec.ts @@ -29,8 +29,8 @@ test('SSO login', async ({ page }) => { test('Non SSO login', async ({ page }) => { // Landing page await page.goto('/'); - await page.getByLabel(/Email address/).fill(users.user1.email); - await page.getByRole('button', { name: 'Continue' }).click(); + await page.locator("input[type=email].vw-email-sso").fill(users.user1.email); + await page.getByRole('button', { name: 'Other' }).click(); // Unlock page await page.getByLabel('Master password').fill(users.user1.password); @@ -58,20 +58,12 @@ test('Non SSO login impossible', async ({ page, browser }, testInfo: TestInfo) = // Landing page await page.goto('/'); - await page.getByLabel(/Email address/).fill(users.user1.email); // Check that SSO login is available await expect(page.getByRole('button', { name: /Use single sign-on/ })).toHaveCount(1); - await page.getByLabel(/Email address/).fill(users.user1.email); - await page.getByRole('button', { name: 'Continue' }).click(); - - // Unlock page - await page.getByLabel('Master password').fill(users.user1.password); - await page.getByRole('button', { name: 'Log in with master password' }).click(); - - // An error should appear - await page.getByLabel('SSO sign-in is required') + // No Continue/Other + await expect(page.getByRole('button', { name: 'Other' })).toHaveCount(0); }); @@ -82,13 +74,12 @@ test('No SSO login', async ({ page }, testInfo: TestInfo) => { // Landing page await page.goto('/'); - await page.getByLabel(/Email address/).fill(users.user1.email); // No SSO button (rely on a correct selector checked in previous test) - await page.getByLabel('Master password'); await expect(page.getByRole('button', { name: /Use single sign-on/ })).toHaveCount(0); // Can continue to Master password + await page.getByLabel(/Email address/).fill(users.user1.email); await page.getByRole('button', { name: 'Continue' }).click(); - await expect(page.getByRole('button', { name: /Log in with master password/ })).toHaveCount(1); + await expect(page.getByRole('button', { name: 'Log in with master password' })).toHaveCount(1); }); diff --git a/playwright/tests/sso_organization.spec.ts b/playwright/tests/sso_organization.spec.ts index c215ce3b..c1238d45 100644 --- a/playwright/tests/sso_organization.spec.ts +++ b/playwright/tests/sso_organization.spec.ts @@ -65,7 +65,7 @@ test('Enforce password policy', async ({ page }) => { await utils.logout(test, page, users.user1); await test.step(`Unlock trigger policy`, async () => { - await page.getByRole('textbox', { name: 'Email address (required)' }).fill(users.user1.email); + await page.locator("input[type=email].vw-email-sso").fill(users.user1.email); await page.getByRole('button', { name: 'Use single sign-on' }).click(); await page.getByRole('textbox', { name: 'Master password (required)' }).fill(users.user1.password); From 550b670dbafa34f6e4b8812c4cf14103d09a1f02 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 25 Aug 2025 19:00:10 +0300 Subject: [PATCH 02/12] Switch to GHA's concurrency control (#6164) - removes the need to use a 3rd party action --- .github/workflows/release.yml | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bd0468ec..02a1878f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,32 +10,14 @@ on: # https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet - '[1-2].[0-9]+.[0-9]+' -jobs: - # https://github.com/marketplace/actions/skip-duplicate-actions - # Some checks to determine if we need to continue with building a new docker. - # We will skip this check if we are creating a tag, because that has the same hash as a previous run already. - skip_check: - # Only run this in the upstream repo and not on forks - if: ${{ github.repository == 'dani-garcia/vaultwarden' }} - name: Cancel older jobs when running - permissions: - actions: write - runs-on: ubuntu-24.04 - outputs: - should_skip: ${{ steps.skip_check.outputs.should_skip }} - - steps: - - name: Skip Duplicates Actions - id: skip_check - uses: fkirc/skip-duplicate-actions@f75f66ce1886f00957d99748a42c724f4330bdcf # v5.3.1 - with: - cancel_others: 'true' - # Only run this when not creating a tag - if: ${{ github.ref_type == 'branch' }} +concurrency: + # Apply concurrency control only on the upstream repo + group: ${{ github.repository == 'dani-garcia/vaultwarden' && format('{0}-{1}', github.workflow, github.ref) || github.run_id }} + # Don't cancel other runs when creating a tag + cancel-in-progress: ${{ github.ref_type == 'branch' }} +jobs: docker-build: - needs: skip_check - if: ${{ needs.skip_check.outputs.should_skip != 'true' && github.repository == 'dani-garcia/vaultwarden' }} name: Build Vaultwarden containers permissions: packages: write From 843c063649ae84e2592eb293fbfa0f5e684f2b91 Mon Sep 17 00:00:00 2001 From: Thomas Violent Date: Mon, 25 Aug 2025 18:32:05 +0200 Subject: [PATCH 03/12] Make database connection pool dynamic (#6166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add min_idle and idle_timeout to database pool * Update src/config.rs Co-authored-by: Daniel García --------- Co-authored-by: Daniel García --- src/config.rs | 8 +++++++- src/db/mod.rs | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 6d375a96..86174fa6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -639,9 +639,15 @@ make_config! { /// Timeout when acquiring database connection database_timeout: u64, false, def, 30; - /// Database connection pool size + /// Timeout in seconds before idle connections to the database are closed + database_idle_timeout: u64, false, def, 600; + + /// Database connection max pool size database_max_conns: u32, false, def, 10; + /// Database connection min pool size + database_min_conns: u32, false, def, 2; + /// Database connection init |> SQL statements to run when creating a new database connection, mainly useful for connection-scoped pragmas. If empty, a database-specific default is used. database_conn_init: String, false, def, String::new(); diff --git a/src/db/mod.rs b/src/db/mod.rs index ece6b597..9f5bb150 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -134,6 +134,8 @@ macro_rules! generate_connections { let manager = ConnectionManager::new(&url); let pool = Pool::builder() .max_size(CONFIG.database_max_conns()) + .min_idle(Some(CONFIG.database_min_conns())) + .idle_timeout(Some(Duration::from_secs(CONFIG.database_idle_timeout()))) .connection_timeout(Duration::from_secs(CONFIG.database_timeout())) .connection_customizer(Box::new(DbConnOptions{ init_stmts: conn_type.get_init_stmts() From 55577fa4eb5e343bd400784da0c92c26927ecd1b Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 25 Aug 2025 21:44:31 +0300 Subject: [PATCH 04/12] Re-add `if` check to release workflow (#6227) - prevents container builds from running on forks --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 02a1878f..cc9adbe2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,6 +19,7 @@ concurrency: jobs: docker-build: name: Build Vaultwarden containers + if: ${{ github.repository == 'dani-garcia/vaultwarden' }} permissions: packages: write contents: read From 5ee908517f071898aedcff71b09f8ad68540325f Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Mon, 25 Aug 2025 20:49:39 +0200 Subject: [PATCH 05/12] Fix Webauthn/Passkey 2FA migration/validation issues (#6190) * Apply Passkey fixes from zUnixorn Applied SecurityKey to Passkey fixes from @zUnixorn Co-authored-by: zUnixorn <77864446+zUnixorn@users.noreply.github.com> Signed-off-by: BlackDex * Fix Webauthn/Passkey 2FA migration issues Because the webauthn-rs v0.3 crate did not know or store new flags currently used in v0.5, some verifications failed. This mainly failed because of a check if a key was backuped or not, and if it was allowed to do so. Most hardware keys like YubiKey's do not have this flag enabled and can't be duplicated or faked via software. Since the rise of Passkey's, like Bitwarden's own implementation, and other platforms like Android, and Apple use Software keys which are shared between devices, they set these backup flags to true. This broke the login attempts, because the default during the migration was `false`, and cause an error during validation. This PR checks for the flags during the response/verification step, and if these flags are `true`, then search for the stored key, adjust it's value, and also update the current challenge state to match, to prevent the first login attempt to fail. This should not cause any issue, since the credential-id is checked and matched, and only updated when needed. Fixes #6154 Signed-off-by: BlackDex * Fix comments Signed-off-by: BlackDex --------- Signed-off-by: BlackDex --- Cargo.toml | 3 +- src/api/core/two_factor/webauthn.rs | 128 ++++++++++++++++++++++++---- 2 files changed, 114 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c4e5fc1e..206d9c79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,8 +126,7 @@ yubico = { package = "yubico_ng", version = "0.14.1", features = ["online-tokio" # WebAuthn libraries # danger-allow-state-serialisation is needed to save the state in the db # danger-credential-internals is needed to support U2F to Webauthn migration -# danger-user-presence-only-security-keys is needed to disable UV -webauthn-rs = { version = "0.5.2", features = ["danger-allow-state-serialisation", "danger-credential-internals", "danger-user-presence-only-security-keys"] } +webauthn-rs = { version = "0.5.2", features = ["danger-allow-state-serialisation", "danger-credential-internals"] } webauthn-rs-proto = "0.5.2" webauthn-rs-core = "0.5.2" diff --git a/src/api/core/two_factor/webauthn.rs b/src/api/core/two_factor/webauthn.rs index 049145e0..d49a80ae 100644 --- a/src/api/core/two_factor/webauthn.rs +++ b/src/api/core/two_factor/webauthn.rs @@ -21,12 +21,12 @@ use std::sync::{Arc, LazyLock}; use std::time::Duration; use url::Url; use uuid::Uuid; -use webauthn_rs::prelude::{Base64UrlSafeData, SecurityKey, SecurityKeyAuthentication, SecurityKeyRegistration}; +use webauthn_rs::prelude::{Base64UrlSafeData, Credential, Passkey, PasskeyAuthentication, PasskeyRegistration}; use webauthn_rs::{Webauthn, WebauthnBuilder}; use webauthn_rs_proto::{ AuthenticationExtensionsClientOutputs, AuthenticatorAssertionResponseRaw, AuthenticatorAttestationResponseRaw, PublicKeyCredential, RegisterPublicKeyCredential, RegistrationExtensionsClientOutputs, - RequestAuthenticationExtensions, + RequestAuthenticationExtensions, UserVerificationPolicy, }; pub static WEBAUTHN_2FA_CONFIG: LazyLock> = LazyLock::new(|| { @@ -38,8 +38,7 @@ pub static WEBAUTHN_2FA_CONFIG: LazyLock> = LazyLock::new(|| { let webauthn = WebauthnBuilder::new(&rp_id, &rp_origin) .expect("Creating WebauthnBuilder failed") .rp_name(&domain) - .timeout(Duration::from_millis(60000)) - .danger_set_user_presence_only_security_keys(true); + .timeout(Duration::from_millis(60000)); Arc::new(webauthn.build().expect("Building Webauthn failed")) }); @@ -78,7 +77,7 @@ pub struct WebauthnRegistration { pub name: String, pub migrated: bool, - pub credential: SecurityKey, + pub credential: Passkey, } impl WebauthnRegistration { @@ -89,6 +88,24 @@ impl WebauthnRegistration { "migrated": self.migrated, }) } + + fn set_backup_eligible(&mut self, backup_eligible: bool, backup_state: bool) -> bool { + let mut changed = false; + let mut cred: Credential = self.credential.clone().into(); + + if cred.backup_state != backup_state { + cred.backup_state = backup_state; + changed = true; + } + + if backup_eligible && !cred.backup_eligible { + cred.backup_eligible = true; + changed = true; + } + + self.credential = cred.into(); + changed + } } #[post("/two-factor/get-webauthn", data = "")] @@ -131,18 +148,27 @@ async fn generate_webauthn_challenge( .map(|r| r.credential.cred_id().to_owned()) // We return the credentialIds to the clients to avoid double registering .collect(); - let (challenge, state) = webauthn.start_securitykey_registration( + let (mut challenge, state) = webauthn.start_passkey_registration( Uuid::from_str(&user.uuid).expect("Failed to parse UUID"), // Should never fail &user.email, &user.name, Some(registrations), - None, - None, )?; + let mut state = serde_json::to_value(&state)?; + state["rs"]["policy"] = Value::String("discouraged".to_string()); + state["rs"]["extensions"].as_object_mut().unwrap().clear(); + let type_ = TwoFactorType::WebauthnRegisterChallenge; TwoFactor::new(user.uuid.clone(), type_, serde_json::to_string(&state)?).save(&mut conn).await?; + // Because for this flow we abuse the passkeys as 2FA, and use it more like a securitykey + // we need to modify some of the default settings defined by `start_passkey_registration()`. + challenge.public_key.extensions = None; + if let Some(asc) = challenge.public_key.authenticator_selection.as_mut() { + asc.user_verification = UserVerificationPolicy::Discouraged_DO_NOT_USE; + } + let mut challenge_value = serde_json::to_value(challenge.public_key)?; challenge_value["status"] = "ok".into(); challenge_value["errorMessage"] = "".into(); @@ -253,7 +279,7 @@ async fn activate_webauthn( let type_ = TwoFactorType::WebauthnRegisterChallenge as i32; let state = match TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await { Some(tf) => { - let state: SecurityKeyRegistration = serde_json::from_str(&tf.data)?; + let state: PasskeyRegistration = serde_json::from_str(&tf.data)?; tf.delete(&mut conn).await?; state } @@ -261,7 +287,7 @@ async fn activate_webauthn( }; // Verify the credentials with the saved state - let credential = webauthn.finish_securitykey_registration(&data.device_response.into(), &state)?; + let credential = webauthn.finish_passkey_registration(&data.device_response.into(), &state)?; let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &mut conn).await?.1; // TODO: Check for repeated ID's @@ -372,21 +398,25 @@ pub async fn generate_webauthn_login( conn: &mut DbConn, ) -> JsonResult { // Load saved credentials - let creds: Vec<_> = get_webauthn_registrations(user_id, conn).await?.1.into_iter().map(|r| r.credential).collect(); + let creds: Vec = + get_webauthn_registrations(user_id, conn).await?.1.into_iter().map(|r| r.credential).collect(); if creds.is_empty() { err!("No Webauthn devices registered") } // Generate a challenge based on the credentials - let (mut response, state) = webauthn.start_securitykey_authentication(&creds)?; + let (mut response, state) = webauthn.start_passkey_authentication(&creds)?; // Modify to discourage user verification let mut state = serde_json::to_value(&state)?; + state["ast"]["policy"] = Value::String("discouraged".to_string()); // Add appid, this is only needed for U2F compatibility, so maybe it can be removed as well let app_id = format!("{}/app-id.json", &CONFIG.domain()); state["ast"]["appid"] = Value::String(app_id.clone()); + + response.public_key.user_verification = UserVerificationPolicy::Discouraged_DO_NOT_USE; response .public_key .extensions @@ -413,9 +443,9 @@ pub async fn validate_webauthn_login( conn: &mut DbConn, ) -> EmptyResult { let type_ = TwoFactorType::WebauthnLoginChallenge as i32; - let state = match TwoFactor::find_by_user_and_type(user_id, type_, conn).await { + let mut state = match TwoFactor::find_by_user_and_type(user_id, type_, conn).await { Some(tf) => { - let state: SecurityKeyAuthentication = serde_json::from_str(&tf.data)?; + let state: PasskeyAuthentication = serde_json::from_str(&tf.data)?; tf.delete(conn).await?; state } @@ -432,7 +462,12 @@ pub async fn validate_webauthn_login( let mut registrations = get_webauthn_registrations(user_id, conn).await?.1; - let authentication_result = webauthn.finish_securitykey_authentication(&rsp, &state)?; + // We need to check for and update the backup_eligible flag when needed. + // Vaultwarden did not have knowledge of this flag prior to migrating to webauthn-rs v0.5.x + // Because of this we check the flag at runtime and update the registrations and state when needed + check_and_update_backup_eligible(user_id, &rsp, &mut registrations, &mut state, conn).await?; + + let authentication_result = webauthn.finish_passkey_authentication(&rsp, &state)?; for reg in &mut registrations { if ct_eq(reg.credential.cred_id(), authentication_result.cred_id()) { @@ -454,3 +489,66 @@ pub async fn validate_webauthn_login( } ) } + +async fn check_and_update_backup_eligible( + user_id: &UserId, + rsp: &PublicKeyCredential, + registrations: &mut Vec, + state: &mut PasskeyAuthentication, + conn: &mut DbConn, +) -> EmptyResult { + // The feature flags from the response + // For details see: https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data + const FLAG_BACKUP_ELIGIBLE: u8 = 0b0000_1000; + const FLAG_BACKUP_STATE: u8 = 0b0001_0000; + + if let Some(bits) = rsp.response.authenticator_data.get(32) { + let backup_eligible = 0 != (bits & FLAG_BACKUP_ELIGIBLE); + let backup_state = 0 != (bits & FLAG_BACKUP_STATE); + + // If the current key is backup eligible, then we probably need to update one of the keys already stored in the database + // This is needed because Vaultwarden didn't store this information when using the previous version of webauthn-rs since it was a new addition to the protocol + // Because we store multiple keys in one json string, we need to fetch the correct key first, and update its information before we let it verify + if backup_eligible { + let rsp_id = rsp.raw_id.as_slice(); + for reg in &mut *registrations { + if ct_eq(reg.credential.cred_id().as_slice(), rsp_id) { + // Try to update the key, and if needed also update the database, before the actual state check is done + if reg.set_backup_eligible(backup_eligible, backup_state) { + TwoFactor::new( + user_id.clone(), + TwoFactorType::Webauthn, + serde_json::to_string(®istrations)?, + ) + .save(conn) + .await?; + + // We also need to adjust the current state which holds the challenge used to start the authentication verification + // Because Vaultwarden supports multiple keys, we need to loop through the deserialized state and check which key to update + let mut raw_state = serde_json::to_value(&state)?; + if let Some(credentials) = raw_state + .get_mut("ast") + .and_then(|v| v.get_mut("credentials")) + .and_then(|v| v.as_array_mut()) + { + for cred in credentials.iter_mut() { + if cred.get("cred_id").is_some_and(|v| { + // Deserialize to a [u8] so it can be compared using `ct_eq` with the `rsp_id` + let cred_id_slice: Base64UrlSafeData = serde_json::from_value(v.clone()).unwrap(); + ct_eq(cred_id_slice, rsp_id) + }) { + cred["backup_eligible"] = Value::Bool(backup_eligible); + cred["backup_state"] = Value::Bool(backup_state); + } + } + } + + *state = serde_json::from_value(raw_state)?; + } + break; + } + } + } + } + Ok(()) +} From 7161f612a1b8a1b7ee06ec0f82a53d7ed65b522a Mon Sep 17 00:00:00 2001 From: "Helmut K. C. Tessarek" Date: Mon, 25 Aug 2025 18:11:36 -0400 Subject: [PATCH 06/12] refactor(config): update template, add validation (#6229) This change is a follow up to #6166 - add new options to `.env.template` - add validation for new config option values --- .env.template | 10 +++++++++- src/config.rs | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.env.template b/.env.template index 1e9a189e..140a4ccc 100644 --- a/.env.template +++ b/.env.template @@ -80,8 +80,16 @@ ## Timeout when acquiring database connection # DATABASE_TIMEOUT=30 +## Database idle timeout +## Timeout in seconds before idle connections to the database are closed. +# DATABASE_IDLE_TIMEOUT=600 + +## Database min connections +## Define the minimum size of the connection pool used for connecting to the database. +# DATABASE_MIN_CONNS=2 + ## Database max connections -## Define the size of the connection pool used for connecting to the database. +## Define the maximum size of the connection pool used for connecting to the database. # DATABASE_MAX_CONNS=10 ## Database connection initialization diff --git a/src/config.rs b/src/config.rs index 86174fa6..840bf474 100644 --- a/src/config.rs +++ b/src/config.rs @@ -834,6 +834,14 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { err!(format!("`DATABASE_MAX_CONNS` contains an invalid value. Ensure it is between 1 and {limit}.",)); } + if cfg.database_min_conns < 1 || cfg.database_min_conns > limit { + err!(format!("`DATABASE_MIN_CONNS` contains an invalid value. Ensure it is between 1 and {limit}.",)); + } + + if cfg.database_min_conns > cfg.database_max_conns { + err!(format!("`DATABASE_MIN_CONNS` must be smaller than or equal to `DATABASE_MAX_CONNS`.",)); + } + if let Some(log_file) = &cfg.log_file { if std::fs::OpenOptions::new().append(true).create(true).open(log_file).is_err() { err!("Unable to write to log file", log_file); From 3510351f4df3923d5c1874739cb1e00af5ea0ba6 Mon Sep 17 00:00:00 2001 From: Timshel Date: Tue, 26 Aug 2025 21:08:43 +0200 Subject: [PATCH 07/12] Show SSO_ALLOW_UNKNOWN_EMAIL_VERIFICATION in admin (#6235) --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 840bf474..75caadbd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -697,7 +697,7 @@ make_config! { /// Allow email association |> Associate existing non-SSO user based on email sso_signups_match_email: bool, true, def, true; /// Allow unknown email verification status |> Allowing this with `SSO_SIGNUPS_MATCH_EMAIL=true` open potential account takeover. - sso_allow_unknown_email_verification: bool, false, def, false; + sso_allow_unknown_email_verification: bool, true, def, false; /// Client ID sso_client_id: String, true, def, String::new(); /// Client Key From 6db5b7115d90cb391514f9c3fcc1b59b1c4c3a8d Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Tue, 26 Aug 2025 21:16:50 +0200 Subject: [PATCH 08/12] Update crates, gha and web-vault (#6234) - Update crates to the latest version (Some are yanked and downgraded) - Update GHA's - Update web-vault to v2025.8.0 Signed-off-by: BlackDex --- .github/workflows/build.yml | 9 +- .github/workflows/check-templates.yml | 2 +- .github/workflows/hadolint.yml | 2 +- .github/workflows/release.yml | 4 +- .github/workflows/trivy.yml | 4 +- .github/workflows/zizmor.yml | 2 +- Cargo.lock | 251 ++++++++++-------- Cargo.toml | 16 +- docker/DockerSettings.yaml | 4 +- docker/Dockerfile.alpine | 12 +- docker/Dockerfile.debian | 12 +- src/api/identity.rs | 1 - .../templates/scss/vaultwarden.scss.hbs | 6 +- 13 files changed, 180 insertions(+), 145 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9c563570..7ed85943 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,8 +34,7 @@ jobs: permissions: actions: write contents: read - # We use Ubuntu 22.04 here because this matches the library versions used within the Debian docker containers - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 timeout-minutes: 120 # Make warnings errors, this is to prevent warnings slipping through. # This is done globally to prevent rebuilds when the RUSTFLAGS env variable changes. @@ -56,7 +55,7 @@ jobs: # Checkout the repo - name: "Checkout" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 with: persist-credentials: false fetch-depth: 0 @@ -82,7 +81,7 @@ jobs: # Only install the clippy and rustfmt components on the default rust-toolchain - name: "Install rust-toolchain version" - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master @ Apr 29, 2025, 9:22 PM GMT+2 + uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master @ Aug 23, 2025, 3:20 AM GMT+2 if: ${{ matrix.channel == 'rust-toolchain' }} with: toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}" @@ -92,7 +91,7 @@ jobs: # Install the any other channel to be used for which we do not execute clippy and rustfmt - name: "Install MSRV version" - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # master @ Apr 29, 2025, 9:22 PM GMT+2 + uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master @ Aug 23, 2025, 3:20 AM GMT+2 if: ${{ matrix.channel != 'rust-toolchain' }} with: toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}" diff --git a/.github/workflows/check-templates.yml b/.github/workflows/check-templates.yml index 25f6faac..943235fe 100644 --- a/.github/workflows/check-templates.yml +++ b/.github/workflows/check-templates.yml @@ -14,7 +14,7 @@ jobs: steps: # Checkout the repo - name: "Checkout" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 with: persist-credentials: false # End Checkout the repo diff --git a/.github/workflows/hadolint.yml b/.github/workflows/hadolint.yml index 2efdd581..75d3a95d 100644 --- a/.github/workflows/hadolint.yml +++ b/.github/workflows/hadolint.yml @@ -35,7 +35,7 @@ jobs: # End Download hadolint # Checkout the repo - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 with: persist-credentials: false # End Checkout the repo diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cc9adbe2..114fa00f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,7 +72,7 @@ jobs: # Checkout the repo - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 # We need fetch-depth of 0 so we also get all the tag metadata with: persist-credentials: false @@ -175,7 +175,7 @@ jobs: - name: Bake ${{ matrix.base_image }} containers id: bake_vw - uses: docker/bake-action@37816e747588cb137173af99ab33873600c46ea8 # v6.8.0 + uses: docker/bake-action@3acf805d94d93a86cce4ca44798a76464a75b88c # v6.9.0 env: BASE_TAGS: "${{ env.BASE_TAGS }}" SOURCE_COMMIT: "${{ env.SOURCE_COMMIT }}" diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index f1defcf6..d70afb66 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 with: persist-credentials: false @@ -48,6 +48,6 @@ jobs: severity: CRITICAL,HIGH - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9 + uses: github/codeql-action/upload-sarif@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11 with: sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 99a21d97..fde1f217 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -16,7 +16,7 @@ jobs: security-events: write steps: - name: Checkout repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 with: persist-credentials: false diff --git a/Cargo.lock b/Cargo.lock index 19fd8a27..39a3d942 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,11 +167,13 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" +checksum = "6448dfb3960f0b038e88c781ead1e7eb7929dfc3a71a1336ec9086c00f6d1e75" dependencies = [ "brotli", + "compression-codecs", + "compression-core", "flate2", "futures-core", "memchr", @@ -277,9 +279,9 @@ dependencies = [ [[package]] name = "async-std" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" dependencies = [ "async-channel 1.9.0", "async-global-executor", @@ -332,9 +334,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", @@ -436,9 +438,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.80.0" +version = "1.81.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e822be5d4ed48fa7adc983de1b814dea33a5460c7e0e81b053b8d2ca3b14c354" +checksum = "79ede098271e3471036c46957cba2ba30888f53bda2515bf04b560614a30a36e" dependencies = [ "aws-credential-types", "aws-runtime", @@ -458,9 +460,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.81.0" +version = "1.82.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66aa7b30f1fac6e02ca26e3839fa78db3b94f6298a6e7a6208fb59071d93a87e" +checksum = "43326f724ba2cc957e6f3deac0ca1621a3e5d4146f5970c24c8a108dac33070f" dependencies = [ "aws-credential-types", "aws-runtime", @@ -480,9 +482,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.82.0" +version = "1.84.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2194426df72592f91df0cda790cb1e571aa87d66cecfea59a64031b58145abe3" +checksum = "91abcdbfb48c38a0419eb75e0eac772a4783a96750392680e4f3c25a8a0535b9" dependencies = [ "aws-credential-types", "aws-runtime", @@ -584,9 +586,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.8.6" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e107ce0783019dbff59b3a244aa0c114e4a8c9d93498af9162608cd5474e796" +checksum = "a3d57c8b53a72d15c8e190475743acf34e4996685e346a3448dd54ef696fc6e0" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -607,9 +609,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.8.7" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75d52251ed4b9776a3e8487b2a01ac915f73b2da3af8fc1e77e0fce697a550d4" +checksum = "07f5e0fc8a6b3f2303f331b94504bbf754d85488f402d6f1dd7a6080f99afe56" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -760,9 +762,9 @@ checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "blake2" @@ -806,9 +808,9 @@ dependencies = [ [[package]] name = "brotli" -version = "8.0.1" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -878,7 +880,7 @@ dependencies = [ "futures", "hashbrown 0.15.5", "once_cell", - "thiserror 2.0.14", + "thiserror 2.0.16", "tokio", "web-time", ] @@ -943,9 +945,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.32" +version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ "jobserver", "libc", @@ -954,9 +956,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -1015,6 +1017,28 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" +[[package]] +name = "compression-codecs" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46cc6539bf1c592cff488b9f253b30bc0ec50d15407c2cf45e27bd8f308d5905" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2957e823c15bde7ecf1e8b64e537aa03a6be5fda0e2334e99887669e75b12e01" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1298,9 +1322,9 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "data-url" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" [[package]] name = "der" @@ -1817,9 +1841,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -1947,9 +1971,9 @@ dependencies = [ [[package]] name = "generator" -version = "0.8.5" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" dependencies = [ "cc", "cfg-if", @@ -2051,7 +2075,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6" dependencies = [ "codemap", - "indexmap 2.10.0", + "indexmap 2.11.0", "lasso", "once_cell", "phf 0.11.3", @@ -2080,7 +2104,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap 2.10.0", + "indexmap 2.11.0", "slab", "tokio", "tokio-util", @@ -2106,7 +2130,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.14", + "thiserror 2.0.16", "walkdir", ] @@ -2173,7 +2197,7 @@ dependencies = [ "once_cell", "rand 0.9.2", "ring", - "thiserror 2.0.14", + "thiserror 2.0.16", "tinyvec", "tokio", "tracing", @@ -2196,7 +2220,7 @@ dependencies = [ "rand 0.9.2", "resolv-conf", "smallvec", - "thiserror 2.0.14", + "thiserror 2.0.16", "tokio", "tracing", ] @@ -2241,9 +2265,9 @@ dependencies = [ [[package]] name = "html5gum" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3918b5f36d61861b757261da986b51be562c7a87ac4e531d4158e67e08bff72" +checksum = "ba6fbe46e93059ce8ee19fbefdb0c7699cc7197fcaac048f2c3593f3e5da845f" dependencies = [ "jetscii", ] @@ -2341,19 +2365,21 @@ dependencies = [ [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http 1.3.1", "http-body 1.0.1", "httparse", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -2366,7 +2392,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.3.1", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "rustls 0.23.31", "rustls-native-certs", @@ -2385,7 +2411,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "native-tls", "tokio", @@ -2406,7 +2432,7 @@ dependencies = [ "futures-util", "http 1.3.1", "http-body 1.0.1", - "hyper 1.6.0", + "hyper 1.7.0", "ipnet", "libc", "percent-encoding", @@ -2537,9 +2563,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -2569,9 +2595,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", "hashbrown 0.15.5", @@ -2596,9 +2622,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ "bitflags", "cfg-if", @@ -2678,9 +2704,9 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", @@ -2784,9 +2810,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libmimalloc-sys" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88cd67e9de251c1781dbe2f641a1a3ad66eaae831b8a2c38fbdc5ddae16d4d" +checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" dependencies = [ "cc", "libc", @@ -2862,7 +2888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" dependencies = [ "cfg-if", - "generator 0.8.5", + "generator 0.8.7", "scoped-tls", "tracing", "tracing-subscriber", @@ -2930,9 +2956,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.47" +version = "0.1.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1791cbe101e95af5764f06f20f6760521f7158f69dbf9d6baf941ee1bf6bc40" +checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" dependencies = [ "libmimalloc-sys", ] @@ -3515,9 +3541,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" @@ -3526,7 +3552,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror 2.0.14", + "thiserror 2.0.16", "ucd-trie", ] @@ -3761,9 +3787,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.97" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -3862,7 +3888,7 @@ dependencies = [ "rustc-hash", "rustls 0.23.31", "socket2 0.5.10", - "thiserror 2.0.14", + "thiserror 2.0.16", "tokio", "tracing", "web-time", @@ -3883,7 +3909,7 @@ dependencies = [ "rustls 0.23.31", "rustls-pki-types", "slab", - "thiserror 2.0.14", + "thiserror 2.0.16", "tinyvec", "tracing", "web-time", @@ -4034,14 +4060,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata 0.4.10", + "regex-syntax 0.8.6", ] [[package]] @@ -4055,20 +4081,20 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax 0.8.6", ] [[package]] name = "regex-lite" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" +checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30" [[package]] name = "regex-syntax" @@ -4078,9 +4104,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "reopen" @@ -4145,7 +4171,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-rustls", "hyper-tls", "hyper-util", @@ -4243,7 +4269,7 @@ dependencies = [ "either", "figment", "futures", - "indexmap 2.10.0", + "indexmap 2.11.0", "log", "memchr", "multer", @@ -4275,7 +4301,7 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" dependencies = [ "devise", "glob", - "indexmap 2.10.0", + "indexmap 2.11.0", "proc-macro2", "quote", "rocket_http", @@ -4295,7 +4321,7 @@ dependencies = [ "futures", "http 0.2.12", "hyper 0.14.32", - "indexmap 2.10.0", + "indexmap 2.11.0", "log", "memchr", "pear", @@ -4368,12 +4394,13 @@ dependencies = [ [[package]] name = "rust-ini" -version = "0.21.2" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7295b7ce3bf4806b419dc3420745998b447178b7005e2011947b38fc5aa6791" +checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f" dependencies = [ "cfg-if", "ordered-multimap", + "trim-in-place", ] [[package]] @@ -4704,9 +4731,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", @@ -4773,7 +4800,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.10.0", + "indexmap 2.11.0", "schemars 0.9.0", "schemars 1.0.4", "serde", @@ -4869,7 +4896,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.14", + "thiserror 2.0.16", "time", ] @@ -5016,9 +5043,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.105" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -5086,15 +5113,15 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -5108,11 +5135,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.14" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.14", + "thiserror-impl 2.0.16", ] [[package]] @@ -5128,9 +5155,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.14" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -5209,9 +5236,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -5338,13 +5365,13 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.11.0", "serde", "serde_spanned 1.0.0", "toml_datetime 0.7.0", "toml_parser", "toml_writer", - "winnow 0.7.12", + "winnow 0.7.13", ] [[package]] @@ -5371,12 +5398,12 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.11.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", - "winnow 0.7.12", + "winnow 0.7.13", ] [[package]] @@ -5385,7 +5412,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" dependencies = [ - "winnow 0.7.12", + "winnow 0.7.13", ] [[package]] @@ -5519,6 +5546,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + [[package]] name = "triomphe" version = "0.1.14" @@ -5607,9 +5640,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", @@ -6007,11 +6040,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -6384,9 +6417,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 206d9c79..c62bc929 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,7 +82,7 @@ tokio-util = { version = "0.7.16", features = ["compat"]} # A generic serialization/deserialization framework serde = { version = "1.0.219", features = ["derive"] } -serde_json = "1.0.142" +serde_json = "1.0.143" # A safe, extensible ORM and Query builder diesel = { version = "2.2.12", features = ["chrono", "r2d2", "numeric"] } @@ -131,11 +131,11 @@ webauthn-rs-proto = "0.5.2" webauthn-rs-core = "0.5.2" # Handling of URL's for WebAuthn and favicons -url = "2.5.4" +url = "2.5.7" # Email libraries lettre = { version = "0.11.18", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "hostname", "tracing", "tokio1-rustls", "ring", "rustls-native-certs"], default-features = false } -percent-encoding = "2.3.1" # URL encoding library used for URL's in the emails +percent-encoding = "2.3.2" # URL encoding library used for URL's in the emails email_address = "0.2.9" # HTML Template library @@ -146,9 +146,9 @@ reqwest = { version = "0.12.23", features = ["rustls-tls", "rustls-tls-native-ro hickory-resolver = "0.25.2" # Favicon extraction libraries -html5gum = "0.7.0" -regex = { version = "1.11.1", features = ["std", "perf", "unicode-perl"], default-features = false } -data-url = "0.3.1" +html5gum = "0.8.0" +regex = { version = "1.11.2", features = ["std", "perf", "unicode-perl"], default-features = false } +data-url = "0.3.2" bytes = "1.10.1" svg-hush = "0.9.5" @@ -178,7 +178,7 @@ semver = "1.0.26" # Allow overriding the default memory allocator # Mainly used for the musl builds, since the default musl malloc is very slow -mimalloc = { version = "0.1.47", features = ["secure"], default-features = false, optional = true } +mimalloc = { version = "0.1.48", features = ["secure"], default-features = false, optional = true } which = "8.0.0" @@ -198,7 +198,7 @@ opendal = { version = "0.54.0", features = ["services-fs"], default-features = f anyhow = { version = "1.0.99", optional = true } aws-config = { version = "1.8.5", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true } aws-credential-types = { version = "1.2.5", optional = true } -aws-smithy-runtime-api = { version = "1.8.7", optional = true } +aws-smithy-runtime-api = { version = "1.9.0", optional = true } http = { version = "1.3.1", optional = true } reqsign = { version = "0.16.5", optional = true } diff --git a/docker/DockerSettings.yaml b/docker/DockerSettings.yaml index f6ad59bd..68fa7532 100644 --- a/docker/DockerSettings.yaml +++ b/docker/DockerSettings.yaml @@ -1,6 +1,6 @@ --- -vault_version: "v2025.7.2" -vault_image_digest: "sha256:e40b20eeffbcccb27db6c08c3aaa1cf7d3c92333f634dec26a077590e910e1c9" +vault_version: "v2025.8.0" +vault_image_digest: "sha256:41c2b51c87882248f405d5a0ab37210d2672a312ec5d4f3b9afcdbbe8eb9d57d" # Cross Compile Docker Helper Scripts v1.6.1 # We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts # https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index 2a24d227..09477b3e 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -19,15 +19,15 @@ # - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # click the tag name to view the digest of the image it currently points to. # - From the command line: -# $ docker pull docker.io/vaultwarden/web-vault:v2025.7.2 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.7.2 -# [docker.io/vaultwarden/web-vault@sha256:e40b20eeffbcccb27db6c08c3aaa1cf7d3c92333f634dec26a077590e910e1c9] +# $ docker pull docker.io/vaultwarden/web-vault:v2025.8.0 +# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.8.0 +# [docker.io/vaultwarden/web-vault@sha256:41c2b51c87882248f405d5a0ab37210d2672a312ec5d4f3b9afcdbbe8eb9d57d] # # - Conversely, to get the tag name from the digest: -# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e40b20eeffbcccb27db6c08c3aaa1cf7d3c92333f634dec26a077590e910e1c9 -# [docker.io/vaultwarden/web-vault:v2025.7.2] +# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:41c2b51c87882248f405d5a0ab37210d2672a312ec5d4f3b9afcdbbe8eb9d57d +# [docker.io/vaultwarden/web-vault:v2025.8.0] # -FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:e40b20eeffbcccb27db6c08c3aaa1cf7d3c92333f634dec26a077590e910e1c9 AS vault +FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:41c2b51c87882248f405d5a0ab37210d2672a312ec5d4f3b9afcdbbe8eb9d57d AS vault ########################## ALPINE BUILD IMAGES ########################## ## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index d3c6aaf2..9d1af57f 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -19,15 +19,15 @@ # - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # click the tag name to view the digest of the image it currently points to. # - From the command line: -# $ docker pull docker.io/vaultwarden/web-vault:v2025.7.2 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.7.2 -# [docker.io/vaultwarden/web-vault@sha256:e40b20eeffbcccb27db6c08c3aaa1cf7d3c92333f634dec26a077590e910e1c9] +# $ docker pull docker.io/vaultwarden/web-vault:v2025.8.0 +# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.8.0 +# [docker.io/vaultwarden/web-vault@sha256:41c2b51c87882248f405d5a0ab37210d2672a312ec5d4f3b9afcdbbe8eb9d57d] # # - Conversely, to get the tag name from the digest: -# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e40b20eeffbcccb27db6c08c3aaa1cf7d3c92333f634dec26a077590e910e1c9 -# [docker.io/vaultwarden/web-vault:v2025.7.2] +# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:41c2b51c87882248f405d5a0ab37210d2672a312ec5d4f3b9afcdbbe8eb9d57d +# [docker.io/vaultwarden/web-vault:v2025.8.0] # -FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:e40b20eeffbcccb27db6c08c3aaa1cf7d3c92333f634dec26a077590e910e1c9 AS vault +FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:41c2b51c87882248f405d5a0ab37210d2672a312ec5d4f3b9afcdbbe8eb9d57d AS vault ########################## Cross Compile Docker Helper Scripts ########################## ## We use the linux/amd64 no matter which Build Platform, since these are all bash scripts diff --git a/src/api/identity.rs b/src/api/identity.rs index ba22104e..7a53ae6a 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -442,7 +442,6 @@ async fn _password_login( authenticated_response(&user, &mut device, auth_tokens, twofactor_token, &now, conn, ip).await } -#[allow(clippy::too_many_arguments)] async fn authenticated_response( user: &User, device: &mut Device, diff --git a/src/static/templates/scss/vaultwarden.scss.hbs b/src/static/templates/scss/vaultwarden.scss.hbs index b031404d..882c788f 100644 --- a/src/static/templates/scss/vaultwarden.scss.hbs +++ b/src/static/templates/scss/vaultwarden.scss.hbs @@ -55,7 +55,7 @@ app-root ng-component > form > div:nth-child(1) > div > button[buttontype="secon {{/if}} /* Hide the `Log in with passkey` settings */ -app-change-password app-webauthn-login-settings { +app-user-layout app-password-settings app-webauthn-login-settings { @extend %vw-hide; } /* Hide Log in with passkey on the login page */ @@ -133,6 +133,10 @@ bit-nav-logo bit-nav-item a:before { bit-nav-logo bit-nav-item .bwi-shield { @extend %vw-hide; } +/* Hide Device Login Protection button on user settings page */ +app-user-layout app-danger-zone button:nth-child(1) { + @extend %vw-hide; +} /**** END Static Vaultwarden Changes ****/ /**** START Dynamic Vaultwarden Changes ****/ {{#if signup_disabled}} From f76362ff89c538e88cde4b276ad437e8c8643e71 Mon Sep 17 00:00:00 2001 From: Timshel Date: Tue, 26 Aug 2025 21:18:25 +0200 Subject: [PATCH 09/12] Fix panic around sso_master_password_policy (#6233) --- src/api/core/organizations.rs | 8 ++++---- src/api/mod.rs | 4 ++-- src/config.rs | 21 ++++++++++++++++----- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index b78bf128..22712003 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -2063,12 +2063,12 @@ async fn list_policies_token(org_id: OrganizationId, token: &str, mut conn: DbCo async fn get_master_password_policy(org_id: OrganizationId, _headers: Headers, mut conn: DbConn) -> JsonResult { let policy = OrgPolicy::find_by_org_and_type(&org_id, OrgPolicyType::MasterPassword, &mut conn).await.unwrap_or_else(|| { - let data = match CONFIG.sso_master_password_policy() { - Some(policy) => policy, - None => "null".to_string(), + let (enabled, data) = match CONFIG.sso_master_password_policy_value() { + Some(policy) if CONFIG.sso_enabled() => (true, policy.to_string()), + _ => (false, "null".to_string()), }; - OrgPolicy::new(org_id, OrgPolicyType::MasterPassword, CONFIG.sso_master_password_policy().is_some(), data) + OrgPolicy::new(org_id, OrgPolicyType::MasterPassword, enabled, data) }); Ok(Json(policy.to_json())) diff --git a/src/api/mod.rs b/src/api/mod.rs index e0df1e64..6227b56f 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -110,8 +110,8 @@ async fn master_password_policy(user: &User, conn: &DbConn) -> Value { enforce_on_login: acc.enforce_on_login || policy.enforce_on_login, } })) - } else if let Some(policy_str) = CONFIG.sso_master_password_policy().filter(|_| CONFIG.sso_enabled()) { - serde_json::from_str(&policy_str).unwrap_or(json!({})) + } else if CONFIG.sso_enabled() { + CONFIG.sso_master_password_policy_value().unwrap_or(json!({})) } else { json!({}) }; diff --git a/src/config.rs b/src/config.rs index 75caadbd..878d3861 100644 --- a/src/config.rs +++ b/src/config.rs @@ -974,7 +974,7 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { validate_internal_sso_issuer_url(&cfg.sso_authority)?; validate_internal_sso_redirect_url(&cfg.sso_callback_path)?; - check_master_password_policy(&cfg.sso_master_password_policy)?; + validate_sso_master_password_policy(&cfg.sso_master_password_policy)?; } if cfg._enable_yubico { @@ -1168,12 +1168,19 @@ fn validate_internal_sso_redirect_url(sso_callback_path: &String) -> Result) -> Result<(), Error> { +fn validate_sso_master_password_policy( + sso_master_password_policy: &Option, +) -> Result, Error> { let policy = sso_master_password_policy.as_ref().map(|mpp| serde_json::from_str::(mpp)); - if let Some(Err(error)) = policy { - err!(format!("Invalid sso_master_password_policy ({error}), Ensure that it's correctly escaped with ''")) + + match policy { + None => Ok(None), + Some(Ok(jsobject @ serde_json::Value::Object(_))) => Ok(Some(jsobject)), + Some(Ok(_)) => err!("Invalid sso_master_password_policy: parsed value is not a JSON object"), + Some(Err(error)) => { + err!(format!("Invalid sso_master_password_policy ({error}), Ensure that it's correctly escaped with ''")) + } } - Ok(()) } /// Extracts an RFC 6454 web origin from a URL. @@ -1578,6 +1585,10 @@ impl Config { validate_internal_sso_redirect_url(&self.sso_callback_path()) } + pub fn sso_master_password_policy_value(&self) -> Option { + validate_sso_master_password_policy(&self.sso_master_password_policy()).ok().flatten() + } + pub fn sso_scopes_vec(&self) -> Vec { self.sso_scopes().split_whitespace().map(str::to_string).collect() } From 5a8736e1163a8ae83771851fc715115d95a55860 Mon Sep 17 00:00:00 2001 From: Stefan Melmuk <509385+stefan0xC@users.noreply.github.com> Date: Tue, 26 Aug 2025 22:07:20 +0200 Subject: [PATCH 10/12] make webauthn more optional (#6160) * make webauthn optional * hide passkey if domain is not set --- src/api/core/two_factor/webauthn.rs | 52 +++++-------------- src/api/identity.rs | 24 +++------ src/api/web.rs | 1 + src/config.rs | 4 ++ src/main.rs | 2 - .../templates/scss/vaultwarden.scss.hbs | 7 +++ 6 files changed, 33 insertions(+), 57 deletions(-) diff --git a/src/api/core/two_factor/webauthn.rs b/src/api/core/two_factor/webauthn.rs index d49a80ae..2a992dbe 100644 --- a/src/api/core/two_factor/webauthn.rs +++ b/src/api/core/two_factor/webauthn.rs @@ -17,7 +17,7 @@ use rocket::serde::json::Json; use rocket::Route; use serde_json::Value; use std::str::FromStr; -use std::sync::{Arc, LazyLock}; +use std::sync::LazyLock; use std::time::Duration; use url::Url; use uuid::Uuid; @@ -29,7 +29,7 @@ use webauthn_rs_proto::{ RequestAuthenticationExtensions, UserVerificationPolicy, }; -pub static WEBAUTHN_2FA_CONFIG: LazyLock> = LazyLock::new(|| { +static WEBAUTHN: LazyLock = LazyLock::new(|| { let domain = CONFIG.domain(); let domain_origin = CONFIG.domain_origin(); let rp_id = Url::parse(&domain).map(|u| u.domain().map(str::to_owned)).ok().flatten().unwrap_or_default(); @@ -40,11 +40,9 @@ pub static WEBAUTHN_2FA_CONFIG: LazyLock> = LazyLock::new(|| { .rp_name(&domain) .timeout(Duration::from_millis(60000)); - Arc::new(webauthn.build().expect("Building Webauthn failed")) + webauthn.build().expect("Building Webauthn failed") }); -pub type Webauthn2FaConfig<'a> = &'a rocket::State>; - pub fn routes() -> Vec { routes![get_webauthn, generate_webauthn_challenge, activate_webauthn, activate_webauthn_put, delete_webauthn,] } @@ -130,12 +128,7 @@ async fn get_webauthn(data: Json, headers: Headers, mut conn: } #[post("/two-factor/get-webauthn-challenge", data = "")] -async fn generate_webauthn_challenge( - data: Json, - headers: Headers, - webauthn: Webauthn2FaConfig<'_>, - mut conn: DbConn, -) -> JsonResult { +async fn generate_webauthn_challenge(data: Json, headers: Headers, mut conn: DbConn) -> JsonResult { let data: PasswordOrOtpData = data.into_inner(); let user = headers.user; @@ -148,7 +141,7 @@ async fn generate_webauthn_challenge( .map(|r| r.credential.cred_id().to_owned()) // We return the credentialIds to the clients to avoid double registering .collect(); - let (mut challenge, state) = webauthn.start_passkey_registration( + let (mut challenge, state) = WEBAUTHN.start_passkey_registration( Uuid::from_str(&user.uuid).expect("Failed to parse UUID"), // Should never fail &user.email, &user.name, @@ -259,12 +252,7 @@ impl From for PublicKeyCredential { } #[post("/two-factor/webauthn", data = "")] -async fn activate_webauthn( - data: Json, - headers: Headers, - webauthn: Webauthn2FaConfig<'_>, - mut conn: DbConn, -) -> JsonResult { +async fn activate_webauthn(data: Json, headers: Headers, mut conn: DbConn) -> JsonResult { let data: EnableWebauthnData = data.into_inner(); let mut user = headers.user; @@ -287,7 +275,7 @@ async fn activate_webauthn( }; // Verify the credentials with the saved state - let credential = webauthn.finish_passkey_registration(&data.device_response.into(), &state)?; + let credential = WEBAUTHN.finish_passkey_registration(&data.device_response.into(), &state)?; let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &mut conn).await?.1; // TODO: Check for repeated ID's @@ -316,13 +304,8 @@ async fn activate_webauthn( } #[put("/two-factor/webauthn", data = "")] -async fn activate_webauthn_put( - data: Json, - headers: Headers, - webauthn: Webauthn2FaConfig<'_>, - conn: DbConn, -) -> JsonResult { - activate_webauthn(data, headers, webauthn, conn).await +async fn activate_webauthn_put(data: Json, headers: Headers, conn: DbConn) -> JsonResult { + activate_webauthn(data, headers, conn).await } #[derive(Debug, Deserialize)] @@ -392,11 +375,7 @@ pub async fn get_webauthn_registrations( } } -pub async fn generate_webauthn_login( - user_id: &UserId, - webauthn: Webauthn2FaConfig<'_>, - conn: &mut DbConn, -) -> JsonResult { +pub async fn generate_webauthn_login(user_id: &UserId, conn: &mut DbConn) -> JsonResult { // Load saved credentials let creds: Vec = get_webauthn_registrations(user_id, conn).await?.1.into_iter().map(|r| r.credential).collect(); @@ -406,7 +385,7 @@ pub async fn generate_webauthn_login( } // Generate a challenge based on the credentials - let (mut response, state) = webauthn.start_passkey_authentication(&creds)?; + let (mut response, state) = WEBAUTHN.start_passkey_authentication(&creds)?; // Modify to discourage user verification let mut state = serde_json::to_value(&state)?; @@ -436,12 +415,7 @@ pub async fn generate_webauthn_login( Ok(Json(serde_json::to_value(response.public_key)?)) } -pub async fn validate_webauthn_login( - user_id: &UserId, - response: &str, - webauthn: Webauthn2FaConfig<'_>, - conn: &mut DbConn, -) -> EmptyResult { +pub async fn validate_webauthn_login(user_id: &UserId, response: &str, conn: &mut DbConn) -> EmptyResult { let type_ = TwoFactorType::WebauthnLoginChallenge as i32; let mut state = match TwoFactor::find_by_user_and_type(user_id, type_, conn).await { Some(tf) => { @@ -467,7 +441,7 @@ pub async fn validate_webauthn_login( // Because of this we check the flag at runtime and update the registrations and state when needed check_and_update_backup_eligible(user_id, &rsp, &mut registrations, &mut state, conn).await?; - let authentication_result = webauthn.finish_passkey_authentication(&rsp, &state)?; + let authentication_result = WEBAUTHN.finish_passkey_authentication(&rsp, &state)?; for reg in &mut registrations { if ct_eq(reg.credential.cred_id(), authentication_result.cred_id()) { diff --git a/src/api/identity.rs b/src/api/identity.rs index 7a53ae6a..d3d22805 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -9,7 +9,6 @@ use rocket::{ }; use serde_json::Value; -use crate::api::core::two_factor::webauthn::Webauthn2FaConfig; use crate::{ api::{ core::{ @@ -49,7 +48,6 @@ async fn login( data: Form, client_header: ClientHeaders, client_version: Option, - webauthn: Webauthn2FaConfig<'_>, mut conn: DbConn, ) -> JsonResult { let data: ConnectData = data.into_inner(); @@ -72,7 +70,7 @@ async fn login( _check_is_some(&data.device_name, "device_name cannot be blank")?; _check_is_some(&data.device_type, "device_type cannot be blank")?; - _password_login(data, &mut user_id, &mut conn, &client_header.ip, &client_version, webauthn).await + _password_login(data, &mut user_id, &mut conn, &client_header.ip, &client_version).await } "client_credentials" => { _check_is_some(&data.client_id, "client_id cannot be blank")?; @@ -93,7 +91,7 @@ async fn login( _check_is_some(&data.device_name, "device_name cannot be blank")?; _check_is_some(&data.device_type, "device_type cannot be blank")?; - _sso_login(data, &mut user_id, &mut conn, &client_header.ip, &client_version, webauthn).await + _sso_login(data, &mut user_id, &mut conn, &client_header.ip, &client_version).await } "authorization_code" => err!("SSO sign-in is not available"), t => err!("Invalid type", t), @@ -171,7 +169,6 @@ async fn _sso_login( conn: &mut DbConn, ip: &ClientIp, client_version: &Option, - webauthn: Webauthn2FaConfig<'_>, ) -> JsonResult { AuthMethod::Sso.check_scope(data.scope.as_ref())?; @@ -270,7 +267,7 @@ async fn _sso_login( } Some((mut user, sso_user)) => { let mut device = get_device(&data, conn, &user).await?; - let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, client_version, webauthn, conn).await?; + let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, client_version, conn).await?; if user.private_key.is_none() { // User was invited a stub was created @@ -325,7 +322,6 @@ async fn _password_login( conn: &mut DbConn, ip: &ClientIp, client_version: &Option, - webauthn: Webauthn2FaConfig<'_>, ) -> JsonResult { // Validate scope AuthMethod::Password.check_scope(data.scope.as_ref())?; @@ -435,7 +431,7 @@ async fn _password_login( let mut device = get_device(&data, conn, &user).await?; - let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, client_version, webauthn, conn).await?; + let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, client_version, conn).await?; let auth_tokens = auth::AuthTokens::new(&device, &user, AuthMethod::Password, data.client_id); @@ -667,7 +663,6 @@ async fn twofactor_auth( device: &mut Device, ip: &ClientIp, client_version: &Option, - webauthn: Webauthn2FaConfig<'_>, conn: &mut DbConn, ) -> ApiResult> { let twofactors = TwoFactor::find_by_user(&user.uuid, conn).await; @@ -687,7 +682,7 @@ async fn twofactor_auth( Some(ref code) => code, None => { err_json!( - _json_err_twofactor(&twofactor_ids, &user.uuid, data, client_version, webauthn, conn).await?, + _json_err_twofactor(&twofactor_ids, &user.uuid, data, client_version, conn).await?, "2FA token not provided" ) } @@ -704,9 +699,7 @@ async fn twofactor_auth( Some(TwoFactorType::Authenticator) => { authenticator::validate_totp_code_str(&user.uuid, twofactor_code, &selected_data?, ip, conn).await? } - Some(TwoFactorType::Webauthn) => { - webauthn::validate_webauthn_login(&user.uuid, twofactor_code, webauthn, conn).await? - } + Some(TwoFactorType::Webauthn) => webauthn::validate_webauthn_login(&user.uuid, twofactor_code, conn).await?, Some(TwoFactorType::YubiKey) => yubikey::validate_yubikey_login(twofactor_code, &selected_data?).await?, Some(TwoFactorType::Duo) => { match CONFIG.duo_use_iframe() { @@ -738,7 +731,7 @@ async fn twofactor_auth( } _ => { err_json!( - _json_err_twofactor(&twofactor_ids, &user.uuid, data, client_version, webauthn, conn).await?, + _json_err_twofactor(&twofactor_ids, &user.uuid, data, client_version, conn).await?, "2FA Remember token not provided" ) } @@ -772,7 +765,6 @@ async fn _json_err_twofactor( user_id: &UserId, data: &ConnectData, client_version: &Option, - webauthn: Webauthn2FaConfig<'_>, conn: &mut DbConn, ) -> ApiResult { let mut result = json!({ @@ -792,7 +784,7 @@ async fn _json_err_twofactor( Some(TwoFactorType::Authenticator) => { /* Nothing to do for TOTP */ } Some(TwoFactorType::Webauthn) if CONFIG.domain_set() => { - let request = webauthn::generate_webauthn_login(user_id, webauthn, conn).await?; + let request = webauthn::generate_webauthn_login(user_id, conn).await?; result["TwoFactorProviders2"][provider.to_string()] = request.0; } diff --git a/src/api/web.rs b/src/api/web.rs index d8e35009..98d51a5e 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -64,6 +64,7 @@ fn vaultwarden_css() -> Cached> { "sso_enabled": CONFIG.sso_enabled(), "sso_only": CONFIG.sso_enabled() && CONFIG.sso_only(), "yubico_enabled": CONFIG._enable_yubico() && CONFIG.yubico_client_id().is_some() && CONFIG.yubico_secret_key().is_some(), + "webauthn_2fa_supported": CONFIG.is_webauthn_2fa_supported(), }); let scss = match CONFIG.render_template("scss/vaultwarden.scss", &css_options) { diff --git a/src/config.rs b/src/config.rs index 878d3861..116c9096 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1525,6 +1525,10 @@ impl Config { } } + pub fn is_webauthn_2fa_supported(&self) -> bool { + Url::parse(&self.domain()).expect("DOMAIN not a valid URL").domain().is_some() + } + /// Tests whether the admin token is set to a non-empty value. pub fn is_admin_token_set(&self) -> bool { let token = self.admin_token(); diff --git a/src/main.rs b/src/main.rs index e91dcbc4..3195300b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,7 +61,6 @@ mod sso_client; mod util; use crate::api::core::two_factor::duo_oidc::purge_duo_contexts; -use crate::api::core::two_factor::webauthn::WEBAUTHN_2FA_CONFIG; use crate::api::purge_auth_requests; use crate::api::{WS_ANONYMOUS_SUBSCRIPTIONS, WS_USERS}; pub use config::{PathType, CONFIG}; @@ -601,7 +600,6 @@ async fn launch_rocket(pool: db::DbPool, extra_debug: bool) -> Result<(), Error> .manage(pool) .manage(Arc::clone(&WS_USERS)) .manage(Arc::clone(&WS_ANONYMOUS_SUBSCRIPTIONS)) - .manage(Arc::clone(&WEBAUTHN_2FA_CONFIG)) .attach(util::AppHeaders()) .attach(util::Cors()) .attach(util::BetterLogging(extra_debug)) diff --git a/src/static/templates/scss/vaultwarden.scss.hbs b/src/static/templates/scss/vaultwarden.scss.hbs index 882c788f..2b84cbb9 100644 --- a/src/static/templates/scss/vaultwarden.scss.hbs +++ b/src/static/templates/scss/vaultwarden.scss.hbs @@ -172,6 +172,13 @@ app-root a[routerlink="/signup"] { } {{/unless}} +{{#unless webauthn_2fa_supported}} +/* Hide `Passkey` 2FA if it is not supported */ +.providers-2fa-7 { + @extend %vw-hide; +} +{{/unless}} + {{#unless emergency_access_allowed}} /* Hide Emergency Access if not allowed */ bit-nav-item[route="settings/emergency-access"] { From 7cc4dfabbf139c56dc5c8b4f25d049fc3cf11db4 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Wed, 27 Aug 2025 20:53:56 +0200 Subject: [PATCH 11/12] Fix 2fa recovery endpoint (#6240) The newer web-vaults handle the 2fa recovery code differently. This commit fixes this by adding this new flow. Fixes #6200 Fixes #6203 Signed-off-by: BlackDex --- src/api/identity.rs | 23 +++++++++++++++++++---- src/db/models/two_factor.rs | 1 + 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/api/identity.rs b/src/api/identity.rs index d3d22805..04863b58 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -267,7 +267,7 @@ async fn _sso_login( } Some((mut user, sso_user)) => { let mut device = get_device(&data, conn, &user).await?; - let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, client_version, conn).await?; + let twofactor_token = twofactor_auth(&mut user, &data, &mut device, ip, client_version, conn).await?; if user.private_key.is_none() { // User was invited a stub was created @@ -431,7 +431,7 @@ async fn _password_login( let mut device = get_device(&data, conn, &user).await?; - let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, client_version, conn).await?; + let twofactor_token = twofactor_auth(&mut user, &data, &mut device, ip, client_version, conn).await?; let auth_tokens = auth::AuthTokens::new(&device, &user, AuthMethod::Password, data.client_id); @@ -658,7 +658,7 @@ async fn get_device(data: &ConnectData, conn: &mut DbConn, user: &User) -> ApiRe } async fn twofactor_auth( - user: &User, + user: &mut User, data: &ConnectData, device: &mut Device, ip: &ClientIp, @@ -723,7 +723,6 @@ async fn twofactor_auth( Some(TwoFactorType::Email) => { email::validate_email_code_str(&user.uuid, twofactor_code, &selected_data?, &ip.ip, conn).await? } - Some(TwoFactorType::Remember) => { match device.twofactor_remember { Some(ref code) if !CONFIG.disable_2fa_remember() && ct_eq(code, twofactor_code) => { @@ -737,6 +736,22 @@ async fn twofactor_auth( } } } + Some(TwoFactorType::RecoveryCode) => { + // Check if recovery code is correct + if !user.check_valid_recovery_code(twofactor_code) { + err!("Recovery code is incorrect. Try again.") + } + + // Remove all twofactors from the user + TwoFactor::delete_all_by_user(&user.uuid, conn).await?; + enforce_2fa_policy(user, &user.uuid, device.atype, &ip.ip, conn).await?; + + log_user_event(EventType::UserRecovered2fa as i32, &user.uuid, device.atype, &ip.ip, conn).await; + + // Remove the recovery code, not needed without twofactors + user.totp_recover = None; + user.save(conn).await?; + } _ => err!( "Invalid two factor provider", ErrorEvent { diff --git a/src/db/models/two_factor.rs b/src/db/models/two_factor.rs index 589967df..46b097bb 100644 --- a/src/db/models/two_factor.rs +++ b/src/db/models/two_factor.rs @@ -31,6 +31,7 @@ pub enum TwoFactorType { Remember = 5, OrganizationDuo = 6, Webauthn = 7, + RecoveryCode = 8, // These are implementation details U2fRegisterChallenge = 1000, From a2ad1dc7c3d28834749d4b14206838d795236c27 Mon Sep 17 00:00:00 2001 From: Stefan Melmuk <509385+stefan0xC@users.noreply.github.com> Date: Fri, 29 Aug 2025 13:14:39 +0200 Subject: [PATCH 12/12] update trivy-action to v0.33.0 (#6248) * update trivy-action to v0.33.0 * update trivy-action again with fix for setup-trivy --- .github/workflows/trivy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index d70afb66..0d52da5a 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -36,7 +36,7 @@ jobs: persist-credentials: false - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@dc5a429b52fcf669ce959baa2c2dd26090d2a6c4 # v0.32.0 + uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # v0.33.0 + b6643a2 env: TRIVY_DB_REPOSITORY: docker.io/aquasec/trivy-db:2,public.ecr.aws/aquasecurity/trivy-db:2,ghcr.io/aquasecurity/trivy-db:2 TRIVY_JAVA_DB_REPOSITORY: docker.io/aquasec/trivy-java-db:1,public.ecr.aws/aquasecurity/trivy-java-db:1,ghcr.io/aquasecurity/trivy-java-db:1