Skip to content

deps: move zip to 9.x for RUSTSEC-2026-0367/0368/0369 - #417

Draft
SashaMIT wants to merge 7 commits into
developfrom
fix/zip-9-rustsec
Draft

SashaMIT wants to merge 7 commits into
developfrom
fix/zip-9-rustsec

Conversation

@SashaMIT

@SashaMIT SashaMIT commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

What

Move zip from 2.4.2 to 9.0.4 in elastos-server, remove the cargo-audit exception, and keep the runtime's own rule for archive entry names. Since 16a132a8 that 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 at bfefe92f, 8bb48764).

What this PR carries

Commit What
756dad1c deps: zip 2.4.2 -> 9.0.4, ZipFile::name() returns Result, and the guard zip_entry_enclosed_path with its tamper test
aaa1c69c port of #419 (test code only, capsules/model-provider/src/journal.rs): the four held-lock journal tests take the lock with the bounded retry, so test-capsules can run (#381 flake). No effect once #419 lands
455ff722 merge of develop 9be009b1, which brings Anders' 0.8.0 exception file elastos/.cargo/audit.toml
bb8afd7d deletes elastos/.cargo/audit.toml (the three zip IDs were its only entries), as #414 asks
5873d2f7 the guard refuses Windows root, drive and UNC names on every platform; tamper cases and a unit test for them
16a132a8 one entry-name rule, archive_entry_path, for every archive reader: list, preview, selected extract and whole extract, ZIP and tar. It replaces zip_entry_enclosed_path and the Path-based check of the other readers
8bb48764 merge of origin/develop at bfefe92f (develop's fix for its own #430 Home link test; no conflicts; zip stays 9.0.4). At 8bb48764: cargo test -p elastos-server --lib -- home_external_link archive zip 36 passed.

Why

cargo-audit fails on every runtime PR. zip 2.4.2 is in elastos/Cargo.lock and in capsules/object-provider/Cargo.lock (through its elastos-server path dependency). It has three advisories:

  • RUSTSEC-2026-0367
  • RUSTSEC-2026-0368 (ZipArchive::extract creates symbolic links whose targets are outside the destination)
  • RUSTSEC-2026-0369

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)

  • The runtime never calls ZipArchive::extract or the stream extract API. It reads each entry itself with by_index (elastos/crates/elastos-server/src/library.rs:5422 extract_zip_archive, and the preview, list and selected readers preview_zip_archive_entry library.rs:4232, list_zip_archive_entries :4440, collect_selected_zip_writes :4930).
  • It does not read unix_mode. No file mode from an archive is applied.
  • A symlink entry is refused: extract_zip_archive stops 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_name changed

zip 9 enclosed_name reads the name as a Windows path, strips a root, drive or UNC prefix, and folds x/../a.txt into a.txt. A plain bump would make extract_zip_archive write /x, C:\x, C:/x, \\server\share\x and \\?\C:\x as x, and Nested/../x as x, into the destination (negative run 2 below). The runtime does not call enclosed_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:

  • holds a NUL byte,
  • holds a \ anywhere (this includes C:\x, \\server\share\x, \\?\C:\x and ..\x),
  • starts with / (this includes //server/share/x),
  • starts with a drive letter (C:/x, C:x),
  • has a .. 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, and normalized_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 with tar_entry_name (library.rs:4541, Entry::path_bytes, GNU long names and PAX path included), so the rule sees the same text on every platform.

Every call site (lines at 16a132a8):

Reader ZIP tar
list list_zip_archive_entries library.rs:4453 list_tar_archive_entries :4399
preview (the entry asked for) archive_preview_entry_for_object :4176 (both formats)
preview (archive side) preview_zip_archive_entry :4246 preview_tar_archive_entry :4288
selected extract (the entries asked for) selected_archive_entries :4855 (both formats)
selected extract (archive side) collect_selected_zip_writes :4945 collect_selected_tar_writes :4890
whole extract extract_zip_archive :5437 extract_tar_archive :5391

The 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.reason set to the rule's message, and path set 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 under blocked) 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 used std::path::Path, so on Linux and macOS C:/x was a folder named C:. The list showed it as safe, preview returned its bytes, and selected extract wrote Imports/C:/x. Whole 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"). Whole ZIP extract already refused all of them (5873d2f7).

Behaviour changes:

  • A name with a \ anywhere is refused with the rule's message by every reader. Before, on Linux whole extract refused it with "invalid library object name"; on Windows std::path::Path read \ as a separator.
  • From 5873d2f7: an entry whose name starts with a letter and a colon, such as a:b.txt, is refused (on Windows it is a drive-relative path). A colon later in the name, such as Nested/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 name C:/x, C:\x, \\server\share\x, /etc/x and ../x (each next to a safe safe.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), because tar::Builder refuses 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, and selected_archive_entries refuses 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.txt and 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 was zip_entry_enclosed_path_refuses_… and now calls the rule that replaces that function). 16 hostile names (the 15 before plus Nested/../a.txt, which the old guard passed to archive_entry_uri) are refused, and 6 ordinary names give the expected path (./a.txt -> a.txt, Nested/ -> Nested, Nested/C:a.txt unchanged, ./ -> the root).

Other API changes

  • ZipFile::name() returns a Result in 9.x. All four fallible entry-name calls in library.rs are handled, and each name then goes through the rule (lines at 16a132a8):

    • whole-archive extract: name() at library.rs:5435, then archive_entry_path at :5437;
    • preview: name() at :4243, then normalized_archive_entry_path at :4246;
    • list: name() at :4450, then normalized_archive_entry_path at :4453;
    • selected extract: name() at :4942, then normalized_archive_entry_path at :4945.

    The test helper gateway_tests/library.rs:895 unwraps it.

  • Behaviour change in form: each of those four calls ends in .map_err(|err| anyhow!("invalid ZIP archive entry: {err}"))?. If name() 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 only Err is for all-ASCII bytes, which are always valid UTF-8 and so never reach it). zip 2.4.2 name() returned &str and 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 flate2 feature. deflate-flate2 enables 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 at 5873d2f7).

Lockfiles (hand-trimmed, cargo-accepted)

cargo update -p zip also moves unrelated range dependencies (about 16 lines of windows-sys / getrandom churn). Those lines stay as they were. Each lockfile changes only for zip:

  • zip 2.4.2 -> 9.0.4
  • nix 0.31.3 and typed-path 0.12.3 are added; derive_arbitrary is removed
  • existing "nix" dependency references become "nix 0.29.0" because two nix versions now exist

cargo metadata --locked accepts both files and does not change them (checked at 756dad1 and again after the develop merge 455ff72). 16a132a8 changes 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):

test api::gateway::gateway_tests::library::test_library_provider_extract_zip_refuses_absolute_dotdot_and_symlink_entries ... ok
test api::gateway::gateway_tests::library::test_library_provider_lists_unsafe_archive_entries_as_blocked ... ok
test api::gateway::gateway_tests::library::test_library_provider_rejects_unsafe_zip_entries ... ok
test library::tests::archive_entry_path_refuses_root_drive_and_climb_on_every_platform ... ok
test library::tests::archive_readers_block_root_drive_unc_and_climb_entry_names ... ok
test api::gateway::gateway_tests::library::test_library_provider_every_archive_reader_refuses_root_drive_unc_and_climb_names ... ok
test result: ok. 34 passed; 0 failed; 0 ignored; 0 measured; 2973 filtered out; finished in 16.07s

cargo clippy -p elastos-server --all-targets -- -D warnings, cargo fmt --all -- --check, git diff --check, node scripts/public-copy-entropy-check.mjs and node scripts/check-product-data.mjs pass.

Negative runs for 16a132a8 (not committed)

A. The two new tests on the code at 5873d2f7 (only the tests added):

7 reader checks failed:
Case.zip "C:/drive-slash-escape.txt" list: [Object {... "kind": String("file"), ... "path": String("C:/drive-slash-escape.txt"), "safety": Object {"reason": Null, "status": String("safe")} ...}]
Case.zip "C:/drive-slash-escape.txt" preview "C:/drive-slash-escape.txt": "escape"
Case.zip "C:/drive-slash-escape.txt" selected: writes=[File { path: "C:/drive-slash-escape.txt", bytes: [101, 115, 99, 97, 112, 101] }] blocked=[]
Case.tar "C:/drive-slash-escape.txt" list: [... "status": String("safe") ...]
Case.tar "C:/drive-slash-escape.txt" preview "C:/drive-slash-escape.txt": "escape"
Case.tar "C:/drive-slash-escape.txt" selected: writes=[File { path: "C:/drive-slash-escape.txt", ... }] blocked=[]
selection "C:/drive-slash-escape.txt" accepted

14 reader checks failed:
zip "C:/drive-slash-escape.txt" list: [... "status": String("safe") ...]
zip "C:/drive-slash-escape.txt" preview "C:/drive-slash-escape.txt": status="ok" message=null text="escape"
zip "C:/drive-slash-escape.txt" selected "C:/drive-slash-escape.txt": status="ok" message=null written=[{"kind":"file","path":"C:/drive-slash-escape.txt",...}]
zip "C:/drive-slash-escape.txt" selected "drive-slash-escape.txt": written=[] blocked=[]
tar "C:/drive-slash-escape.txt" list: [... "status": String("safe") ...]
tar "C:/drive-slash-escape.txt" preview "C:/drive-slash-escape.txt": status="ok" message=null text="escape"
tar "C:/drive-slash-escape.txt" selected "C:/drive-slash-escape.txt": status="ok" message=null written=[{"kind":"file","path":"C:/drive-slash-escape.txt",...}]
tar "C:/drive-slash-escape.txt" selected "drive-slash-escape.txt": written=[] blocked=[]
tar "C:/drive-slash-escape.txt" extract: status="ok" message=null
tar "C:\\drive-backslash-escape.txt" extract: status="error" message="invalid library object name"
tar "\\\\server\\share\\unc-escape.txt" extract: status="error" message="invalid library object name"
hostile entry was written: .../Documents/Imports/C:/drive-slash-escape.txt
hostile entry was written: .../Documents/Imports/C:/drive-slash-escape.txt
hostile entry was written: .../Documents/Case0/C:/drive-slash-escape.txt
test result: FAILED. 0 passed; 2 failed

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 at 5873d2f7 (the Path::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:

Reverted call site Failing test(s) First failing check
list ZIP :4453 both new tests zip "C:/drive-slash-escape.txt" list: [... "status": "safe" ...]
list tar :4399 both new tests tar "C:/drive-slash-escape.txt" list: [... "status": "safe" ...]
preview request :4176 gateway test zip "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)
preview ZIP :4246 unit test Case.zip "C:/drive-slash-escape.txt" preview "C:/drive-slash-escape.txt": "escape"
preview tar :4288 unit test Case.tar "C:/drive-slash-escape.txt" preview "C:/drive-slash-escape.txt": "escape"
selected request :4855 both new tests selection "C:/drive-slash-escape.txt" accepted
selected ZIP :4945 both new tests Case.zip "C:/drive-slash-escape.txt" selected: writes=[File { path: "C:/drive-slash-escape.txt", ... }]
selected tar :4890 both new tests Case.tar "C:/drive-slash-escape.txt" selected: writes=[File { path: "C:/drive-slash-escape.txt", ... }]
whole extract ZIP :5437 gateway test and …extract_zip_refuses_absolute_dotdot_and_symlink_entries zip "C:/drive-slash-escape.txt" extract: status="ok" and hostile entry was written: .../Documents/Case0/C:/drive-slash-escape.txt
whole extract tar :5391 gateway test tar "C:/drive-slash-escape.txt" extract: status="ok" and hostile entry was written: ...

Negative runs for 5873d2f7 (not committed)

  1. Guard reverted to its 756dad1c body (the Path::components() loop with Component::Prefix(_) | Component::RootDir => return None):
thread 'library::tests::zip_entry_enclosed_path_refuses_root_drive_and_climb_on_every_platform' panicked at crates/elastos-server/src/library.rs:7238:9:
accepted hostile names: ["\\a.txt", "C:\\a.txt", "C:/a.txt", "C:a.txt", "c:", "\\\\server\\share\\a.txt", "\\\\?\\C:\\a.txt", "\\\\.\\C:\\a.txt", "..\\a.txt", "Nested\\..\\..\\a.txt"]
thread '...test_library_provider_extract_zip_refuses_absolute_dotdot_and_symlink_entries' panicked at crates/elastos-server/src/api/gateway_tests/library.rs:4031:9:
Backslash.zip: {"code":"library_error","message":"invalid library object name","status":"error"}

With the expected message of the four backslash cases set back to "invalid library object name", the old guard writes the C:/ entry:

assertion `left == right` failed: DriveSlash.zip: hostile entry "C:/drive-slash-escape.txt" was not refused: {"data":{"object":{...
  1. extract_zip_archive with zip_entry_enclosed_path(&raw_name) replaced by zip 9 file.enclosed_name(). The committed test stops at the first case:
assertion `left == right` failed: Absolute.zip: hostile entry "/absolute-escape.txt" was not refused: {"data":{"object":{...
test result: FAILED. 0 passed; 1 failed; ...

The same run, with the test changed to print each case and go on:

CASE Absolute.zip "/absolute-escape.txt" -> status="ok" message=null
CASE DotDot.zip "../dotdot-escape.txt" -> status="error" message="library archive entry path must be relative and safe"
CASE Inner.zip "Nested/../inner-escape.txt" -> status="ok" message=null
CASE Backslash.zip "..\\backslash-escape.txt" -> status="error" message="library archive entry path must be relative and safe"
CASE DriveBackslash.zip "C:\\drive-backslash-escape.txt" -> status="ok" message=null
CASE DriveSlash.zip "C:/drive-slash-escape.txt" -> status="ok" message=null
CASE Unc.zip "\\\\server\\share\\unc-escape.txt" -> status="ok" message=null
CASE Verbatim.zip "\\\\?\\C:\\verbatim-escape.txt" -> status="ok" message=null
CASE Symlink.zip "symlink-escape.txt" -> status="error" message="library archive extraction rejects non-file archive entries"
hostile entry was written: /tmp/.tmpyCfbR7/Users/0f1b3909e69728c65b362c38/Documents/Verbatim/verbatim-escape.txt

Audit

CI-style audit (the cargo-audit job loop over every tracked Cargo.lock, cargo-audit 0.22.2) with audit.toml deleted: 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). CI cargo-audit passed on bb8afd7 (run 39023894827, job 117370971768).

CI

Merged into the open runtime draft PRs

So their CI can run past cargo-audit, this branch reaches these branches by plain git merge, no rewrite. Not #407 and not #408. These merges carry 756dad1c only, not the later commits (5873d2f7, 16a132a8 and the others); the orchestrator's final cascade brings the rest.

On develop (merge of fix/zip-9-rustsec):

PR Branch Merge
#394 cursor/launcher-contract-1297 519a2df9
#396 cursor/wsl2-runtime-notes-1297 ba88f427
#67 feat/library-set-desktop-background 7e1b4d9f
#68 feat/assistant-dock-mark-0.7.1 67da7137
#413 fix/ci-journey-failure-causes 69e2bae3
#415 fix/clipboard-source-gate 47281640
#418 fix/browser-helper-leftover-notice 241ba6a7

Stacked PRs get it through their base (merge of the updated base branch):

PR Base Merge
#395 #394 028c6137
#397 #395 b4316341
#398 #397 75175f97
#399 #398 d40a2f5e

When this PR lands in develop, those merges become no-ops.

Gaps

  • Not done from Upgrade zip to 9.x in elastos-server and remove the cargo-audit exception (RUSTSEC-2026-0367/0368/0369) #414: the Mac smoke (preview and download a ZIP in Library). Someone with a Mac must run it.
  • Non-ASCII entry names (normalization, look-alike separators, bidi characters): no test, follow-up Library: decide and test how ZIP entry names with non-ASCII characters are decoded and checked #432.
  • A colon after the first part (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.
  • Whole extract writes entries in archive order and stops at the first refused entry, so the safe entries before it stay in the new folder. This was so before; not changed here.
  • The rule covers the Library archive readers. The runtime's own release, capsule and Browser image tar readers (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 with tar unpack/unpack_in or a fixed file list; they are not changed here.
  • The WebSpace archive operations use the same functions, but no WebSpace test has a hostile name.
  • Only the elastos-server zip 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

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

Copy link
Copy Markdown
Contributor Author

This PR now has a second commit, aaa1c69c. It is test code only and has nothing to do with zip.

test-capsules failed on 756dad1 (job 117356008539) in capsules/model-provider:

journal::tests::live_transaction_creator_preserves_partial_files_and_bindings panicked at src/journal.rs:4336:35:
called `Result::unwrap()` on an `Err` value: "WouldBlock"

This is the #381 flake. Develop 5051505 fixed one test. The same single try_lock() pattern is still in four other test sites, and this test is one of them. Draft #419 fixes all four on develop. aaa1c69c brings the same change here, so this PR's CI can test the zip change. When #419 lands, this commit has no effect. Locally, cargo test --lib journal:: gives 52 passed.


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

Copy link
Copy Markdown
Contributor Author

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:

  • 455ff722 merges develop, which brings in the exception file.
  • bb8afd7d deletes elastos/.cargo/audit.toml. The three zip IDs were its only entries.

With the file deleted, the CI-style audit loop (cargo-audit 0.22.2) passes on all 34 tracked lockfiles: OVERALL failed=0. cargo metadata --locked accepts elastos/Cargo.lock and capsules/object-provider/Cargo.lock after the merge.

How this PR meets #414's Done means:

  • zip 9.0.4: done. That is past upstream's first fixed release, 9.0.1.
  • ZipFile::name() returning Result: handled at the three readers and in the test helper.
  • Exception: removed.
  • cargo-audit: green on every lockfile.
  • ZIP preview, listing and download tests: 8 passed at 756dad1.
  • Not done: the Mac smoke (preview and download a ZIP in Library). Someone with a Mac must run it.

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

Copy link
Copy Markdown
Contributor Author

5873d2f7: the ZIP guard now refuses C:\x, C:/x, C:x, \\server\share\x and \\?\C:\x on every platform. Before, C:/x passed on Linux and macOS and was written as a folder C:.
Proof: test_library_provider_extract_zip_refuses_absolute_dotdot_and_symlink_entries (4 new cases) and zip_entry_enclosed_path_refuses_root_drive_and_climb_on_every_platform. Both fail with the old guard and with zip 9 enclosed_name (output in the body). Non-ASCII names follow-up: #432.


Generated by Claude Code

@SashaMIT

Copy link
Copy Markdown
Contributor Author

Round 3 review: PASS (B+). Guard and tamper tests pass at 5873d2f.
Follow-up: one path rule for list, preview and selected-extract too (team decision).

Copy link
Copy Markdown
Contributor Author

CI on 5873d2f: source-home-linux (ubuntu-24.04) failed in the model-repair journey (run 39040338532, job 117453636062) with Installed Home journey failed: stage=model_interrupted_get_retry code=timeout check=restarted_home. This is the same stage and check that failed on #415, whose product tree is byte-identical to a head that passed this job, so this PR's zip and Library guard change is not the cause. It is tracked in #433. No fix exists yet. I cannot re-run the job from here (403); it needs one re-run.


Generated by Claude Code

@SashaMIT

Copy link
Copy Markdown
Contributor Author

Decision (Sash): one path rule for list, preview and selected-extract too, so C:/x is refused by every archive reader.

SashaMIT pushed a commit that referenced this pull request Oct 18, 2026
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

Copy link
Copy Markdown
Contributor Author

16a132a8: one rule, archive_entry_path (library.rs:4511), now checks every entry name in list, preview, selected extract and whole extract, for ZIP and tar (10 call sites, table in the body). C:/x, C:\x, \\server\share\x, /etc/x and ../x are refused by each reader. Before, list/preview/selected (and whole tar extract) accepted C:/x as a folder C:.
Proof: test_library_provider_every_archive_reader_refuses_root_drive_unc_and_climb_names and archive_readers_block_root_drive_unc_and_climb_entry_names. They fail on the old code (14 and 7 checks), and each of the 10 call sites, reverted alone, fails at least one of them (output in the body).


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

Copy link
Copy Markdown
Contributor Author

Merged develop at bfefe92f (8bb48764, no conflicts). It carries develop's fix for its own Home link test; the run on 16a132a8 was created before that fix. zip stays 9.0.4.
home_external_link archive zip at 8bb48764: 36 passed.


Generated by Claude Code

@SashaMIT

Copy link
Copy Markdown
Contributor Author

Re-review at 8bb4876: PASS (A). Only a develop merge; archive guards and tampers still hold; cargo audit clean; all CI jobs green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants