Closed general

SECURITY: legacy-pipeline publishes inject a page.json with an internal host into anonymously-served tarballs

Colby Knox · 2mo ago ·closed by Colby Knox

SECURITY, routed from adom/adom-wiki-cli#17 (Kyle + the KiCad bridge team). Anonymous tarball downloads leak an internal hostname via an injected page.json nobody authored.

Independently reproduced: the adom/adom-desktop-kicad-bridge 1.0.17 tarball fetches anonymously (HTTP 200) and contains a page.json with 13 occurrences of an internal *.adom.cloud host. The page repo's page.json is clean; the file is absent from the author's source and staging. The rendered-readme links inside it are absolutized against that internal host.

Two findings that narrow the hunt:

  1. The current CLI publish path is exonerated with evidence: adom-wiki (the Rust CLI) never creates or injects page.json into tarballs (verified in source), and packages published through it carry no page.json in their tarballs at all (adom/adom-wiki-cli 1.0.71 and adom/hero-studio 0.7.1, both checked anonymously: zero page.json entries). Kyle's own control (adom/adom-desktop 1.9.176, also clean) is consistent.
  2. The leaked host is NOT a per-user container: it is the LEGACY adom-wiki app host (the pre-registry wiki the *-build skills still publish through). So the injection almost certainly happens on the legacy pipeline path: a page.json generated by the old wiki (readme rendered and absolutized against its own origin) rides inside tarballs that transit that pipeline into the registry.

Asks, in order:

  1. Sweep the stored tarballs: scan every version's tarball for an injected page.json containing *.adom.cloud (or any non-wiki.adom.inc absolute origin), enumerate affected packages, and scrub or rebuild those artifacts. The affected file is unfixable by authors: it is not in their source and invisible until they unpack a published artifact.
  2. Find and fix the injection point on the legacy/import path: keep readme links relative, or absolutize against the canonical public origin only.
  3. Note the tamper-evidence angle: this class is invisible to verification because the registry seals tarballs at/after storage. Per-publisher signing (issue #85) would have made a post-pack mutation detectable; this incident is a concrete argument for prioritizing it.
  4. The legacy wiki host itself: decide whether that hostname is considered sensitive; if yes, the exposure window and downloaders are worth a look server-side.

Cross-linked from adom/adom-wiki-cli#17, which stays open as the reporter-visible tracker until the sweep confirms the blast radius.

3 Replies

Colby Knox · 2mo ago

CORRECTION from the original filer (Kyle), superseding both his first mechanism and my legacy-pipeline framing above; his revised measurements are decisive:

The page.json inside the tarball is a STALE SERVER-SIDE SNAPSHOT, frozen 2026-06-30 at page version 1.0.13 (its own updated_at says so, and its readme still documents kicad-bridge-v0.8.2, four releases back). The live page entity readme and the page repo page.json are BOTH clean today; the frozen snapshot is what pkg publish embeds into every subsequent tarball, including July publishes at 1.0.16 and 1.0.17. The internal host in it is historical readme text that was since cleaned from every live surface, which also explains why the host is the legacy wiki's (old links in old content) rather than any live absolutization.

Two consequences that reshape the fix:

  1. Cleaning the page does NOT clear the leak. The author already cleaned this page and the artifact kept leaking; it will keep leaking on every future publish until the cached snapshot is invalidated.
  2. The snapshot also ships stale CONTENT generally: month-old page metadata and documentation presented as current to anyone reading page.json from a tarball.

Revised asks: (1) invalidate whatever cache holds the 2026-06-30 snapshot for this page NOW, since every publish re-ships it; (2) embed CURRENT page content at publish time, or better, stop embedding page.json into tarballs at all (the page serves it); (3) the stored-tarball sweep from the original filing stands unchanged, but scan for stale embedded snapshots generally, not just host strings; (4) the per-publisher-signing tamper-evidence point stands unchanged.

Unchanged from my verification: the current CLI creates no page.json (packages published through it ship none), and the artifact-level measurements reproduce anonymously.

Colby Knox · 2mo ago

SECOND correction from the filer, and this one closes the mechanism hunt: NOTHING server-side injects or embeds anything. The Puppeteer bridge team proved pack adds no page.json, and the actual propagation is rebuild-from-tarball staging: a 2026-06-30 publish legitimately shipped a then-dirty page.json, and every maintainer since staged the next version by downloading the previous tarball and overlaying, inheriting the stale file while the live surfaces got cleaned. It stops when someone publishes a corrected payload (already staged for the kicad bridge).

This reduces the server asks to two, per the filer, and I endorse both:

  1. A publish-time scan that rejects or scrubs non-canonical *.adom.cloud hosts in any shipped file. The CLI half of this backstop ships today in 1.0.72 (NONCANONICAL_HOST lint at pack/lint/publish, plus STALE_PAGE_JSON, which flags a page.json snapshot whose version trails the manifest, the exact inheritance signature). A server-side gate remains worth having for non-CLI publishers.
  2. Payload reconstructibility from source, so rebuilding from the last tarball stops being the sane workflow.

The stored-tarball sweep is now diagnostic rather than urgent (no active mechanism), but still worth one pass to enumerate which historical tarballs carry internal hosts. The per-publisher-signing point (#85) stands: an inherited-but-unnoticed file is exactly what a source-anchored signature chain surfaces. And do not fix affected packages by dropping page.json from files[]: for the kicad bridge it carries 41 discovery_triggers; ship it clean instead.

Colby Knox · 2mo ago

The reported class is fully remediated, gated against recurrence, and verified. Closing with the ledger; one adjacent (different) class is documented below as follow-up material.

Ask 2, injection door: closed on prod. Publish refuses any tarball whose page.json carries adom.cloud/adom.internal absolute origins (TARBALL_INTERNAL_ORIGIN, hosts named, fix taught in the hint; valve ADOMPKG_SCAN_INTERNAL_ORIGINS=0). Verified with a live rejected probe on prod. The legacy pipeline's next publish fails loudly instead of leaking quietly, which is the forcing function for fixing it at its source.

Ask 1, sweep and scrub: done. Swept all 415 packages, 2499 stored versions. 697 tarballs carry a page.json; 179 carried internal-origin URLs somewhere in the archive. Of those, 31 artifacts had the INJECTED page.json with internal hosts (the exact reported class, including all 17 versions of adom/adom-desktop-kicad-bridge): every one is scrubbed (injected member removed, artifact rewritten, integrity recomputed, registry seal re-signed, original bytes kept server-side as .pre-scrub-77), zero failures. Kyle's measured case verified end to end: the 1.0.17 tarball still serves 200 anonymously and now contains zero page.json and zero adom.cloud occurrences.

Ask 3: agreed and on record: a post-pack mutation was invisible precisely because the seal is registry-side; this incident is concrete ammunition for per-publisher signing (#85).

Ask 4 and the adjacent class, for a decision rather than this issue: the sweep also surfaced 166 artifacts across 35 packages where internal hostnames appear in AUTHOR-SHIPPED files (READMEs, docs, and in several cases runtime code/config: adom-parts-search, adom-digikey, adom-mouser, adom-jlcpcb legitimately call their sibling service containers, so scrubbing would break them). That is not injection and I have not rewritten any of it: tampering with authored content is not a unilateral call. Notable within it: three PERSONAL workspace hostnames (two of John's, one of Colby's) which the authors should probably remove themselves, versus shared service-container hosts which every installer necessarily learns anyway. Recommended follow-up if wanted: a publish-time WARNING hint (not a block) when any member references a personal-workspace-shaped host, and author pings for the three personal cases. Full per-package table preserved with the incident notes.

The tamper-evidence gap, the legacy-pipeline source fix, and the sensitivity/audit decision on the legacy hostname remain tracked by #85, the pipeline owners (who now hit the gate), and Colby respectively.

Log in to reply.