Station: verified on station-v0.96 (current). File: workbench/railcall_signing.py::published_pubkey() (231, returns the on-disk doc's public_key_hex and only falls back to the seed when the file is absent) feeding workbench/primitives/module_entitlement.py::verify_module_license() (the sole anti-sharing check bound != install_pubkey_hex, 179); caller routes/modules.py:980-982 (install_pk = published_pubkey()["public_key_hex"]); same pattern in entitlement.py::_local_install_pubkey_hex() (180). Class: improper verification of a possession claim / trust of a mutable, non-secret value as the DRM anchor (CWE-345 / CWE-807).
The guarantee
A paid-module license is signed by the marketplace licensing authority and binds to one install: its docstring says "install_pubkey — which install can use it (copy to another box = fails)" and "a trial can't be shared across boxes." The intended defense against a bought/leaked license being reused on many machines is that install_pubkey must match this install.
The break — the binding anchor is a public value read from a mutable file, never tied to the private seed
verify_module_license enforces the binding with a single equality: bound = token["install_pubkey"]; if bound != install_pubkey_hex: invalid (179). The caller supplies install_pubkey_hex = published_pubkey()["public_key_hex"]. And published_pubkey() is:
def published_pubkey():
doc = _jload(_pubkey_path(), None) # .railcall_workspace/signing_pubkey.json
if isinstance(doc, dict) and doc.get("public_key_hex"):
return doc # <-- returns the FILE verbatim
seed = _load_seed() # only if the file is ABSENT
if seed: return _publish_pubkey(_publickey_from_seed(seed))
return None
It returns the file's public_key_hex verbatim and only derives from the private seed when the file is missing — it never verifies the published key actually derives from the held seed. install_pubkey is a public, non-secret value carried inside the license file itself. So the entire per-install binding reduces to: "does the string in my signing_pubkey.json equal the string in the license?" — and an attacker running their own station owns that JSON file. The signature check is genuine and untouched (verified against the pinned marketplace issuer, 166-174); the attack forges nothing and needs no private seed.
Proof (container, REAL railcall_signing.published_pubkey + module_entitlement.verify_module_license, v0.96)
A genuinely-signed license bound to a victim install (ca…); the attacker's own station has a real seed deriving a different key (591bce…):
license bound to install: cacacaca… signed by issuer: 123c4c25…
[before tamper] published_pubkey() = 591bce62… (== real seed key: True)
license valid? = False | license is bound to a different install (not this machine)
[after tamper] published_pubkey() = cacacaca… (matches real seed key: False) # overwrote signing_pubkey.json.public_key_hex := license.install_pubkey
license valid? = True | valid core license
BYPASS (invalid→valid via overwriting a public file, seed unchanged): True
Overwriting the public signing_pubkey.json with the license's install_pubkey flips the entitlement from invalid to valid — while the station's real seed still derives a different key (matches real seed key: False). The signature stays valid because it is verified against the pinned issuer, not the install key. (My stand-in issuer isolates the binding logic; a real attacker supplies the one genuine signature via a single purchased/leaked/shared license.)
Impact
The per-install DRM that gates paid modules is bypassable with no signature forgery and no possession of any private seed: the holder of one genuine marketplace-signed license (purchased, leaked, or shared) reuses it on additional machines by setting each station's signing_pubkey.json.public_key_hex to the license's install_pubkey; the entitlement then reports the paid module as licensed on that box. This defeats the license's core anti-sharing promise ("copy to another box = fails"): a paid seat can be run on more installs than paid for, and a leaked license can be reused by whoever holds it. The harm is economic (module sellers' and the marketplace's revenue), not a security compromise — see scope for the limits.
Honest scope
- Requires one genuinely marketplace-signed license (bought, trial, or leaked) and local write to
.railcall_workspace/signing_pubkey.jsonon the attacker's own box — both are things the pirate controls. It is not a remote or unauthenticated exploit; it defeats the anti-sharing / per-install binding, which is exactly the control the license claims to provide. - Side effect (real, and more than cosmetic): overwriting
signing_pubkey.jsondesyncs the published pubkey from the held seed, so the station's own signing/verification identity is now inconsistent — its receipts won't verify against the published key and some signature-dependent flows may break. The entitlement gate itself does not require the published key to match the seed, so the paid-module load/unlock still succeeds; but this is not a clean, side-effect-free clone — the pirate accepts a degraded signing identity on that box. - Threat model / where it matters: it requires a genuine license and local write to the attacker's own
.railcall_workspace/signing_pubkey.json. This matters most where the application code is immutable/signature-protected but the workspace state is writable — if the attacker can already patch the installed Python, they could just patch the license verdict directly, so the interesting case is the constrained one where only workspace files are mutable. - No remote access, code execution, signature forgery, privilege escalation, or victim-data compromise: it is a licensing/DRM/anti-sharing bypass (economic), not a station security compromise.
Distinctness
Distinct from the module-code signature self-attestation finding (that was a module's code signature verified against a manifest-embedded key). This is the entitlement/install-binding surface: published_pubkey() trusting the mutable signing_pubkey.json as the DRM anchor without proving seed possession. I did not find a community thread about the module-license install-binding trusting a mutable public file.
Fix
Bind the license to a value the holder must prove possession of, not a mutable public string: published_pubkey() (or the license-binding path) must derive-and-compare public_key_hex against _publickey_from_seed(_load_seed()) and fail/regenerate on mismatch, so a signing_pubkey.json that doesn't match the held seed is never trusted; or replace the equality check with a challenge-signature (the station signs a nonce with its seed at license-verify time, proving it holds the private key the license is bound to). Apply the same in entitlement._local_install_pubkey_hex(). A public value the buyer can copy can never anchor a per-install license.
Reviewed adversarially against the source before posting.