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:
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.
Reported by the
linc-layerconsumer againstulinc-tools-layer2026.09.4.Symptom
varve export-docsrefuses two documentation tarballs:The tarballs are valid — any
tar -xzfextracts them — and they were producedby 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, usedby
export-docsas well asexport-sdk, which is why the message says "sdkmember") already handles the
./PREFIX, with a comment naming this exactscenario. What it does not handle is the bare root entry:
So
./docs/index.htmlis 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/./bhas nobusiness 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 adirectory.
Fix shape
safe_member_pathshould 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 compilerenumerate the call sites, which is how the analogous fix went in #176.
..must stay refused everywhere, including./../x, which normalises to../xand 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 pointeddocs =links at each site'sindex.html. Integrity was preserved throughout — this is an extractionergonomics defect, not a trust one.
Test to add with the fix
An archive built the way the report describes — a
./root entry plus./index.htmland./assets/app.css— must export, and the resulting treemust contain no entry named
.. Plus the negative control that./../xisstill refused.