Skip to content

fix: resolve HF symlinks in from_hub via temp local_dir - #1495

Open
Disha714 wants to merge 1 commit into
generative-computing:mainfrom
Disha714:bot-fix
Open

fix: resolve HF symlinks in from_hub via temp local_dir#1495
Disha714 wants to merge 1 commit into
generative-computing:mainfrom
Disha714:bot-fix

Conversation

@Disha714

@Disha714 Disha714 commented Aug 5, 2026

Copy link
Copy Markdown

Fixes #1492.

Summary of Changes

  • from_hub downloads via huggingface_hub.snapshot_download with the default cache layout, where io_configs/*/io.yaml entries in the returned snapshot directory are symlinks into a sibling blobs/ directory outside the snapshot root. This violated from_model_directoryx27s documented self-contained-directory contract and tripped the path-escape check.
  • Rather than loosening or touching the security check in from_model_directory, this PR updates from_hub to download files into a temporary self-contained local directory via with tempfile.TemporaryDirectory() as local_dir: and passing local_dir=local_dir to snapshot_download. This populates the temporary directory with real files rather than symlinks escaping the directory.
  • Passes the return value of snapshot_download (downloaded_dir) to from_model_directory so that both runtime operations and mocked test fixtures operate reliably.

Verification & Security

  • Security Barrier Untouched: Verified locally that all security-relevant unit tests (test_path_traversal_in_io_config_raises, test_symlink_escape_in_io_config_raises) pass cleanly, confirming that the directory traversal / path-escape defenses remain 100% intact for any malicious input.
  • Regression Testing: Added a dedicated unit test test_from_hub_requests_local_dir to explicitly verify that from_hub passes local_dir to snapshot_download. Updated existing test mocks across TestFromHub and TestFromSource to expect local_dir=ANY.
  • Test Suite Results: Executed uv run --extra switch pytest test/backends/test_adapters/test_embedded_adapter.py -v; all 38 tests passed locally without errors or regressions.

@Disha714
Disha714 requested a review from a team as a code owner August 5, 2026 16:16
@Disha714 Disha714 changed the title Fix #1492: resolve HF symlinks in from_hub via temp local_dir to satisfy path-escape check fix: resolve HF symlinks in from_hub via temp local_dir Aug 5, 2026
@github-actions github-actions Bot added the bug Something isn't working label Aug 5, 2026
Fixes generative-computing#1492: snapshot_download with default cache layout returns symlinks into a sibling blobs/ directory, violating from_model_directory contract and tripping the path-escape security check. By passing local_dir to snapshot_download within a TemporaryDirectory context, from_hub resolves genuine files to pass to from_model_directory without weakening any security barriers.

Assisted-by: Claude Code
Signed-off-by: Disha714 <nsaidisha@gmail.com>

@AngeloDanducci AngeloDanducci left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello, thanks for the contribution!

Overall I think this look good. One test question and I will ping @planetf1 for his 2C since he was the original issue author.

May be worth adding another test where from_hub loads adapters when snapshot_download populates local_dir with real files (as huggingface_hub does with local_dir set).

@planetf1 planetf1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(superseded — see consolidated review below)

@planetf1 planetf1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(superseded — see consolidated review below)

@planetf1 planetf1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(superseded — see consolidated review below)

try:
return EmbeddedIntrinsicAdapter.from_model_directory(
local_root, intrinsic_name=intrinsic_name
with tempfile.TemporaryDirectory() as local_dir:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache always cold. local_dir prevents cache_dir from ever being written to; nothing in mellea/ pre-populates io_configs/** in cache mode, so try_to_load_from_cache never hits.

Each call is ~51 HTTP round-trips (repo_info + 25 HEAD + 25 GET) with zero reuse across calls. _resolve_adapter (adapter.py:513) calls this once per distinct intrinsic name — 12 times for a full Switch model.

Measured on ibm-granite/granite-switch-4.1-3b-preview: three consecutive calls took 0.88s / 0.44s / 0.50s.

Hard offline failure: HF_HUB_OFFLINE=1 now raises LocalEntryNotFoundError because a fresh empty temp dir has nothing to read from, and the cached-snapshot fallback at _snapshot_download.py:293 is gated on local_dir is None.

Suggested fix: Use a persistent directory under cache_dir/HF_HUB_CACHE + repo + revision instead. Verified: no symlinks (escape check passes), warm online 0.9s → 0.1s, offline works. Path must include revision to avoid cross-revision collisions.

assert len(adapters) == 1
assert adapters[0].intrinsic_name == "citations"

def test_from_hub_requests_local_dir(self, model_dir):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test gap: mock returns model_dir (pre-built fixture with real files) — never proves that snapshot_download(..., local_dir=...) actually produces a self-contained directory that passes the escape check.

Suggested: a side_effect-based unit test that materialises a cache-style symlink layout (relative symlinks into a sibling blobs/ dir) when local_dir is None, and real files when it is set. Fails on main, passes on this branch.

delegates to :meth:`from_model_directory`.
Downloads `adapter_index.json` and the `io_configs/` directory into a
self-contained local directory, then delegates to
:meth:`from_model_directory`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: :meth:from_model_directoryis an RST cross-reference directive. AGENTS.md §5 bans RST markup in docstrings — should be from_model_directory ``. Fix all three occurrences in this docstring (lines 757, 782, 784) or leave for a sweep.

@planetf1 planetf1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The symlink-escape fix is correct, but cache_dir is silently defeated:

  • Cache always cold: local_dir prevents cache_dir from ever being written to; each call is ~51 HTTP round-trips with zero reuse
  • Offline broken: HF_HUB_OFFLINE=1 raises LocalEntryNotFoundError

Suggested fix: Use a persistent directory under cache_dir/HF_HUB_CACHE + repo + revision instead of TemporaryDirectory(). Verified: restores caching (0.9s → 0.1s warm) and offline operation while keeping the self-contained layout.

Inline comments on test gap and docstring nit follow.

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

from_hub passes HF snapshot path to from_model_directory, breaking 0.7.0 path-escape check

3 participants