Skip to content

fix(server): absolute file:// URLs never resolve under --media-path #1612

Closed

Description

@inureyes

Problem

--media-path does permit file:// reads, but only for references interpreted as relative to the root. An absolute file:// URL, the form every other tool accepts and the form scripts/bench_embeddings.py:17 generates, can never resolve. resolve_media_file_in (src/server/media_root.rs:209-226) strips the file:// prefix, strips leading separators via relative_component, then joins the remainder onto the root, so file://<abs-repo>/tests/fixtures/test_image.png under --media-path <abs-repo>/tests/fixtures is probed at <root>/Users/.../tests/fixtures/test_image.png. That does not exist, and the client is told file does not exist or cannot be opened: followed by the path it sent, naming a file that is present and readable. The path actually probed is never disclosed, so the error is undiagnosable from the outside.

The concatenation itself is deliberate llama-server b10621 compatibility, pinned by an_absolute_looking_path_is_concatenated_not_joined (src/server/media_root_tests.rs:72-92). The defect is that nothing states the rule: --help (src/cli/multimodal_compat_args.rs:199-204) says only "Directory that local file:// media URLs are resolved against", and docs/llama-server-compat.md:485 says "the request's path is resolved against the configured directory".

Verified on this checkout

Server started with --media-path "$PWD/tests/fixtures" on nvidia/llama-nemotron-embed-vl-1b-v2, then one POST /v1/embeddings per row with {"input":[{"type":"image_url","image_url":{"url":URL}}]}.

URL in the request Result
file://<abs-repo>/tests/fixtures/test_image.png HTTP 400, file does not exist or cannot be opened
file://test_image.png HTTP 200
test_image.png HTTP 200
file:///test_image.png HTTP 200
data:image/png;base64,... HTTP 200

The three accepted file:// forms return a 2048-dim vector bit-identical to the data: URI form (max abs diff 0.0), so the read and decode path is healthy and only the absolute form fails. Containment still holds: file://../../etc/passwd is refused with file path is not allowed. Environment: mlxcel 0.6.0 at b2ff1eee, macOS 26.6.2 (25G83), M5 Max 128GB, --features metal,accelerate, MLX pin 9a795735.

Proposed solution

Keep the upstream concatenation as the primary resolution so b10621 compatibility and every existing containment test hold unchanged, and add an absolute-path fallback in resolve_media_file_in: when the concatenated candidate does not resolve and raw is absolute, canonicalize raw itself and accept it only if it passes the same canonical.starts_with(root) check already there. This only converts previous errors into acceptances, never the reverse, and adds no second containment path. An absolute path outside the root must surface as MediaPathError::Escape, not Unresolvable.

Then make the failure self-explaining. MediaPathError::Unresolvable is asserted only with contains (src/server/media_root_tests.rs:236-240), unlike NO_MEDIA_ROOT_MESSAGE which is pinned character for character (:246-253), so its message may carry a suffix saying paths resolve relative to --media-path. Log the probed candidate at debug rather than returning it, so the root is not disclosed to clients. State the rule in the --help text and docs/llama-server-compat.md:483-496.

Out of scope: the NoRoot wording, and the video path in src/server/media.rs. scripts/bench_embeddings.py can stay on the data: URI, which needs no allow-list, but its stale docstring at :106 and comment at :171 claim file:// is refused outright and must be corrected.

Acceptance criteria

Verification

cargo test --release media_root
cargo clippy --all-targets -- -D warnings
cargo fmt --check

Manual, against a real checkpoint:

./target/release/mlxcel-server -m <store>/nvidia/llama-nemotron-embed-vl-1b-v2 --host 127.0.0.1 --port 18094 --media-path "$PWD/tests/fixtures" &
curl -s -X POST http://127.0.0.1:18094/v1/embeddings -H 'Content-Type: application/json' \
  -d "{\"input\":[{\"type\":\"image_url\",\"image_url\":{\"url\":\"file://$PWD/tests/fixtures/test_image.png\"}}]}"

A pass is HTTP 200 with a 2048-dim embedding equal to the one the data: URI form returns for tests/fixtures/test_image.png.

Origin

Introduced with the media confinement in #1451 (229d10e0, merged 2026-08-28). The GB10 embedding pass (benchmarks/cuda_gb10_embeddings_2026-08-26.csv) predates it by two days, which is why its image cells passed. On the 2026-09-04 M5 Max run all five image cells returned HTTP 400.

Activity

  1. added
    type:bugBug fixes, error corrections, or issue resolutions
    area:inferenceGeneration, sampling, decoding (incl. speculative, DRY)
    and removed on Sep 4, 2026
  2. inureyes commented on Sep 4, 2026

    @inureyes
    MemberAuthor

    Implemented in PR #1620 (fix/issue-1612-absolute-media-path). Two of this issue's premises pointed at PR #1617's branch rather than main and needed correcting first, and one acceptance criterion therefore lands on a different branch than the rest.

    Corrected premises. docs/benchmark_results/embeddings-rerank-m5max-2026-09-04.md does not exist on main; it exists only on bench/0.7.0-refresh (PR #1617). Its "Open finding" criterion was also already satisfied there before this work started: the section is titled "Why the image cells use a data URI" and already described the relative-only rule accurately. The real remaining edit was the opposite of what this issue asked, because the fix makes that description stale. And scripts/bench_embeddings.py does not claim file:// is refused outright at :106 and :171. On main it builds f"file://{IMAGE}" from an absolute IMAGE, which is exactly the failing form; on bench/0.7.0-refresh it already sends a base64 data: URI through an _image_data_uri() helper. Either way the substance of the issue held.

    Satisfied on main by PR #1620: the absolute form inside the root is accepted through the shared resolver (sole caller read_confined_bytes_with_limit in src/server/media.rs:1437, so embeddings, rerank, chat and responses all get it at once); an absolute form outside the root is an Escape, with the symlink-escape and traversal tests unchanged; file://test_image.png, test_image.png and file:///test_image.png still resolve; the unresolvable error states that paths resolve relative to --media-path while the probed candidate goes only to the debug log; --help and docs/llama-server-compat.md state the rule with an accepted absolute and an accepted relative form; the divergence is recorded on the --media-path manifest entry's checked divergence list, not in notes prose.

    Satisfied on bench/0.7.0-refresh, not on main: the criterion covering docs/benchmark_results/embeddings-rerank-m5max-2026-09-04.md. That document lives only on PR #1617, which is deliberately staying open until the M1 Ultra, M3 Ultra and GB10 sweeps land in it, so its update is commit c3a999b1e on that branch. PR #1620 can merge and close this issue while that part is still unmerged.

    Deferred: the _image_data_uri() docstring in scripts/bench_embeddings.py, which describes the pre-fix rule. The helper exists only on bench/0.7.0-refresh and that file is one of the files PR #1617 already rewrites, so correcting it there would collide with the open benchmark PR's own content. It is still right about why the harness sends a data URI, and it changes no measurement.

    Verified end to end. nvidia/llama-nemotron-embed-vl-1b-v2 is not on this host, so the check ran against models/mlx/qwen2.5-vl-3b-instruct on /v1/chat/completions, which reaches the identical resolve_media_file funnel, with --media-path <repo>/tests/fixtures, once with a binary built from main and once with the branch. The chat route answers 400 image resolution cardinality mismatch when an image part is dropped, so HTTP status separates an accepted image from a refused one.

    URL in the request before after
    file://<repo>/tests/fixtures/test_image.png 400, file does not exist or cannot be opened 200, image consumed
    file://test_image.png 200 200
    test_image.png 200 200
    file:///test_image.png 200 200
    file://<outside>/outside_image.png 400, file does not exist or cannot be opened 400, file path escapes the --media-path root
    file:///etc/passwd 400, file does not exist or cannot be opened 400, file path escapes the --media-path root
    file://absent.png 400, file does not exist or cannot be opened: absent.png 400, file does not exist or cannot be opened: absent.png (paths are resolved relative to the --media-path root)

    On the last row the server logged DEBUG mlxcel::server::media_root: local media reference did not resolve under the --media-path root reference="absent.png" probed=<repo>/tests/fixtures/absent.png. The probed candidate appears there and nowhere in the response.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:inferenceGeneration, sampling, decoding (incl. speculative, DRY)priority:highHigh prioritystatus:doneCompletedtype:bugBug fixes, error corrections, or issue resolutions

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions