Version or commit
7df224a
Environment
Ubuntu 24.04, Python 3.11, x86_64
Minimal reproduction
from pathlib import Path
import json, tempfile, hashlib
from hflow.snapshot import verify_dataset_snapshot
def sha256_hex(path: Path) -> str:
digest = hashlib.sha256()
digest.update(path.read_bytes())
return digest.hexdigest()
with tempfile.TemporaryDirectory() as temporary_directory:
root = Path(temporary_directory)
snap = root / "snap"
outside = root / "outside"
snap.mkdir()
outside.mkdir()
secret = outside / "secret.bin"
secret.write_bytes(b"secret-bytes")
(snap / "format.json").write_text(
json.dumps(
{
"format": "hflow-dataset-snapshot",
"format_version": 1,
"media_mode": "references",
"media_uri_base": None,
"tables": ["samples.parquet"],
"integrity": {
"tables": {
"samples": {
"path": "../outside/secret.bin",
"size_bytes": secret.stat().st_size,
"sha256": sha256_hex(secret),
}
},
"assets": [],
"content_id": "00" * 32,
},
}
)
)
report = verify_dataset_snapshot(snap)
print(report.status, report.ok, report.findings)
### Expected behavior
Verify is scoped to the snapshot directory handed in. Docs say recorded paths are “joined only onto the handed directory” (`docs/how-to/export-dataset-snapshot.md`).
A receipt `path` with `..`, an absolute filesystem path, or a drive/UNC form should be refused as unreadable input (exit `2` / `ValueError`) — same spirit as `_validated_relative_key` in `src/hflow/storage.py` — not accepted by hashing bytes outside the delivery.
A normal relative path under that root should still pass when size and sha256 match.
### Actual behavior
Implementation is a bare join in `verify_dataset_snapshot`:
```python
delivered_path = resolved_directory / relative_path
(src/hflow/snapshot.py, integrity tables/assets loop)
On the reproduction above:
status=ok ok=True findings=[]
The verifier hashed the sibling file outside snap/ and reported the delivery clean. Absolute paths to the same object also pass. Moved-root tests from #457 do not cover this escape.
Additional context
Same trap Kingston called out on #454 for LeRobot verify: verify what is under the root you were handed, never what a receipt says about where bytes used to live (or live next door).
Honest export already writes POSIX-relative keys under the snapshot; the gap is the verifier accepting out-of-root paths from a tampered or hand-built format.json.
Related: #397 / #401 (integrity writer), #428 / #457 (snapshot verify). Not a duplicate of those — they never scoped receipt path resolution.
Happy to implement after scope is agreed (refuse as exit 2 vs finding; reuse storage key validation vs a local helper).
Version or commit
7df224a
Environment
Ubuntu 24.04, Python 3.11, x86_64
Minimal reproduction
(src/hflow/snapshot.py, integrity tables/assets loop)
On the reproduction above:
The verifier hashed the sibling file outside snap/ and reported the delivery clean. Absolute paths to the same object also pass. Moved-root tests from #457 do not cover this escape.
Additional context
Same trap Kingston called out on #454 for LeRobot verify: verify what is under the root you were handed, never what a receipt says about where bytes used to live (or live next door).
Honest export already writes POSIX-relative keys under the snapshot; the gap is the verifier accepting out-of-root paths from a tampered or hand-built
format.json.Related: #397 / #401 (integrity writer), #428 / #457 (snapshot verify). Not a duplicate of those — they never scoped receipt path resolution.
Happy to implement after scope is agreed (refuse as exit
2vs finding; reuse storage key validation vs a local helper).