diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md
index 28aed43..b814879 100644
--- a/THIRD_PARTY_NOTICES.md
+++ b/THIRD_PARTY_NOTICES.md
@@ -2,7 +2,9 @@
Firelink distributes separate executable tools. Firelink's MIT license does not replace their licenses.
-Exact versions, target hashes, sources, and build descriptions are pinned in `engines.lock.json`.
+Exact versions, target hashes, sources, and build descriptions are pinned in
+`engines.lock.json` for the packaged macOS payload and `engine-sources.lock.json`
+for the provisioned Windows and Linux payloads.
## Bundled fonts
@@ -27,9 +29,14 @@ License text:
- Source:
- License: GNU General Public License version 2 or later
-Corresponding source for the distributed version is available from the source link and release tag listed in `engines.lock.json`. Firelink release notes must retain that source reference.
+Corresponding source for the distributed version is available from the source
+link and release tag listed in the applicable engine lock file. Firelink release
+notes must retain that source reference.
-Linux x64 uses a checksum-pinned musl static build produced from upstream aria2 by . Builder source and upstream tag are recorded in `engine-sources.lock.json`.
+Windows and Linux use checksum-pinned upstream aria2 source archives built with
+Firelink's reviewed native-DNS, network-target, and allocation-telemetry patch.
+The archive, patch checksum, and build provenance are recorded in
+`engine-sources.lock.json`.
## FFmpeg
@@ -56,4 +63,6 @@ Firelink uses a self-contained PyInstaller onedir distribution. Embedded Python
Engine payloads may contain OpenSSL, SQLite, c-ares, libssh2, gettext/libintl, zstd, and other runtime libraries. Their copyright and license notices remain part of their source distributions and embedded package metadata.
-Release engineering must review each newly added target payload before adding its hashes to `engines.lock.json`. Missing provenance or license data blocks release.
+Release engineering must review each newly added target payload before adding its
+hashes to the applicable engine lock file. Missing provenance or license data
+blocks release.
diff --git a/engine-sources.lock.json b/engine-sources.lock.json
index 6dff525..0d9bb99 100644
--- a/engine-sources.lock.json
+++ b/engine-sources.lock.json
@@ -13,9 +13,9 @@
"sha256": "15e5300b0ba3c3695a7621d90160a746ec9e710228cee639afa9d580f6e3cd11"
},
"ffmpeg": {
- "version": "9.0.1-11-ge47273f4d9",
- "url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-09-04-14-01/ffmpeg-n9.0.1-11-ge47273f4d9-win64-gpl-9.0.zip",
- "sha256": "41d735c9364a8deda25b3bd5f05abf37720316be9495edfe94f51bc088ce9d86"
+ "version": "9.0.1-26-g5c8e7e2433",
+ "url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-09-06-13-06/ffmpeg-n9.0.1-26-g5c8e7e2433-win64-gpl-9.0.zip",
+ "sha256": "dd232ccf8661f837a1faa5f534a1a0bdbdb25c42afe79391e8345154df78f791"
},
"aria2c": {
"version": "1.37.0-firelink-native-dns-v1",
@@ -45,9 +45,9 @@
"sha256": "394f07f4da2bebe6ce6f1e7ce0fa16429b29b08c35e3fac3fe25972676dff4b2"
},
"ffmpeg": {
- "version": "9.0.1-11-ge47273f4d9",
- "url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-09-04-14-01/ffmpeg-n9.0.1-11-ge47273f4d9-linux64-gpl-9.0.tar.xz",
- "sha256": "1a4fa0f89f690bd81bddbb7cf3a65554095e8e9f9dc98693494e6f5fb97918c5"
+ "version": "9.0.1-26-g5c8e7e2433",
+ "url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-09-06-13-06/ffmpeg-n9.0.1-26-g5c8e7e2433-linux64-gpl-9.0.tar.xz",
+ "sha256": "e60c4187c792cc35d2558adbae5582c470713f5afed7212999200340f1394f8d"
},
"aria2c": {
"version": "1.37.0-firelink-native-dns-v1",
diff --git a/package-lock.json b/package-lock.json
index 3a13fe2..80fa463 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,7 +25,7 @@
"@tauri-apps/plugin-notification": "^2.4.0",
"@tauri-apps/plugin-opener": "^2.5.5",
"i18next": "^26.4.2",
- "lucide-react": "^1.41.0",
+ "lucide-react": "^1.42.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-i18next": "^17.0.13",
@@ -2043,9 +2043,9 @@
}
},
"node_modules/lucide-react": {
- "version": "1.41.0",
- "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.41.0.tgz",
- "integrity": "sha512-6lksP35l6KszDKUeRTi4LV7i6DEe0Yzl2ALJm9j4c5xEYN91GdW1xGsawGMOg2mgjF5GHBVX8pKX9kP+cWsP3Q==",
+ "version": "1.42.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.42.0.tgz",
+ "integrity": "sha512-b3jprplnoLS8n5etw1z8xODe3hF/yjKATrTitsrKrnjUhCef5BdDct6Ppv3zVvzFwmtfWgLO6XNM3C9fAD93ug==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
diff --git a/package.json b/package.json
index 1c800c7..d86b7ac 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,7 @@
"@tauri-apps/plugin-notification": "^2.4.0",
"@tauri-apps/plugin-opener": "^2.5.5",
"i18next": "^26.4.2",
- "lucide-react": "^1.41.0",
+ "lucide-react": "^1.42.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-i18next": "^17.0.13",
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 76cb7a8..0aea1c7 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -749,6 +749,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "core_detect"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
+
[[package]]
name = "cpubits"
version = "0.1.1"
@@ -784,18 +790,18 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
-version = "0.5.16"
+version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e"
+checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
-version = "0.8.22"
+version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
+checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
[[package]]
name = "crunchy"
@@ -1240,11 +1246,17 @@ checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
[[package]]
name = "encoding_rs"
-version = "0.8.35"
+version = "0.8.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+checksum = "2a7a45518d2863d18aa47f4a0cf9faec2aa4304cc09df5e41299f276b3ad135e"
dependencies = [
"cfg-if",
+ "core_detect",
+ "multiversion",
+ "multiversion_no_op",
+ "rustversion",
+ "scopeguard",
+ "simdutf8",
]
[[package]]
@@ -1955,9 +1967,9 @@ dependencies = [
[[package]]
name = "hashlink"
-version = "0.12.1"
+version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32069d97bb81e38fa67eab65e3393bf804bb85969f2bc06bf13f64aef5aba248"
+checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb"
dependencies = [
"hashbrown 0.17.1",
]
@@ -2329,9 +2341,9 @@ dependencies = [
[[package]]
name = "ipnet"
-version = "2.12.1"
+version = "2.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
+checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0"
[[package]]
name = "is-docker"
@@ -2795,6 +2807,34 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "multiversion"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edb7f0ff51249dfda9ab96b5823695e15a052dc15074c9dbf3d118afaf2c201"
+dependencies = [
+ "multiversion-macros",
+ "target-features",
+]
+
+[[package]]
+name = "multiversion-macros"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b093064383341eb3271f42e381cb8f10a01459478446953953c75d24bd339fc0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "target-features",
+]
+
+[[package]]
+name = "multiversion_no_op"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d"
+
[[package]]
name = "ndk"
version = "0.9.0"
@@ -3407,13 +3447,13 @@ checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
[[package]]
name = "plist"
-version = "1.10.0"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85"
+checksum = "2896bade328c13f7042a297ea5ac5b0951f6cf989dea5f32c2fd98da398195cb"
dependencies = [
- "base64 0.22.1",
+ "base64 0.23.1",
"indexmap 2.14.2",
- "quick-xml",
+ "quick-xml 0.42.0",
"serde",
"time",
]
@@ -3586,6 +3626,15 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "quick-xml"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "quote"
version = "1.0.47"
@@ -3870,9 +3919,9 @@ dependencies = [
[[package]]
name = "rustls"
-version = "0.23.43"
+version = "0.23.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
+checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba"
dependencies = [
"once_cell",
"ring",
@@ -4666,6 +4715,12 @@ dependencies = [
"syn 2.0.119",
]
+[[package]]
+name = "target-features"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1bbb9f3c5c463a01705937a24fdabc5047929ac764b2d5b9cf681c1f5041ed5"
+
[[package]]
name = "target-lexicon"
version = "0.12.16"
@@ -5952,7 +6007,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0"
dependencies = [
"proc-macro2",
- "quick-xml",
+ "quick-xml 0.41.0",
"quote",
]
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 420d82d..2e92bdb 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -32,7 +32,7 @@ serde_json = "1"
tokio = { version = "1", features = ["fs", "process", "io-util", "rt", "rt-multi-thread", "macros", "sync", "time"] }
regex = "1.10"
reqwest = { version = "0.13", default-features = false, features = ["rustls-no-provider", "json", "stream", "socks"] }
-rustls = { version = "0.23.43", default-features = false, features = ["ring", "std", "tls12"] }
+rustls = { version = "0.23.44", default-features = false, features = ["ring", "std", "tls12"] }
uuid = { version = "1", features = ["v4"] }
ts-rs = { version = "12", features = ["serde-compat", "uuid-impl"] }
tauri-plugin-notification = "2.4.0"
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index cc11ffb..ed5af23 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -1035,6 +1035,19 @@ fn drain_media_output_lines(buffer: &mut String, chunk: &str) -> Vec {
lines
}
+fn append_bounded_stderr_tail(tail: &mut String, chunk: &str, max_bytes: usize) {
+ tail.push_str(chunk);
+ if tail.len() <= max_bytes {
+ return;
+ }
+
+ let start = tail.len() - max_bytes;
+ let boundary = (start..tail.len())
+ .find(|&index| tail.is_char_boundary(index))
+ .unwrap_or(tail.len());
+ tail.drain(..boundary);
+}
+
fn flush_media_output_line(buffer: &mut String) -> Option {
let line = std::mem::take(buffer);
(!line.trim().is_empty()).then_some(line)
@@ -5052,10 +5065,7 @@ pub(crate) async fn start_media_download_internal(
}
Some(tauri_plugin_shell::process::CommandEvent::Stderr(line_bytes)) => {
let chunk = String::from_utf8_lossy(&line_bytes);
- stderr_tail.push_str(&chunk);
- if stderr_tail.len() > STDERR_TAIL {
- stderr_tail = stderr_tail.split_off(stderr_tail.len() - STDERR_TAIL);
- }
+ append_bounded_stderr_tail(&mut stderr_tail, &chunk, STDERR_TAIL);
for line in drain_media_output_lines(&mut stderr_buffer, &chunk) {
if let Some(progress) = parse_media_progress_line(&line) {
emit_media_progress(
@@ -14249,7 +14259,7 @@ mod tests {
use super::{
aggregate_media_byte_progress, aggregate_media_fraction, append_ytdlp_config_option,
- append_ytdlp_http_headers,
+ append_bounded_stderr_tail, append_ytdlp_http_headers,
build_media_format_options,
collect_download_uris, drain_media_output_lines, filename_from_content_disposition,
filename_from_url_disposition_query, filename_from_url_path, is_excluded_yt_dlp_format,
@@ -18035,6 +18045,24 @@ mod tests {
assert!(buffer.is_empty());
}
+ #[test]
+ fn bounds_stderr_tail_without_splitting_utf8_across_appends() {
+ const MAX_BYTES: usize = 7;
+ let mut tail = String::new();
+
+ for chunk in ["a", "🙂", "é", "🙂", "b", "🙂", "c", "🙂"] {
+ append_bounded_stderr_tail(&mut tail, chunk, MAX_BYTES);
+ assert!(tail.len() <= MAX_BYTES);
+ }
+
+ for _ in 0..32 {
+ append_bounded_stderr_tail(&mut tail, "🙂", MAX_BYTES);
+ assert!(tail.len() <= MAX_BYTES);
+ }
+
+ assert_eq!(tail, "🙂");
+ }
+
#[test]
fn parses_structured_ytdlp_numeric_percent_without_total() {
let line = format!(