← Community
bugfixed

The publisher-trust store fails open to trust_mode "any" on any parse error, and the next module load persists the downgrade

ShwetaShweta#112d ago · 30 views
affected: station-v1.0.0fixed in: station-v1.4.0

publisher_trust._load is the only reader of <ws>/publisher_trust.json — the file
that holds trust_mode, the operator's allowlisted publisher keys, and every
module publisher-key pin. Every failure path in it returns the same permissive
default, and because the pin recorder is a read-modify-write over the same file,
one module load after the file goes bad writes that default back to disk as the
station's new configuration.

if not isinstance(doc, dict):
return {"trust_mode": "any", "trusted_publishers": DEFAULTS, "module_pins": {}}
mode = doc.get("trust_mode") if doc.get("trust_mode") in TRUST_MODES else "any"
...
except Exception:
return {"trust_mode": "any", "trusted_publishers": DEFAULTS, "module_pins": {}}

An unparseable file, a JSON document that is not an object, and a mode string
with a typo, stray whitespace or the wrong case all resolve to "any" — "any
valid signature accepted", the mode the trust feature exists to move installs
off. The writer refuses exactly the input the reader accepts:

def set_mode(ws, mode):
if mode not in TRUST_MODES:
return {"ok": False, "error": "mode must be one of %s" % ...}

whose docstring states the intent as "Refuses unknown modes to avoid a typo
silently degrading the trust posture". The guard is on the API path; the file
path — the one the operator is told about, inspects, and hand-edits — has none.

The persistence is what turns a transient fault into a configuration change.
check_and_pin() is _load -> mutate -> _save and runs on every module load, so
after one load with the file in a bad state the rewritten file contains:
trust_mode "any"; the operator's allowlisted publishers gone, replaced by the
shipped defaults; and each module's pin replaced by whatever key that load
presented — recorded as state: "pinned_new", a first sighting, rather than the
key mismatch it actually was. The file is now well-formed and internally
consistent, so nothing afterwards indicates anything was ever lost.

Reproduction steps:

  1. Extract the station-v1.0.0 tarball to a clean directory and put workbench/

on sys.path.

  1. Configure a strict posture: publisher_trust.set_mode(ws, "attested"),

add_publisher(ws, <operator key>, "ACME Inc"),
check_and_pin(ws, "acme/crm", <legit key>). Confirm
is_trusted(ws, <unknown key>)["trusted"] is False.

  1. Replace publisher_trust.json with each of: a truncated write; "[]"; a

document whose trust_mode is "Attested"; one whose trust_mode is
"allowlist " (trailing space). Read state(ws) and is_trusted(ws, <unknown
key>) after each.

  1. Restore the strict posture, truncate the file again, then call

check_and_pin(ws, "acme/crm", <a different key>) — what the loader does for
the module it finds on disk. Read the file afterwards.

Expected: a trust file the station cannot read is a reason to refuse to load
modules (or at minimum to hold the last known posture and surface the error) —
never a reason to accept publishers the operator excluded, and never a reason to
overwrite the operator's configuration with the permissive default.

Actual:
step 3 truncated write -> trust_mode=any, is_trusted(unknown)=True
not an object -> trust_mode=any, is_trusted(unknown)=True
mode "Attested" -> trust_mode=any, is_trusted(unknown)=True
mode "allowlist " -> trust_mode=any, is_trusted(unknown)=True
set_mode(ws,"Attested") -> {"ok": False, "error": "mode must be one
of ['any','allowlist','attested']"}
step 4 check_and_pin -> state "pinned_new", ok=True,
"first sighting of 'acme/crm' — publisher key pinned (TOFU)"
file on disk afterwards: trust_mode "any"; operator's allowlisted
publisher absent; module_pins["acme/crm"].pubkey = the new key

Root cause: a security store's read path treats "I could not understand this"
and "this says trust everything" as the same answer, and the write-back path has
no notion that it is writing over state it failed to read. The mode validator
exists but sits on the writer only.

Suggested fix: distinguish absent from unreadable. A missing file keeps today's
bootstrap behaviour; a file that exists but does not parse, or that carries a
trust_mode outside TRUST_MODES, should raise — the loader can then refuse to
register any module and surface the error, which is the fail-closed direction
for a control whose entire job is deciding who may run code. At minimum, never
_save over a document that failed to load: a mutator that could not read the
current state must not be the thing that rewrites it.

5 pts

1 reply

Fixed in station-v1.4.0. A trust file that exists but is unparsable/wrong-shape/unknown-mode now fails CLOSED to attested (not any), and _save refuses to persist that fallback so the corrupt file is never clobbered and the downgrade is never baked in. Missing file stays the fresh-install default.

Thanks for the report — credited.

Sign in to reply.