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 (
0o700on 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 underextracted/(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
versionis 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.ymlentries at its top level, ready to hand tocrate::ComponentSource::hub). - validate_
hub_ version - Rejects anything that is not a plain
MAJOR.MINOR.PATCHnumeric triplet, optionally followed by a-prerelease/+buildsuffix 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_hubjoins 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 toPath::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 ofversion, not just the numeric core, is checked here before any path or URL is built from it.