Repository navigation
Conversation
cargo-audit fails on every PR: zip 2.4.2 in elastos/Cargo.lock and in capsules/object-provider/Cargo.lock (through its elastos-server path dependency) has three advisories against ZipArchive::extract and the stream extract API. The runtime calls neither; it reads each entry itself. zip 9.0.4 is the patched release. - elastos-server uses zip 9.0.4, still without default features. 9.x has no separate flate2 feature; deflate-flate2 now enables the dependency. - Both lockfiles change only for zip: nix 0.31.3 and typed-path come in, derive_arbitrary goes. `cargo update -p zip` also moved unrelated range dependencies (windows-sys, getrandom); those lines stay as they were, and `cargo metadata --locked` accepts both files. - ZipFile::name now returns a Result. The ZIP readers report an error as "invalid ZIP archive entry". - Whole-archive extract keeps its rule for entry names. zip 9 enclosed_name turns "/a.txt" into "a.txt" and folds "x/../a.txt" into "a.txt", so an entry that extract refuses today would be written. The runtime now checks the name with the rule zip 2.x applied: no NUL byte, no root or drive prefix, no climb above the archive root. test_library_provider_extract_zip_refuses_absolute_dotdot_and_symlink_entries covers absolute, "..", inner "..", backslash and symlink entries. It passes on zip 2.4.2 and with this change, and fails on the Absolute.zip case when extract uses zip 9 enclosed_name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6Qn9RX7LDqrwiAfNC2F2K
…bounded retry 5051505 fixed one test. Four other test sites still took the journal directory lock with one try_lock(). A child forked on another test thread can briefly hold a copy of an earlier lock fd (#381), so they failed with "WouldBlock" in CI (live_transaction_creator_preserves_partial_files_and_bindings, journal.rs:4336, run 39018319086). They now use lock_journal_directory, the provider's own bounded retry. Test code only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6Qn9RX7LDqrwiAfNC2F2K
|
This PR now has a second commit,
This is the #381 flake. Develop 5051505 fixed one test. The same single Generated by Claude Code |
Brings develop's reviewed cargo-audit exception (9be009b) so the next commit can remove it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6Qn9RX7LDqrwiAfNC2F2K
zip 9.0.4 is past the first fixed release for RUSTSEC-2026-0367/0368/0369 (upstream GHSA-4684-h2q9-2vcp names 9.0.1). The three IDs were the only entries in elastos/.cargo/audit.toml, so the file goes, as #414 asks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6Qn9RX7LDqrwiAfNC2F2K
|
Closes #414. This PR is the zip upgrade that #414 describes. Develop now has the reviewed 0.8.0 exception (9be009b), and this PR is meant to land after 0.8.0, as #414 plans. Two new commits:
With the file deleted, the CI-style audit loop (cargo-audit 0.22.2) passes on all 34 tracked lockfiles: How this PR meets #414's Done means:
Note for whoever merges: the open draft PRs #394–#399, #396, #67, #68, #413, #415 and #418 have this zip change merged in, so each of them depends on this PR. If one of them has to land in 0.8.0 before this PR, take the zip change back out of that branch first. Generated by Claude Code |
…very platform zip_entry_enclosed_path read the entry name with std Path, so on Linux and macOS a drive letter is not a prefix and `\` is not a separator. `C:/x` passed the guard and extract wrote a folder named "C:". `C:\x`, `\\server\share\x` and `\\?\C:\x` were refused only later, by the object name check, because they hold a backslash. The guard now reads the name the same way on every platform: a leading `/` or `\` (this includes UNC and `\\?\` names) or a drive letter (`C:\x`, `C:/x`, `C:x`) is refused, and `\` counts as a separator when it counts the depth. A name that starts with a letter and a colon, such as `a:b.txt`, is now refused too. The tamper test adds `C:\x`, `C:/x`, `\\server\share\x` and `\\?\C:\x`, and the backslash case now expects the guard's message. A new unit test checks the guard alone. With the old guard, the unit test lists 10 accepted hostile names and the tamper test writes `C:/drive-slash-escape.txt`. With zip 9 enclosed_name in place of the guard, all four Windows names are written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6Qn9RX7LDqrwiAfNC2F2K
|
Generated by Claude Code |
|
Round 3 review: PASS (B+). Guard and tamper tests pass at 5873d2f. |
|
CI on 5873d2f: Generated by Claude Code |
|
Decision (Sash): one path rule for list, preview and selected-extract too, so C:/x is refused by every archive reader. |
No textual conflicts. The branch keeps zip 9 from #417; develop still pins zip 2.4.2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6Qn9RX7LDqrwiAfNC2F2K
Decision on #417: list, preview and selected extract use the same path rule as whole-archive extract, so `C:/x` is refused by every archive reader. Before, list, preview and selected extract read an entry name with std Path (normalized_archive_entry_path). On Linux and macOS that reads `C:/x` as a folder named "C:": the list showed the entry as safe, preview returned its bytes, and selected extract wrote Imports/C:/x. Whole-archive tar extract had no guard: it wrote `C:/x`, and refused `C:\x` and `\\server\share\x` only through the object name check ("invalid library object name"). archive_entry_path is now the one rule. All ten call sites use it: the ZIP and tar readers for list, preview, selected extract and whole extract, and the entry a person asks to preview or to extract. It is the same on every platform: a NUL byte, any `\`, a leading `/`, a drive letter and any `..` part are refused with "library archive entry path must be relative and safe". It replaces zip_entry_enclosed_path. Tar names are read from the raw header bytes (path_bytes), so the rule sees the same text on every platform. The list still shows the archive. A refused entry is a row with kind "blocked", safety status "blocked" and the rule's reason. It cannot be previewed or extracted: the request is refused by the rule, and each reader skips or blocks the entry by the same rule. Behaviour change: a name with a `\` anywhere is refused with the rule's message by every reader. Before, whole extract refused it on Linux with "invalid library object name"; on Windows, std Path read `\` as a separator. Tests: test_library_provider_every_archive_reader_refuses_root_drive_unc_and_climb_names (gateway, ZIP and tar, five hostile names, every reader) and archive_readers_block_root_drive_unc_and_climb_entry_names (readers called past the request check). Before this change they list 14 and 7 failed checks. With any one of the ten call sites routed back to the old code, at least one of them fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6Qn9RX7LDqrwiAfNC2F2K
|
Generated by Claude Code |
Brings develop to bfefe92, which fixes develop's own #430 Home link test (it now uses Library, which every platform launches). This PR's pull_request CI merges develop, so its test-elastos runs on 13cdcad / 16a132a tested develop 8ea4aac/0dbba668 and failed on that test (403 vs 200, home_system.rs:11964). No conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6Qn9RX7LDqrwiAfNC2F2K
|
Merged develop at Generated by Claude Code |
|
Re-review at 8bb4876: PASS (A). Only a develop merge; archive guards and tampers still hold; cargo audit clean; all CI jobs green. |
What
Move
zipfrom 2.4.2 to 9.0.4 inelastos-server, remove the cargo-audit exception, and keep the runtime's own rule for archive entry names. Since16a132a8that rule is one function used by every Library archive reader (Sash's decision on this PR: "one path rule for list, preview and selected-extract too, so C:/x is refused by every archive reader"). Closes #414 (Anders). It lands after 0.8.0, and first of the open runtime PRs (Sasha's decision).Head:
8bb48764. Base: develop (merged in atbfefe92f,8bb48764).What this PR carries
756dad1cZipFile::name()returnsResult, and the guardzip_entry_enclosed_pathwith its tamper testaaa1c69ccapsules/model-provider/src/journal.rs): the four held-lock journal tests take the lock with the bounded retry, sotest-capsulescan run (#381 flake). No effect once #419 lands455ff7229be009b1, which brings Anders' 0.8.0 exception fileelastos/.cargo/audit.tomlbb8afd7delastos/.cargo/audit.toml(the three zip IDs were its only entries), as #414 asks5873d2f716a132a8archive_entry_path, for every archive reader: list, preview, selected extract and whole extract, ZIP and tar. It replaceszip_entry_enclosed_pathand thePath-based check of the other readers8bb48764origin/developatbfefe92f(develop's fix for its own #430 Home link test; no conflicts; zip stays 9.0.4). At8bb48764:cargo test -p elastos-server --lib -- home_external_link archive zip36 passed.Why
cargo-auditfails on every runtime PR. zip 2.4.2 is inelastos/Cargo.lockand incapsules/object-provider/Cargo.lock(through itselastos-serverpath dependency). It has three advisories:ZipArchive::extractcreates symbolic links whose targets are outside the destination)The patched versions are
>= 9.0.1(#414, and upstream GHSA-4684-h2q9-2vcp names 9.0.1 as the first fixed release; RustSec lists 9.0.0). This pins 9.0.4.Reachability (why this is a dependency bump, not an incident)
ZipArchive::extractor the stream extract API. It reads each entry itself withby_index(elastos/crates/elastos-server/src/library.rs:5422extract_zip_archive, and the preview, list and selected readerspreview_zip_archive_entrylibrary.rs:4232,list_zip_archive_entries:4440,collect_selected_zip_writes:4930).unix_mode. No file mode from an archive is applied.extract_zip_archivestops on any entry that is not a file or a folder (library.rs:5448, "library archive extraction rejects non-file archive entries"). The list and selected-extract readers mark it rejected with the same reason (library.rs:4424,:4478,:4916,:4967).The trap in zip 9:
enclosed_namechangedzip 9
enclosed_namereads the name as a Windows path, strips a root, drive or UNC prefix, and foldsx/../a.txtintoa.txt. A plain bump would makeextract_zip_archivewrite/x,C:\x,C:/x,\\server\share\xand\\?\C:\xasx, andNested/../xasx, into the destination (negative run 2 below). The runtime does not callenclosed_name.One rule for every archive reader (
16a132a8)The rule:
archive_entry_path(library.rs:4511). It is the same on every platform. It refuses, with "library archive entry path must be relative and safe", a name that:\anywhere (this includesC:\x,\\server\share\x,\\?\C:\xand..\x),/(this includes//server/share/x),C:/x,C:x),..part.It returns the path with
/separators and no empty or.parts. An empty path is the archive root (for example./): whole extract skips it, andnormalized_archive_entry_path(library.rs:4530, the same rule plus "must not be empty") refuses it for list, preview and selected extract. Tar names are read from the raw header bytes withtar_entry_name(library.rs:4541,Entry::path_bytes, GNU long names and PAXpathincluded), so the rule sees the same text on every platform.Every call site (lines at
16a132a8):list_zip_archive_entrieslibrary.rs:4453list_tar_archive_entries:4399archive_preview_entry_for_object:4176(both formats)preview_zip_archive_entry:4246preview_tar_archive_entry:4288selected_archive_entries:4855(both formats)collect_selected_zip_writes:4945collect_selected_tar_writes:4890extract_zip_archive:5437extract_tar_archive:5391The WebSpace archive operations (
library.rs:1108-1140,extract_archive_entries_to_webspace_destination) call the same list, preview and selected-extract functions.How a refused entry is shown: the list still shows the archive. The entry is a row with
kind: "blocked",safety.status: "blocked",safety.reasonset to the rule's message, andpathset to the raw name (trimmed) for display only. It can never be previewed or extracted: a request that names it is refused by the rule, and each reader skips (preview) or blocks (selected extract, listed underblocked) the entry by the same rule. Whole extract refuses the whole archive with the rule's message.What was true before
16a132a8(shown by the new tests on the old code, below): list, preview and selected extract usedstd::path::Path, so on Linux and macOSC:/xwas a folder namedC:. The list showed it as safe, preview returned its bytes, and selected extract wroteImports/C:/x. Whole tar extract had no guard: it wroteC:/x, and refusedC:\xand\\server\share\xonly through the object name check ("invalid library object name"). Whole ZIP extract already refused all of them (5873d2f7).Behaviour changes:
\anywhere is refused with the rule's message by every reader. Before, on Linux whole extract refused it with "invalid library object name"; on Windowsstd::path::Pathread\as a separator.5873d2f7: an entry whose name starts with a letter and a colon, such asa:b.txt, is refused (on Windows it is a drive-relative path). A colon later in the name, such asNested/C:a.txt, is allowed, as before.Tamper tests
test_library_provider_every_archive_reader_refuses_root_drive_unc_and_climb_names(elastos/crates/elastos-server/src/api/gateway_tests/library.rs:4117, new). For ZIP and for tar, one archive per hostile nameC:/x,C:\x,\\server\share\x,/etc/xand../x(each next to a safesafe.txt), through the provider routes: the list row is blocked with the rule's reason; preview of the name is refused with the rule's message and preview of its last part finds nothing; selected extract of the name is refused with the rule's message, and of its last part writes nothing and reports the entry as blocked; whole extract is refused with the rule's message; and no file named*escape*exists anywhere under the data dir. Tar archives are written with raw header names (tar_with_raw_entry_names,:4102), becausetar::Builderrefuses these names.archive_readers_block_root_drive_unc_and_climb_entry_names(library.rs:7227, new). The same names, ZIP and tar, with the list, preview and selected-extract readers called directly, past the request check: the row is blocked, preview of the raw name or its last part returns nothing, selected extract writes nothing and blocks one entry, andselected_archive_entriesrefuses the name.test_library_provider_extract_zip_refuses_absolute_dotdot_and_symlink_entries(gateway_tests/library.rs:3891, unchanged, passes): one hostile archive per entry/absolute-escape.txt,../dotdot-escape.txt,Nested/../inner-escape.txt,..\backslash-escape.txt,C:\drive-backslash-escape.txt,C:/drive-slash-escape.txt,\\server\share\unc-escape.txt,\\?\C:\verbatim-escape.txtand a symlink entry; refusal with the expected message and no file or symlink on disk for each.archive_entry_path_refuses_root_drive_and_climb_on_every_platform(library.rs:7185; it waszip_entry_enclosed_path_refuses_…and now calls the rule that replaces that function). 16 hostile names (the 15 before plusNested/../a.txt, which the old guard passed toarchive_entry_uri) are refused, and 6 ordinary names give the expected path (./a.txt->a.txt,Nested/->Nested,Nested/C:a.txtunchanged,./-> the root).Other API changes
ZipFile::name()returns aResultin 9.x. All four fallible entry-name calls inlibrary.rsare handled, and each name then goes through the rule (lines at16a132a8):name()atlibrary.rs:5435, thenarchive_entry_pathat:5437;name()at:4243, thennormalized_archive_entry_pathat:4246;name()at:4450, thennormalized_archive_entry_pathat:4453;name()at:4942, thennormalized_archive_entry_pathat:4945.The test helper
gateway_tests/library.rs:895unwraps it.Behaviour change in form: each of those four calls ends in
.map_err(|err| anyhow!("invalid ZIP archive entry: {err}"))?. Ifname()fails, the whole list, preview or extract fails with "invalid ZIP archive entry: …" instead of showing that one entry as blocked. This fails closed. Today no name can reach that error: zip 9.0.4 uses the bytes as UTF-8 when they are valid (zip-9.0.4/src/types.rs:122-131), otherwise every byte has a CP437 character (zip-9.0.4/src/cp437.rs:17-31; its onlyErris for all-ASCII bytes, which are always valid UTF-8 and so never reach it). zip 2.4.2name()returned&strand could not fail (zip-2.4.2/src/read.rs:1543). So no archive changes behaviour with 9.0.4, and no test can show the error path; it is untested. Non-ASCII names are follow-up Library: decide and test how ZIP entry names with non-ASCII characters are decoded and checked #432.9.x has no separate
flate2feature.deflate-flate2enables the dependency. Default features stay off.Non-ASCII names decode differently. zip 2.4.2 used the language-encoding flag: flag set means lossy UTF-8, flag clear means CP437. zip 9.0.4 (
src/types.rs:122) uses UTF-8 when the bytes are valid UTF-8, whatever the flag, and CP437 otherwise. Every name still goes through the same rule, so this changes only the displayed or written name, not what is refused. No test covers non-ASCII names: follow-up Library: decide and test how ZIP entry names with non-ASCII characters are decoded and checked #432 (its line numbers are at5873d2f7).Lockfiles (hand-trimmed, cargo-accepted)
cargo update -p zipalso moves unrelated range dependencies (about 16 lines ofwindows-sys/getrandomchurn). Those lines stay as they were. Each lockfile changes only for zip:zip2.4.2 -> 9.0.4nix0.31.3 andtyped-path0.12.3 are added;derive_arbitraryis removed"nix"dependency references become"nix 0.29.0"because twonixversions now existcargo metadata --lockedaccepts both files and does not change them (checked at 756dad1 and again after the develop merge 455ff72).16a132a8changes no lockfile.Verification
At
16a132a8,cd elastos && cargo test -p elastos-server --lib -- zip archive(34 tests whose name holds "zip" or "archive"; output trimmed to the name-check tests):cargo clippy -p elastos-server --all-targets -- -D warnings,cargo fmt --all -- --check,git diff --check,node scripts/public-copy-entropy-check.mjsandnode scripts/check-product-data.mjspass.Negative runs for
16a132a8(not committed)A. The two new tests on the code at
5873d2f7(only the tests added):B. Each of the ten call sites reverted on its own, at
16a132a8, by a temporary switch that sends that one call to the code at5873d2f7(thePath::components()check for list, preview and selected extract; no rule for whole extract). The archive and tamper tests above were run once per site. Every site fails at least one test::4453zip "C:/drive-slash-escape.txt" list: [... "status": "safe" ...]:4399tar "C:/drive-slash-escape.txt" list: [... "status": "safe" ...]:4176zip "C:/drive-slash-escape.txt" preview "C:/drive-slash-escape.txt": status="error" message="library archive preview entry not found"(the reader then finds nothing, but the request is not refused by the rule):4246Case.zip "C:/drive-slash-escape.txt" preview "C:/drive-slash-escape.txt": "escape":4288Case.tar "C:/drive-slash-escape.txt" preview "C:/drive-slash-escape.txt": "escape":4855selection "C:/drive-slash-escape.txt" accepted:4945Case.zip "C:/drive-slash-escape.txt" selected: writes=[File { path: "C:/drive-slash-escape.txt", ... }]:4890Case.tar "C:/drive-slash-escape.txt" selected: writes=[File { path: "C:/drive-slash-escape.txt", ... }]:5437…extract_zip_refuses_absolute_dotdot_and_symlink_entrieszip "C:/drive-slash-escape.txt" extract: status="ok"andhostile entry was written: .../Documents/Case0/C:/drive-slash-escape.txt:5391tar "C:/drive-slash-escape.txt" extract: status="ok"andhostile entry was written: ...Negative runs for
5873d2f7(not committed)756dad1cbody (thePath::components()loop withComponent::Prefix(_) | Component::RootDir => return None):With the expected message of the four backslash cases set back to "invalid library object name", the old guard writes the
C:/entry:extract_zip_archivewithzip_entry_enclosed_path(&raw_name)replaced by zip 9file.enclosed_name(). The committed test stops at the first case:The same run, with the test changed to print each case and go on:
Audit
CI-style audit (the
cargo-auditjob loop over every trackedCargo.lock, cargo-audit 0.22.2) withaudit.tomldeleted: 34 lockfiles,OVERALL failed=0(author check at bb8afd7, see the PR comment of 17 Oct 10:10; not re-run for 5873d2f or 16a132a, which change no lockfile). CIcargo-auditpassed on bb8afd7 (run 39023894827, job 117370971768).CI
16a132a8) can fail in develop's own Home link test (403 vs 200), which is not this PR's.8bb48764merges develop at bfefe92, which fixes the test.source-home-linux (ubuntu-24.04)failed on 5873d2f in the model-repair journey (run 39040338532, job 117453636062); tracked in Installed journeys in source-home-linux (ubuntu-24.04) fail intermittently, on develop too #433, not this PR's (see the PR comment of 17 Oct 20:52).Merged into the open runtime draft PRs
So their CI can run past
cargo-audit, this branch reaches these branches by plaingit merge, no rewrite. Not #407 and not #408. These merges carry756dad1conly, not the later commits (5873d2f7,16a132a8and the others); the orchestrator's final cascade brings the rest.On develop (merge of
fix/zip-9-rustsec):cursor/launcher-contract-1297519a2df9cursor/wsl2-runtime-notes-1297ba88f427feat/library-set-desktop-background7e1b4d9ffeat/assistant-dock-mark-0.7.167da7137fix/ci-journey-failure-causes69e2bae3fix/clipboard-source-gate47281640fix/browser-helper-leftover-notice241ba6a7Stacked PRs get it through their base (merge of the updated base branch):
028c6137b431634175175f97d40a2f5eWhen this PR lands in develop, those merges become no-ops.
Gaps
Nested/C:a.txt) is allowed. On NTFS that name opens an alternate data stream; the runtime runs on Linux, macOS and WSL2, so this matters only if the extracted files are later written to NTFS by another tool. Not tested on Windows.supervisor.rs:930,setup.rs:3451,setup.rs:4008,setup/browser_vm_image.rs:632,elastos-runtime/src/capsule/fetched_launch.rs:73) read signed or admitted archives withtarunpack/unpack_inor a fixed file list; they are not changed here.elastos-serverzip and archive tests, its clippy, and the audit were run here, not the full workspace test suite. CI runs the rest.🤖 Generated with Claude Code
https://claude.ai/code/session_01W6Qn9RX7LDqrwiAfNC2F2K