The paid-module trust check is the mechanism that is supposed to catch a
refunded or canceled subscription even while the customer's locally-held,
validly-signed license token hasn't hit its own expires_at yet. Its own
docstring states the intended order: a fresh marketplace call reporting
active grants trust and caches; a fresh call reporting
refunded/canceled/revoked refuses and nukes the cache; an unreachable
marketplace falls back to the cache within a grace window (7 days
consumer, 30 days enterprise). The middle step is not actually
implemented as described -- refusing a canceled subscription does not
clear the cache, and the grace-window fallback never looks at what
status was cached, only how old it is.
primitives/module_entitlement.py check_server_trust() writes to the
trust cache unconditionally on every successful fresh response,
regardless of status:
trust_cache_write(ws, module_id, fresh) # runs whether status is
# "active" or "canceled"
status = token.get("status")
if status == "active":
return {"trusted": True, ...}
return {"trusted": False, "status": status, ...} # refuses now,
# cache untouched
_trust_apply_grace() -- the function consulted whenever a later check
finds the marketplace unreachable -- reads that same cache file back but
only checks the cached verification's age, never its status:
token = (cached or {}).get("trust_token") or {}
verified_at = _parse_iso(token.get("verified_at")) or 0
...
age_days = int(max(0, (now - verified_at) // 86400))
if age_days > grace_days:
return {"trusted": False, ...}
return {"trusted": True, "cached": True, ...} # status never read
This check is not cosmetic -- routes/modules.py's module-load loop gates
a license_required module's command registration directly on
check_server_trust()'s "trusted" result, and the calling code's own
comment states this exists specifically to close "the refund-abuse gap
where a canceled sub keeps granting until embedded expires_at."
Reproduction steps:
- Extract a clean station-v0.80 tarball, sys.path.insert(0, "workbench").
- Call primitives.module_entitlement.check_server_trust() with a fresh
marketplace response reporting a genuinely, validly signed
status="canceled" trust token (standing in for a real cancellation
notice from the marketplace -- confirm it correctly returns
trusted=False).
- Call check_server_trust() again, a couple of days later (well within
the 7-day consumer grace window), this time with the marketplace
unreachable (the fetch returns None, as it genuinely would on a
network failure or the operator's machine being offline).
Expected: the second call also refuses, since the marketplace already
told the station this subscription was canceled.
Actual: the second call returns trusted=True, "trusting cache (2/7 grace
days used) — marketplace unreachable" -- the exact canceled response
from step 2, now serving as grounds to grant access for up to 5 more
days (or up to 30 for an enterprise tier), simply because the marketplace
could not be reached at that moment.
Root cause: workbench/primitives/module_entitlement.py
check_server_trust() never clears or marks the trust cache when a fresh
check reports a non-active status, and _trust_apply_grace() (the
fallback path used whenever the marketplace is unreachable) determines
trust purely from the cached verification's age, never from the cached
status field.
Suggested fix: when a fresh trust check returns a non-active status,
either delete the trust cache file for that module (matching the
docstring's own "nuke cache" description) or write a cache entry that
_trust_apply_grace() can recognize as a hard refusal regardless of age,
so a known-canceled subscription cannot re-enter the grace window on a
later offline check.