Component: primitives/team_jobs.py — team job offer / worker execution (station v0.97).
The asymmetry. A live team job must target one worker pubkey, never a role: fan-out — otherwise one live effect runs on every worker in the fleet (double-charge). That invariant is enforced only on the sender:
def offer_job(ws, *, to, workflow_id, ..., dry_run=True):
...
if to.startswith("role:") and not dry_run: # L96–99
raise ValueError("live jobs must target ONE worker pubkey, not a role fan-out")
The worker never re-checks it. handle_job_offer verifies the worker role, spec_mode, workflow_id, and the spend cap, then runs the job with the dry_run flag taken straight from the envelope body — it never inspects env["to_pubkey"]:
def handle_job_offer(ws, env, member, *, runner): # L198
...
result = runner( # L260
workflow_id=wf_id,
context=body.get("context") or {},
max_spend_cents=job_cap,
dry_run=bool(body.get("dry_run", True)), # ← from the body; to_pubkey never consulted
...)
And team_mesh.verify_envelope (the receiver-side chain) validates the sender's membership, role (job_offer requires operator), signature, expiry, and replay — but never validates the to field. So an envelope addressed to:"role:worker" passes verification on every worker.
Consequence. An insider holding the operator role mints the job-offer envelope directly (mesh.mint_envelope(kind="job_offer", to="role:worker", body={..., "dry_run": False})) instead of calling offer_job, and signs it with their own seed. It verifies on every worker, and each worker's handle_job_offer runs it live — the exact live role fan-out offer_job exists to refuse. The sender-side guard is bypassed by the same operator principal it is meant to constrain, turning one live effect into a fleet-wide N-fold duplication.
Reproduction (station v0.97, isolated container). With the legitimate preconditions modeled (this station holds worker; the sender is an operator) and the mesh side-effects stubbed:
handle_job_offer(env.to_pubkey = "role:worker", body.dry_run = False)
-> runner invoked with dry_run = False (LIVE)
offer_job(to = "role:worker", dry_run = False)
-> ValueError: live jobs must target ONE worker pubkey, not a role fan-out (refused)
The identical live role fan-out is refused sender-side and executed worker-side. (Verified end-to-end originally through a real verify_envelope → (True, "ok") → worker runner(dry_run=False) COMPLETED.)
Scope (stated honestly). The actor is an insider with valid operator credentials — this is not privilege escalation or unauthenticated access. Per-worker spend stays bounded by the existing min(job, workflow) cap, so it is not an uncapped financial drain. The defect is an invariant-bypass / defense-in-depth validation gap: the "no live role fan-out" safeguard is enforced only on the send path, so a direct mint produces a fleet-wide duplication of one live effect. The N-fold amplification depends on the Relay fanning a role: envelope to all workers (single-worker live execution proven end-to-end; the fleet fan-out follows by construction). This is distinct from the retry/idempotency handling, which is covered separately by the run_id-derived-from-job_id binding. Severity is left to the maintainer.
Fix. In handle_job_offer, reject a live job whose delivering envelope was addressed to a role: fan-out — mirror the offer_job guard on the worker side: if env["to_pubkey"] starts with role: and the decoded body is not dry_run, refuse before invoking runner. CWE-284 (improper access control) / CWE-807 (reliance on an untrusted, sender-side-only check).