Skip to content

[Bug]: Snapshot verify can read outside the handed directory via receipt paths #469

Description

@VARUN3WARE

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 arejoined 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).

Activity

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

Metadata

Metadata

Assignees

Labels

advancedNeeds codebase familiarity; not a starter issuebugSomething isn't workinghelp wantedExtra attention is needed

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions