Skip to main content

Module hub_fetch

Module hub_fetch 

Source
Expand description

Fetches the Hub component library over the network into a per-user cache, when there is no in-repo hub/components/ to prefer instead (see the crate’s in_repo_hub_dir helper and crate::default_component_sources).

Resolution order: a profile’s own components/ (Local, highest precedence) beats the user cache, which beats fetching the central Hub fresh. This module only implements the last two steps — filling and trusting the cache.

Everything here is in-process: ureq + rustls for HTTPS (no shelling out to curl/wget, absent from debian:bookworm-slim), flate2 + tar for archive handling (no shelling out to system tar). The Hub ships as one hub-<version>.tar.gz archive per release, unpacking to component directories at its top level (answer_query/component.yml, not components/answer_query/component.yml).

§Cache trust

A prior, abandoned design cached to the world-writable, fully-derivable-path shared temp directory and returned early on a bare .complete sentinel — never verifying content or ownership. A local actor could pre-plant a poisoned cache at that guessable path and every later compile would silently use it (reproduced end to end: a canary in a cached step file appeared twice in the emitted IR). This module closes that vector two ways:

  • The cache root is a per-user directory (the platform user cache dir, never the shared temp dir), created owner-only (0o700 on unix).
  • A cache hit is decided by recomputing the SHA-256 of the on-disk archive and comparing it against the on-disk checksum sidecar, every time — not by checking whether a marker file exists. Any mismatch, or a missing/malformed file on either side, is treated as a cache miss and triggers a fresh fetch. The extracted/ directory is always deleted and re-derived from the now-verified archive, never trusted as pre-existing content, so a directly-planted file under extracted/ (bypassing the archive and its checksum entirely) cannot survive either.

Concurrent warble compile invocations that land on the same version’s cache directory can race on extracted/ (remove → mkdir tmp → unpack → rename). This is benign, not silently unsafe: every racing process is extracting the same checksum-verified archive bytes, so the worst case is a transient, loudly-reported rename/ENOENT error that a retry clears — never mixed versions or silently corrupted content.

Functions§

ensure_cached_hub
Ensures the Hub for version is present, verified, and extracted under the per-user cache, fetching it over the network if needed. Returns the path to the extracted component directory (containing <id>/component.yml entries at its top level, ready to hand to crate::ComponentSource::hub).
validate_hub_version
Rejects anything that is not a plain MAJOR.MINOR.PATCH numeric triplet, optionally followed by a -prerelease/+build suffix drawn from a safe character set. This exists for two reasons: to reject mutable refs like "main" or "latest" as a Hub version (a Hub version must be fixed and checksum-verifiable, never a moving target with no hash to verify against), and — just as importantly — to keep the whole string, suffix included, safe to use as a path component and a URL segment. ensure_cached_hub joins this value directly into a cache path (cache_root.join("warble").join("hub").join(version)) and interpolates it into a release URL; a suffix left unvalidated (as an earlier version of this function did, checking only the pre--/+ core) lets a value like "1.0.0-../../../ESCAPED" pass as a “valid triplet” while smuggling .. path segments through to Path::join, writing (and owner-only-chmod-ing) a directory outside the intended cache subtree — independent of whether the subsequent fetch ever succeeds. So every character of version, not just the numeric core, is checked here before any path or URL is built from it.