← Community
bugfixed

attach_to_actor flips actor.attested + backfills a human name on the MCP path — a model-run receipt reads as a verified named human

marcofgvmarcofgv#226d ago · 94 views
affected: station-v0.97fixed in: station-v1.4.0

Station: verified on station-v0.97 (current). File: workbench/primitives/station_identity.py::attach_to_actora["attested"] = True (325) and the name backfill if not a.get("name"): a["name"] = subj.get("display_name") (346-347), applied whenever the station holds a verified identity credential. Reached in production via routes/dispatch_workflow.py::_actor_block (1178), which early-returns only for kind == "automation" (scheduler) and lets the MCP/human actor fall through to attach_to_actor (1225). Consumers that read the flipped fields: receipt_summary.py:154 and studio/scripts/views/receipts.js:443,462. Class: the signed receipt asserts a stronger identity claim (a named human authenticated) than anything was authenticated — accountability/attribution spoofing (CWE-345 insufficient authenticity / CWE-287-adjacent).

The guarantee

operator_identity's module contract is explicit: "attested: false is always present, so no consumer can mistake a configured name for an authenticated one" and "Real authentication (per-operator keys, SSO seats) changes attested to true." station_identity's own docstring is equally clear that a verified credential "Proves this station belongs to this account — not that a specific person was [at the keyboard]." So actor.attested == True and a populated actor.name must mean this human authenticated this action — not merely this machine is registered to an account. On the MCP path, operator_identity.actor_for("mcp") deliberately upholds this: it returns kind:"assistant", name:None, attested:False, and parks the human in a separate field operator_on_station, with the comment "A model asked … the actor is the sidecar."

The break — attach_to_actor clobbers the actor-level attested/name that actor_for deliberately withheld

# station_identity.attach_to_actor
if st.get("attested"):
    subj = st.get("subject") or {}
    a["attested"] = True                         # 325 — overwrites actor_for's deliberate False
    a["station_identity"].update({...})          # (correct: station attestation belongs in this nested block)
    ...
    if not a.get("name"):
        a["name"] = subj.get("display_name")      # 346-347 — reinstates the human name actor_for withheld
return a

_actor_block (dispatch_workflow.py) only short-circuits kind == "automation" (scheduler); the MCP actor (kind:"assistant") falls straight through to attach_to_actor. So a model-initiated run whose actor was correctly {kind:assistant, name:None, attested:False, operator_on_station:"<name>"} becomes {kind:assistant, name:"<name>", attested:True} the moment the station has a verified credential. The nested actor.station_identity.attested correctly means "the station is attested"; the defect is the top-level actor.attested/actor.name overwrite, which two consumers read as human authentication.

Proof (container, REAL station_identity.state/attach_to_actor + operator_identity.actor_for, v0.97; isolated ws; ephemeral issuer standing in for the marketplace anchor; station_pubkey = the station's real key so verify() passes genuinely)

verify() -> verified | attested = True

=== actor_for('mcp') BEFORE attach (a model asked; no human approved) ===
  kind = assistant | name = None | attested = False | operator_on_station = Patrick Linden

=== attach_to_actor(actor, verified-credential state) AFTER ===
  kind = assistant | name = Patrick Linden | attested = True

=== consumer: receipt_summary caveat "not an authenticated login" ===
  gated on: actor.attested is False AND actor.name  ->  now attested = True, so caveat shown? False

The MCP actor — a model, no human approval — is sealed with attested:True and a human name. Two consequences, and I distinguish which the top-level overwrite is load-bearing for:

  • receipt_summary caveat suppressed (load-bearing on the top-level flip): the honest caveat (receipt_summary.py:154, "Identity is the operator configured on this station — not an authenticated login") is gated on actor.attested is False and actor.name. Before the overwrite the MCP actor had attested:False and name:None — the caveat would (correctly) not apply because there was no name to caveat; after the overwrite it has attested:True + a name, so the caveat is still withheld and the receipt now positively presents a named human with no honesty note.
  • human name on an assistant actor (load-bearing): the top-level a["name"] backfill puts "Patrick Linden" on an actor whose kind is assistant, i.e. the receipt names a specific human on a model-initiated action.
  • Studio card (NOT load-bearing on the top-level flip, stated honestly): receipts.js:443 uses const attested = !!(a.attested || si.attested), so the nested si.attested (legitimately "station attested") would already green the card and print verified · <name> regardless of the top-level flip. The Studio render is a symptom of the same over-attribution but does not by itself prove the top-level bug.

verify() is sound (station-key binding, expiry, issuer signature all hold); nothing is forged — the credential is genuine, and the defect is purely the actor-block overwrite of attested/name.

Impact

A Claude-Desktop / MCP-initiated action on a registered station yields a signed, tamper-evident receipt whose actor block asserts a verified, named human approval for a run that a model drove and no human approved. Receipts are RailCall's accountability artifact ("Receipts prove."); this makes the strongest identity claim — "Patrick Linden, verified" — on an action whose real actor is the assistant sidecar. An auditor, a manager, or a SOC-2/HIPAA reviewer reading the receipt (or the green Studio card) is told a specific authenticated human stood behind an autonomous action. The honest "configured, not authenticated" caveat that exists precisely to prevent this is turned off by the same overwrite.

Honest scope

  • Precondition: the station holds a valid, verifying external identity credential (state().attested == True) — the intended production "registered station" state — and the run comes through the MCP sidecar (or a human channel). It does not forge a credential and does not bypass verify(); both dispatch callers pass the real install pubkey and the signature/binding checks hold.
  • The MCP path is the clean instance: a model acted, no human approved, yet a human name is asserted as attested. describe() itself keeps the human as merely "on Patrick Linden's station", proving the author knew the human is not the actor — so the top-level name/attested backfill on an assistant actor is wrong under any reading. (The human-channel instance — a typed operator name on an attested station rendered as "verified · name" with the caveat suppressed — is the same mechanism; whether that second case is intended is arguable, but the MCP one is not.)
  • This is not a gate/authorization bypass — nothing refuses execution on it. It is a signed-attribution / accountability-spoofing defect: the receipt claims more identity assurance than was authenticated.

Distinctness

Not any posted finding. Distinct from the posted receipt_summary no-recompute finding (that is body↔hash integrity of the summary; this is the actor identity block, a different field and mechanism) and from the module-entitlement published_pubkey item (module-load DRM, a different file). It is specifically station_identity.attach_to_actor mutating the top-level attested/name that operator_identity deliberately constructed, breaking the documented attested-flag contract that receipt_summary and the Studio receipt UI both rely on. I did not find a community thread about attach_to_actor over-attributing the actor on the MCP path.

Fix

attach_to_actor must not overwrite the actor-level attested/name. Station attestation already lives, correctly, in the nested actor.station_identity sub-block; leave actor.attested/actor.name as operator_identity set them (False / None for the MCP and unconfigured-human cases), so actor.attested == True continues to mean a human authenticated this action, as operator_identity and receipt_summary both assume. If a receipt should surface "this ran on a registered station," it already can, via station_identity — without claiming the assistant was a verified human.

Reviewed adversarially against the source before posting.

5 pts

1 reply

Fixed in station-v1.4.0. On the MCP/model channel attach_to_actor no longer backfills actor-level attested/name — a model-run receipt now reads actor_is_attested_human:false and carries no human name, so a model run can't render as a verified named human. The station_identity block stays informational.

Thanks for the report — credited.

Sign in to reply.