Skip to content

export-docs refuses an archive whose root entry is ./ — the shape tar czf out.tar.gz . produces #183

Description

@avrabe

Reported by the linc-layer consumer against ulinc-tools-layer 2026.09.4.

Symptom

varve export-docs refuses two documentation tarballs:

sdk member "./" is not a usable path

The tarballs are valid — any tar -xzf extracts them — and they were produced
by the ordinary upstream CI idiom:

tar czf out.tar.gz .

which emits an explicit ./ directory entry for the archive root.

Root cause — confirmed by reading, not inferred

crates/varve-core/src/sdkexport.rs::safe_member_path (the shared rule, used
by export-docs as well as export-sdk, which is why the message says "sdk
member") already handles the ./ PREFIX, with a comment naming this exact
scenario. What it does not handle is the bare root entry:

raw = "./"
  → trim_end_matches('/')          → "."
  → the `./` strip loop: "." does not start with "./", so nothing is stripped
  → "." is not empty
  → split('/') → ["."] → component_fault(".") = "a relative path element"
  → REFUSED

So ./docs/index.html is accepted and ./ alone is refused. . on its own
(some tars emit it without the slash) fails identically.

The check is not wrong to refuse . as a path component — a/./b has no
business in an export. It is wrong to treat the archive root as a path at
all. That entry denotes the directory the archive was made from; there is
nothing to write for it, so the correct handling is to SKIP it, not to reject
the archive.

Why this is varve's to fix

The reporter's conclusion is right: it is not fixable from the layer
repository, and asking upstreams to change how they tar is asking the world to
work around us. tar czf out.tar.gz . is the most common way to archive a
directory.

Fix shape

safe_member_path should distinguish three outcomes rather than two —
"usable path", "archive root, skip it", "unsafe, refuse" — so every caller has
to decide what to do with the root entry instead of one of them silently
treating it as a file. Returning Result<Option<String>> makes the compiler
enumerate the call sites, which is how the analogous fix went in #176.

.. must stay refused everywhere, including ./../x, which normalises to
../x and is caught by the component check today.

Workaround in place downstream (no action needed there)

The reporter verified the raw tarballs' sha256 against the signed manifest,
extracted with plain tar, and pointed docs = links at each site's
index.html. Integrity was preserved throughout — this is an extraction
ergonomics defect, not a trust one.

Test to add with the fix

An archive built the way the report describes — a ./ root entry plus
./index.html and ./assets/app.css — must export, and the resulting tree
must contain no entry named .. Plus the negative control that ./../x is
still refused.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions