Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions crates/execution/assets/runners/wasi-module.js
Original file line number Diff line number Diff line change
Expand Up @@ -1393,11 +1393,18 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOSWasiModule =
return this._measureWasiPhase("writeResultPtr", () => this._writeUint32(nwrittenPtr, written));
}
}
const entry = this._descriptorEntry(descriptor);
const localHostPassthrough =
handle.kind === "host-passthrough" &&
entry?.kind === "file" &&
entry.realFd === handle.targetFd;
const position = handle.append
? this._measureWasiPhase("appendFstat", () =>
Number(__agentOSFs().fstatSync(handle.targetFd).size ?? 0)
)
: null;
: localHostPassthrough
? (entry.offset ?? 0)
: null;
const written = this._measureWasiPhase("writeSync", () =>
__agentOSFs().writeSync(
handle.targetFd,
Expand All @@ -1407,6 +1414,15 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOSWasiModule =
position,
)
);
if (localHostPassthrough) {
if (handle.append) {
entry.offset = this._measureWasiPhase("appendFstat", () =>
Number(__agentOSFs().fstatSync(handle.targetFd).size ?? 0)
);
} else {
entry.offset = (entry.offset ?? 0) + written;
}
}
return this._measureWasiPhase("writeResultPtr", () => this._writeUint32(nwrittenPtr, written));
}
if (handle?.kind === "guest-file" && typeof handle.targetFd === "number") {
Expand Down Expand Up @@ -1745,15 +1761,23 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOSWasiModule =
(handle?.kind === "passthrough" || handle?.kind === "host-passthrough") &&
typeof handle.targetFd === "number"
) {
const localEntry = this._descriptorEntry(descriptor);
const localHostPassthrough =
handle.kind === "host-passthrough" &&
localEntry?.kind === "file" &&
localEntry.realFd === handle.targetFd;
const totalLength = this._boundedReadLength(iovs, iovsLen);
const buffer = Buffer.alloc(totalLength);
const bytesRead = __agentOSFs().readSync(
handle.targetFd,
buffer,
0,
totalLength,
null,
localHostPassthrough ? (localEntry.offset ?? 0) : null,
);
if (localHostPassthrough) {
localEntry.offset = (localEntry.offset ?? 0) + bytesRead;
}
const written = this._writeToIovs(iovs, iovsLen, buffer.subarray(0, bytesRead));
return this._writeUint32(nreadPtr, written);
}
Expand Down
28 changes: 27 additions & 1 deletion crates/vfs/src/posix/overlay_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1661,6 +1661,8 @@ impl VirtualFileSystem for OverlayFileSystem {
let mut snapshot_entries = Vec::new();
self.collect_snapshot_entries(&old_normalized, &mut snapshot_entries)?;

self.clear_path_metadata(&resolved_new_normalized)?;
self.clear_subtree_metadata(&resolved_new_normalized)?;
if let Ok(destination_stat) = self.merged_lstat(&resolved_new_normalized) {
if destination_stat.is_directory
&& !destination_stat.is_symbolic_link
Expand All @@ -1678,7 +1680,6 @@ impl VirtualFileSystem for OverlayFileSystem {
.remove_file(&resolved_new_normalized)?;
}
}
self.clear_subtree_metadata(&resolved_new_normalized)?;
}

self.stage_snapshot_entries_in_upper(&snapshot_entries)?;
Expand Down Expand Up @@ -1985,6 +1986,31 @@ mod tests {
assert_error_code(overlay.read_dir("/a"), "ENOENT");
}

#[test]
fn rename_clears_destination_whiteout() {
let lower = MemoryFileSystem::new();
let mut overlay = OverlayFileSystem::new(vec![lower], OverlayMode::Ephemeral);
overlay
.write_file("/archive.zip", b"old".to_vec())
.expect("create old archive");
overlay
.remove_file("/archive.zip")
.expect("whiteout old archive");
overlay
.write_file("/workspace-temp", b"new".to_vec())
.expect("create replacement");

overlay
.rename("/workspace-temp", "/archive.zip")
.expect("rename replacement over whiteout");

assert_eq!(
overlay.read_file("/archive.zip").expect("read renamed file"),
b"new"
);
assert!(!overlay.exists("/workspace-temp"));
}

#[test]
fn remove_dir_still_rejects_visible_children() {
let mut lower = MemoryFileSystem::new();
Expand Down
55 changes: 39 additions & 16 deletions docs-internal/registry-parity-worklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,21 @@ works (`wasi-spawn` broker), so `xargs` is not a blocker.
`2026-07-08T06-58-56-0700-ripgrep-check-types-after-install.log`; e2e ripgrep
tests pass 8/8 in `2026-07-08T07-02-00-0700-ripgrep-vitest-after-git-fixture.log`.
Rev: `msypkqmo` — `fix(ripgrep): build upstream ripgrep`.
- **zip / unzip — moderate.** Real **Info-ZIP** source (fetch+pin like zlib/sqlite);
zlib is already vendored. Filesystem + `isatty`/`utime`/`chmod`/perms stubs; no
sockets/threads/spawn. Friction is Info-ZIP's crufty build, not syscalls.
- **zip / unzip — DONE.** Replaced both custom minizip-based C wrappers with real
upstream Info-ZIP Zip 3.0 and UnZip 6.0 release tarballs, built through the C
toolchain. Runtime/sysroot fixes stayed one layer down: temp-file, ownership,
`system`/`popen`/`pclose` compatibility in patched wasi-libc; overlay rename
over destination whiteouts; and WASI host-passthrough read/write offset
tracking after `fd_seek`. Proof: wasi-libc patch check passes in
`2026-07-08T08-34-29-0700-wasi-libc-patch-check-final.log`; native sidecar
build passes in `2026-07-08T08-34-07-0700-sidecar-build-final-runner-format.log`;
VFS rename regression passes in
`2026-07-08T08-34-29-0700-vfs-core-rename-whiteout-final.log`; final Zip e2e
passes 2/2 in
`2026-07-08T08-36-45-0700-zip-test-final-after-current-sysroot-copy-after-install.log`;
final UnZip e2e passes 6/6 in
`2026-07-08T08-37-12-0700-unzip-test-final-after-current-sysroot-copy-after-install.log`.
Rev: `nppnuxpr` — `fix(infozip): build upstream zip and unzip`.
- **findutils — moderate→hard.** `find` is fs+regex (easy); `xargs` spawn already
works. **uutils/findutils** (Rust) avoids gnulib; **GNU findutils** (C) hits the
same gnulib cascade as wget. Prefer uutils unless GNU parity is required.
Expand Down Expand Up @@ -396,19 +408,30 @@ so a reader sees the whole board at a glance.
cookie-suffix checks, LDAP, and no CA bundle (cert trust is whatever `wasi_tls`
enforces). Those are the 5 skipped tests. Verdict: solid for real HTTP(S).

### 7. zip / unzip — hostile-archive hardening cases fail (3 each) — DONE
- **Broken:** fallback parser doesn't reject a wrapping local offset, doesn't skip
empty-normalized-name entries, doesn't cap hostile uncompressed sizes before
allocating.
- **Objective:** unzip rejects/handles malformed & hostile archives the way a
hardened Linux unzip does (bounded allocation, typed errors), and zip↔unzip
roundtrips remain correct.
- **Proof:** `software/unzip/test/` passes 6/6 in
`2026-07-08T00-57-22-0700-item7-unzip-test-final-pass.txt`; `software/zip/test/`
passes 2/2 in `2026-07-08T00-57-22-0700-item7-zip-test-final-pass.txt`.
Package type checks pass in `2026-07-08T00-57-39-0700-item7-unzip-check-types.txt`
and `2026-07-08T00-57-39-0700-item7-zip-check-types.txt`.
- **rev:** `krxkqtnx` — `fix(unzip): harden fallback archive parsing`
### 7. zip / unzip — replace custom wrappers with real Info-ZIP — DONE
- **Broken:** the shipped commands were custom C wrappers over zlib/minizip, not
real Zip/UnZip. The old fallback parser also diverged from hardened Linux unzip
behavior on wrapping local offsets, empty normalized names, and hostile size
declarations.
- **Objective:** build real upstream Info-ZIP Zip and UnZip to `wasm32-wasip1`,
patching the AgentOS sysroot/runtime where needed, and prove real zip↔unzip
roundtrips plus malformed-archive rejection in VM e2e tests.
- **Resolved:** upstream Info-ZIP Zip 3.0 and UnZip 6.0 now build from release
tarballs through the C toolchain. The custom `software/zip/native/c/zip.c` and
`software/unzip/native/c/unzip.c` sources are deleted. Required compatibility
lives one layer down: patched wasi-libc temp-file, ownership, `system`, `popen`,
and `pclose` surfaces; VFS overlay rename-over-whiteout cleanup; and
host-passthrough `fd_seek` offset tracking in the WASI runner.
- **Proof:** wasi-libc patch check passes in
`2026-07-08T08-34-29-0700-wasi-libc-patch-check-final.log`; native sidecar
build passes in `2026-07-08T08-34-07-0700-sidecar-build-final-runner-format.log`;
VFS rename regression passes in
`2026-07-08T08-34-29-0700-vfs-core-rename-whiteout-final.log`; `software/zip`
e2e passes 2/2 in
`2026-07-08T08-36-45-0700-zip-test-final-after-current-sysroot-copy-after-install.log`;
`software/unzip` e2e passes 6/6 in
`2026-07-08T08-37-12-0700-unzip-test-final-after-current-sysroot-copy-after-install.log`.
- **rev:** `nppnuxpr` — `fix(infozip): build upstream zip and unzip`

---

Expand Down
Binary file modified packages/runtime-core/commands/unzip
100644 → 100755
Binary file not shown.
Binary file modified packages/runtime-core/commands/zip
100644 → 100755
Binary file not shown.
Binary file modified software/unzip/bin/unzip
100644 → 100755
Binary file not shown.
Loading