mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-05-25 18:53:21 +00:00
Misc Updates and favicon fixes (#5993)
- Updated crates - Switched to rustls instead of native-tls Some dependency were already using rustls by default or without option. By removing native-tls we also have just one way of working here. Updated favicon fetching which now is able to fetch more icons. - Use rustls instead of native-tls This seems to work better, probably because of tls sniffing - Use different user-agent and added several other headers - Added SVG support. SVG Images will be sanitized first before stored or presented. Also, a special CSP for images will be sent to prevent scripts etc.. from SVG images. Signed-off-by: BlackDex <black.dex@gmail.com>
This commit is contained in:
committed by
GitHub
parent
ad75ce281e
commit
f125d5f1a1
Generated
+142
-135
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -6,7 +6,7 @@ name = "vaultwarden"
|
||||
version = "1.0.0"
|
||||
authors = ["Daniel García <dani-garcia@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.85.0"
|
||||
rust-version = "1.86.0"
|
||||
resolver = "2"
|
||||
|
||||
repository = "https://github.com/dani-garcia/vaultwarden"
|
||||
@@ -81,7 +81,7 @@ serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
|
||||
# A safe, extensible ORM and Query builder
|
||||
diesel = { version = "2.2.10", features = ["chrono", "r2d2", "numeric"] }
|
||||
diesel = { version = "2.2.11", features = ["chrono", "r2d2", "numeric"] }
|
||||
diesel_migrations = "2.2.0"
|
||||
diesel_logger = { version = "0.4.0", optional = true }
|
||||
|
||||
@@ -126,7 +126,7 @@ webauthn-rs = "0.3.2"
|
||||
url = "2.5.4"
|
||||
|
||||
# Email libraries
|
||||
lettre = { version = "0.11.17", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "tokio1-native-tls", "hostname", "tracing", "tokio1"], default-features = false }
|
||||
lettre = { version = "0.11.17", 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
|
||||
email_address = "0.2.9"
|
||||
|
||||
@@ -134,7 +134,7 @@ email_address = "0.2.9"
|
||||
handlebars = { version = "6.3.2", features = ["dir_source"] }
|
||||
|
||||
# HTTP client (Used for favicons, version check, DUO and HIBP API)
|
||||
reqwest = { version = "0.12.20", features = ["native-tls-alpn", "stream", "json", "gzip", "brotli", "socks", "cookies"] }
|
||||
reqwest = { version = "0.12.20", features = ["rustls-tls", "rustls-tls-native-roots", "stream", "json", "deflate", "gzip", "brotli", "zstd", "socks", "cookies", "charset", "http2", "system-proxy"], default-features = false}
|
||||
hickory-resolver = "0.25.2"
|
||||
|
||||
# Favicon extraction libraries
|
||||
@@ -142,6 +142,7 @@ html5gum = "0.7.0"
|
||||
regex = { version = "1.11.1", features = ["std", "perf", "unicode-perl"], default-features = false }
|
||||
data-url = "0.3.1"
|
||||
bytes = "1.10.1"
|
||||
svg-hush = "0.9.5"
|
||||
|
||||
# Cache function results (Used for version check and favicon fetching)
|
||||
cached = { version = "0.55.1", features = ["async"] }
|
||||
@@ -165,7 +166,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.46", features = ["secure"], default-features = false, optional = true }
|
||||
mimalloc = { version = "0.1.47", features = ["secure"], default-features = false, optional = true }
|
||||
|
||||
which = "8.0.0"
|
||||
|
||||
@@ -185,7 +186,7 @@ opendal = { version = "0.53.3", features = ["services-fs"] }
|
||||
anyhow = { version = "1.0.98", optional = true }
|
||||
aws-config = { version = "1.8.0", features = ["behavior-version-latest"], optional = true }
|
||||
aws-credential-types = { version = "1.2.3", optional = true }
|
||||
reqsign = { version = "0.16.3", optional = true }
|
||||
reqsign = { version = "0.16.4", optional = true }
|
||||
|
||||
# Strip debuginfo from the release builds
|
||||
# The debug symbols are to provide better panic traces
|
||||
@@ -276,7 +277,6 @@ macro_use_imports = "deny"
|
||||
manual_assert = "deny"
|
||||
manual_instant_elapsed = "deny"
|
||||
manual_string_new = "deny"
|
||||
match_on_vec_items = "deny"
|
||||
match_wildcard_for_single_variants = "deny"
|
||||
mem_forget = "deny"
|
||||
needless_continue = "deny"
|
||||
|
||||
@@ -5,7 +5,7 @@ vault_image_digest: "sha256:494be10bd99d9d05c7bec13dad71ad99102ea920de9a5d358752
|
||||
# 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
|
||||
xx_image_digest: "sha256:9c207bead753dda9430bdd15425c6518fc7a03d866103c516a2c6889188f5894"
|
||||
rust_version: 1.87.0 # Rust version to be used
|
||||
rust_version: 1.88.0 # Rust version to be used
|
||||
debian_version: bookworm # Debian release name to be used
|
||||
alpine_version: "3.22" # Alpine version to be used
|
||||
# For which platforms/architectures will we try to build images
|
||||
|
||||
@@ -32,10 +32,10 @@ FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:494be10bd99d9
|
||||
########################## ALPINE BUILD IMAGES ##########################
|
||||
## NOTE: The Alpine Base Images do not support other platforms then linux/amd64
|
||||
## And for Alpine we define all build images here, they will only be loaded when actually used
|
||||
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.87.0 AS build_amd64
|
||||
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.87.0 AS build_arm64
|
||||
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.87.0 AS build_armv7
|
||||
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.87.0 AS build_armv6
|
||||
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.88.0 AS build_amd64
|
||||
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.88.0 AS build_arm64
|
||||
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.88.0 AS build_armv7
|
||||
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.88.0 AS build_armv6
|
||||
|
||||
########################## BUILD IMAGE ##########################
|
||||
# hadolint ignore=DL3006
|
||||
|
||||
@@ -36,7 +36,7 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:9c207bead753dda9430bd
|
||||
|
||||
########################## BUILD IMAGE ##########################
|
||||
# hadolint ignore=DL3006
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.87.0-slim-bookworm AS build
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.88.0-slim-bookworm AS build
|
||||
COPY --from=xx / /
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.40"
|
||||
syn = "2.0.101"
|
||||
syn = "2.0.104"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "1.87.0"
|
||||
channel = "1.88.0"
|
||||
components = [ "rustfmt", "clippy" ]
|
||||
profile = "minimal"
|
||||
|
||||
+51
-3
@@ -14,6 +14,7 @@ use reqwest::{
|
||||
Client, Response,
|
||||
};
|
||||
use rocket::{http::ContentType, response::Redirect, Route};
|
||||
use svg_hush::{data_url_filter, Filter};
|
||||
|
||||
use html5gum::{Emitter, HtmlString, Readable, StringReader, Tokenizer};
|
||||
|
||||
@@ -35,11 +36,29 @@ pub fn routes() -> Vec<Route> {
|
||||
static CLIENT: Lazy<Client> = Lazy::new(|| {
|
||||
// Generate the default headers
|
||||
let mut default_headers = HeaderMap::new();
|
||||
default_headers.insert(header::USER_AGENT, HeaderValue::from_static("Links (2.22; Linux X86_64; GNU C; text)"));
|
||||
default_headers.insert(header::ACCEPT, HeaderValue::from_static("text/html, text/*;q=0.5, image/*, */*;q=0.1"));
|
||||
default_headers.insert(header::ACCEPT_LANGUAGE, HeaderValue::from_static("en,*;q=0.1"));
|
||||
default_headers.insert(
|
||||
header::USER_AGENT,
|
||||
HeaderValue::from_static(
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
|
||||
),
|
||||
);
|
||||
default_headers.insert(header::ACCEPT, HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"));
|
||||
default_headers.insert(header::ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9"));
|
||||
default_headers.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-cache"));
|
||||
default_headers.insert(header::PRAGMA, HeaderValue::from_static("no-cache"));
|
||||
default_headers.insert(header::UPGRADE_INSECURE_REQUESTS, HeaderValue::from_static("1"));
|
||||
|
||||
default_headers.insert("Sec-Ch-Ua-Mobile", HeaderValue::from_static("?0"));
|
||||
default_headers.insert("Sec-Ch-Ua-Platform", HeaderValue::from_static("Linux"));
|
||||
default_headers.insert(
|
||||
"Sec-Ch-Ua",
|
||||
HeaderValue::from_static("\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Google Chrome\";v=\"138\""),
|
||||
);
|
||||
|
||||
default_headers.insert("Sec-Fetch-Site", HeaderValue::from_static("none"));
|
||||
default_headers.insert("Sec-Fetch-Mode", HeaderValue::from_static("navigate"));
|
||||
default_headers.insert("Sec-Fetch-User", HeaderValue::from_static("?1"));
|
||||
default_headers.insert("Sec-Fetch-Dest", HeaderValue::from_static("document"));
|
||||
|
||||
// Generate the cookie store
|
||||
let cookie_store = Arc::new(Jar::default());
|
||||
@@ -53,6 +72,7 @@ static CLIENT: Lazy<Client> = Lazy::new(|| {
|
||||
.pool_max_idle_per_host(5) // Configure the Hyper Pool to only have max 5 idle connections
|
||||
.pool_idle_timeout(pool_idle_timeout) // Configure the Hyper Pool to timeout after 10 seconds
|
||||
.default_headers(default_headers.clone())
|
||||
.http1_title_case_headers()
|
||||
.build()
|
||||
.expect("Failed to build client")
|
||||
});
|
||||
@@ -561,6 +581,16 @@ async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> {
|
||||
|
||||
if buffer.is_empty() {
|
||||
err_silent!("Empty response or unable find a valid icon", domain);
|
||||
} else if icon_type == Some("svg+xml") {
|
||||
let mut svg_filter = Filter::new();
|
||||
svg_filter.set_data_url_filter(data_url_filter::allow_standard_images);
|
||||
let mut sanitized_svg = Vec::new();
|
||||
if svg_filter.filter(&*buffer, &mut sanitized_svg).is_err() {
|
||||
icon_type = None;
|
||||
buffer.clear();
|
||||
} else {
|
||||
buffer = sanitized_svg.into();
|
||||
}
|
||||
}
|
||||
|
||||
Ok((buffer, icon_type))
|
||||
@@ -581,6 +611,16 @@ async fn save_icon(path: &str, icon: Vec<u8>) {
|
||||
}
|
||||
|
||||
fn get_icon_type(bytes: &[u8]) -> Option<&'static str> {
|
||||
fn check_svg_after_xml_declaration(bytes: &[u8]) -> Option<&'static str> {
|
||||
// Look for SVG tag within the first 1KB
|
||||
if let Ok(content) = std::str::from_utf8(&bytes[..bytes.len().min(1024)]) {
|
||||
if content.contains("<svg") || content.contains("<SVG") {
|
||||
return Some("svg+xml");
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
match bytes {
|
||||
[137, 80, 78, 71, ..] => Some("png"),
|
||||
[0, 0, 1, 0, ..] => Some("x-icon"),
|
||||
@@ -588,6 +628,8 @@ fn get_icon_type(bytes: &[u8]) -> Option<&'static str> {
|
||||
[255, 216, 255, ..] => Some("jpeg"),
|
||||
[71, 73, 70, 56, ..] => Some("gif"),
|
||||
[66, 77, ..] => Some("bmp"),
|
||||
[60, 115, 118, 103, ..] => Some("svg+xml"), // Normal svg
|
||||
[60, 63, 120, 109, 108, ..] => check_svg_after_xml_declaration(bytes), // An svg starting with <?xml
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -599,6 +641,12 @@ async fn stream_to_bytes_limit(res: Response, max_size: usize) -> Result<Bytes,
|
||||
let mut buf = BytesMut::new();
|
||||
let mut size = 0;
|
||||
while let Some(chunk) = stream.next().await {
|
||||
// It is possible that there might occure UnexpectedEof errors or others
|
||||
// This is most of the time no issue, and if there is no chunked data anymore or at all parsing the HTML will not happen anyway.
|
||||
// Therfore if chunk is an err, just break and continue with the data be have received.
|
||||
if chunk.is_err() {
|
||||
break;
|
||||
}
|
||||
let chunk = &chunk?;
|
||||
size += chunk.len();
|
||||
buf.extend(chunk);
|
||||
|
||||
+52
-43
@@ -61,9 +61,11 @@ impl Fairing for AppHeaders {
|
||||
|
||||
// The `Cross-Origin-Resource-Policy` header should not be set on images or on the `icon_external` route.
|
||||
// Otherwise some clients, like the Bitwarden Desktop, will fail to download the icons
|
||||
let mut is_image = true;
|
||||
if !(res.headers().get_one("Content-Type").is_some_and(|v| v.starts_with("image/"))
|
||||
|| req.route().is_some_and(|v| v.name.as_deref() == Some("icon_external")))
|
||||
{
|
||||
is_image = false;
|
||||
res.set_raw_header("Cross-Origin-Resource-Policy", "same-origin");
|
||||
}
|
||||
|
||||
@@ -71,49 +73,56 @@ impl Fairing for AppHeaders {
|
||||
// This can cause issues when some MFA requests needs to open a popup or page within the clients like WebAuthn, or Duo.
|
||||
// This is the same behavior as upstream Bitwarden.
|
||||
if !req_uri_path.ends_with("connector.html") {
|
||||
// # Frame Ancestors:
|
||||
// Chrome Web Store: https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb
|
||||
// Edge Add-ons: https://microsoftedge.microsoft.com/addons/detail/bitwarden-free-password/jbkfoedolllekgbhcbcoahefnbanhhlh?hl=en-US
|
||||
// Firefox Browser Add-ons: https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/
|
||||
// # img/child/frame src:
|
||||
// Have I Been Pwned to allow those calls to work.
|
||||
// # Connect src:
|
||||
// Leaked Passwords check: api.pwnedpasswords.com
|
||||
// 2FA/MFA Site check: api.2fa.directory
|
||||
// # Mail Relay: https://bitwarden.com/blog/add-privacy-and-security-using-email-aliases-with-bitwarden/
|
||||
// app.simplelogin.io, app.addy.io, api.fastmail.com, quack.duckduckgo.com
|
||||
let csp = format!(
|
||||
"default-src 'none'; \
|
||||
font-src 'self'; \
|
||||
manifest-src 'self'; \
|
||||
base-uri 'self'; \
|
||||
form-action 'self'; \
|
||||
object-src 'self' blob:; \
|
||||
script-src 'self' 'wasm-unsafe-eval'; \
|
||||
style-src 'self' 'unsafe-inline'; \
|
||||
child-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
|
||||
frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
|
||||
frame-ancestors 'self' \
|
||||
chrome-extension://nngceckbapebfimnlniiiahkandclblb \
|
||||
chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh \
|
||||
moz-extension://* \
|
||||
{allowed_iframe_ancestors}; \
|
||||
img-src 'self' data: \
|
||||
https://haveibeenpwned.com \
|
||||
{icon_service_csp}; \
|
||||
connect-src 'self' \
|
||||
https://api.pwnedpasswords.com \
|
||||
https://api.2fa.directory \
|
||||
https://app.simplelogin.io/api/ \
|
||||
https://app.addy.io/api/ \
|
||||
https://api.fastmail.com/ \
|
||||
https://api.forwardemail.net \
|
||||
{allowed_connect_src};\
|
||||
",
|
||||
icon_service_csp = CONFIG._icon_service_csp(),
|
||||
allowed_iframe_ancestors = CONFIG.allowed_iframe_ancestors(),
|
||||
allowed_connect_src = CONFIG.allowed_connect_src(),
|
||||
);
|
||||
let csp = if is_image {
|
||||
// Prevent scripts, frames, objects, etc., from loading with images, mainly for SVG images, since these could contain JavaScript and other unsafe items.
|
||||
// Even though we sanitize SVG images before storing and viewing them, it's better to prevent allowing these elements.
|
||||
String::from("default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'; script-src 'none'; frame-src 'none'; object-src 'none")
|
||||
} else {
|
||||
// # Frame Ancestors:
|
||||
// Chrome Web Store: https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb
|
||||
// Edge Add-ons: https://microsoftedge.microsoft.com/addons/detail/bitwarden-free-password/jbkfoedolllekgbhcbcoahefnbanhhlh?hl=en-US
|
||||
// Firefox Browser Add-ons: https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/
|
||||
// # img/child/frame src:
|
||||
// Have I Been Pwned to allow those calls to work.
|
||||
// # Connect src:
|
||||
// Leaked Passwords check: api.pwnedpasswords.com
|
||||
// 2FA/MFA Site check: api.2fa.directory
|
||||
// # Mail Relay: https://bitwarden.com/blog/add-privacy-and-security-using-email-aliases-with-bitwarden/
|
||||
// app.simplelogin.io, app.addy.io, api.fastmail.com, api.forwardemail.net
|
||||
format!(
|
||||
"default-src 'none'; \
|
||||
font-src 'self'; \
|
||||
manifest-src 'self'; \
|
||||
base-uri 'self'; \
|
||||
form-action 'self'; \
|
||||
object-src 'self' blob:; \
|
||||
script-src 'self' 'wasm-unsafe-eval'; \
|
||||
style-src 'self' 'unsafe-inline'; \
|
||||
child-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
|
||||
frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
|
||||
frame-ancestors 'self' \
|
||||
chrome-extension://nngceckbapebfimnlniiiahkandclblb \
|
||||
chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh \
|
||||
moz-extension://* \
|
||||
{allowed_iframe_ancestors}; \
|
||||
img-src 'self' data: \
|
||||
https://haveibeenpwned.com \
|
||||
{icon_service_csp}; \
|
||||
connect-src 'self' \
|
||||
https://api.pwnedpasswords.com \
|
||||
https://api.2fa.directory \
|
||||
https://app.simplelogin.io/api/ \
|
||||
https://app.addy.io/api/ \
|
||||
https://api.fastmail.com/ \
|
||||
https://api.forwardemail.net \
|
||||
{allowed_connect_src};\
|
||||
",
|
||||
icon_service_csp = CONFIG._icon_service_csp(),
|
||||
allowed_iframe_ancestors = CONFIG.allowed_iframe_ancestors(),
|
||||
allowed_connect_src = CONFIG.allowed_connect_src(),
|
||||
)
|
||||
};
|
||||
|
||||
res.set_raw_header("Content-Security-Policy", csp);
|
||||
res.set_raw_header("X-Frame-Options", "SAMEORIGIN");
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user