fix(updater): HTTPS für alle Anfragen erzwingen

Bisher wurde nur die Server-URL geprüft. Download-Links aus der Release-API
und Weiterleitungsziele durften auch http:// sein, womit Paket und
SHA256SUMS.txt unverschlüsselt geladen werden konnten. Jetzt wird jede
Anfrage einzeln geprüft (allow_insecure_http bleibt als Ausnahme).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 19:45:16 +02:00
co-authored by Claude Opus 5.5
parent d61e1819ed
commit 5ce2c250bb
+47 -1
View File
@@ -368,6 +368,17 @@ pub enum UpdaterStatus {
Error(String), Error(String),
} }
/// Lehnt jede URL ohne HTTPS ab, sofern unsicheres HTTP nicht ausdrücklich erlaubt ist.
fn ensure_secure_scheme(url: &url::Url, allow_insecure_http: bool) -> Result<(), String> {
match url.scheme() {
"https" => Ok(()),
"http" if allow_insecure_http => Ok(()),
scheme => Err(format!(
"Unsicheres Protokoll '{scheme}' für '{url}' abgewiesen. Es wird zwingend HTTPS verlangt."
)),
}
}
/// Erstellt einen ureq Agent mit sauberen Timeouts. /// Erstellt einen ureq Agent mit sauberen Timeouts.
fn create_http_agent() -> ureq::Agent { fn create_http_agent() -> ureq::Agent {
ureq::AgentBuilder::new() ureq::AgentBuilder::new()
@@ -384,12 +395,15 @@ fn http_get_with_auth_guard(
initial_url_str: &str, initial_url_str: &str,
token: Option<&str>, token: Option<&str>,
max_redirects: usize, max_redirects: usize,
allow_insecure_http: bool,
) -> Result<ureq::Response, String> { ) -> Result<ureq::Response, String> {
let mut current_url = url::Url::parse(initial_url_str) let mut current_url = url::Url::parse(initial_url_str)
.map_err(|e| format!("Ungültige URL '{initial_url_str}': {e}"))?; .map_err(|e| format!("Ungültige URL '{initial_url_str}': {e}"))?;
let initial_origin = current_url.origin().ascii_serialization(); let initial_origin = current_url.origin().ascii_serialization();
for _ in 0..=max_redirects { for _ in 0..=max_redirects {
// Jede Anfrage prüfen – auch Download-Links aus der API und Weiterleitungsziele
ensure_secure_scheme(&current_url, allow_insecure_http)?;
let mut req = agent.get(current_url.as_str()); let mut req = agent.get(current_url.as_str());
// Token NUR anhängen, wenn Origin (Scheme, Host, Port) identisch ist // Token NUR anhängen, wenn Origin (Scheme, Host, Port) identisch ist
@@ -500,7 +514,13 @@ impl UpdaterController {
); );
let agent = create_http_agent(); let agent = create_http_agent();
match http_get_with_auth_guard(&agent, &api_url, config.auth_token.as_deref(), 5) { match http_get_with_auth_guard(
&agent,
&api_url,
config.auth_token.as_deref(),
5,
config.allow_insecure_http,
) {
Ok(resp) => match resp.into_json::<GiteaRelease>() { Ok(resp) => match resp.into_json::<GiteaRelease>() {
Ok(release) => { Ok(release) => {
if release.prerelease && !config.include_prereleases { if release.prerelease && !config.include_prereleases {
@@ -569,6 +589,7 @@ impl UpdaterController {
&asset.browser_download_url, &asset.browser_download_url,
config.auth_token.as_deref(), config.auth_token.as_deref(),
5, 5,
config.allow_insecure_http,
) { ) {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
@@ -658,6 +679,7 @@ impl UpdaterController {
&ca.browser_download_url, &ca.browser_download_url,
config.auth_token.as_deref(), config.auth_token.as_deref(),
5, 5,
config.allow_insecure_http,
) { ) {
Ok(c_resp) => match c_resp.into_string() { Ok(c_resp) => match c_resp.into_string() {
Ok(text) => text, Ok(text) => text,
@@ -996,6 +1018,30 @@ dcdeb7ac87d9e6cb8022e5a71bc7f2c56d779b4fce4efdd5bae5be3b0214884a ink-notes_0.1.
assert!(allowed_insecure.validate_url().is_ok()); assert!(allowed_insecure.validate_url().is_ok());
} }
#[test]
fn test_https_enforced_for_every_request() {
let https = url::Url::parse("https://gitea.pansi.eu/a.deb").unwrap();
let http = url::Url::parse("http://cdn.example.com/a.deb").unwrap();
let ftp = url::Url::parse("ftp://example.com/a.deb").unwrap();
assert!(ensure_secure_scheme(&https, false).is_ok());
assert!(ensure_secure_scheme(&http, false).is_err());
assert!(ensure_secure_scheme(&http, true).is_ok());
assert!(ensure_secure_scheme(&ftp, true).is_err());
// Ein http-Download-Link aus der Release-API wird abgewiesen, bevor eine
// Verbindung aufgebaut wird
let agent = create_http_agent();
let err = http_get_with_auth_guard(
&agent,
"http://127.0.0.1:9/ink-notes.AppImage",
Some("geheim"),
5,
false,
)
.unwrap_err();
assert!(err.contains("HTTPS"), "{err}");
}
#[test] #[test]
fn test_appimage_atomic_self_update() { fn test_appimage_atomic_self_update() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();