Skip to content

id(currency): sign_authorisation never calls validate — a signed authorisation naming another issuer or with rate_den = 0 is dead on arrival, and signing succeeds silently #6422

Open

Description

@dezren39

id(currency): sign_authorisation never calls validate — a signed authorisation naming another issuer or with rate_den = 0 is dead on arrival, and signing succeeds silently

Summary

pkgs/id/src/currency.rs has exactly one validity contract for a RateAuthorisation body — RateAuthorisation::validate (issuer must equal the event author; rate_den must be non-zero) — and the fold enforces it (fold_authorisation filters on body.validate(author).is_ok(), currency.rs:299 at origin/main a2b4345e80). But the signing entry point, sign_authorisation (currency.rs:328), never calls it: it serializes the body and hands it straight to chain::sign. A signer can therefore produce a properly signed, chain-valid event whose body is invalid by the module's own two hard rules, and nothing anywhere will ever fold it as Live:

  • Issuer mismatch: body names issuer B, signed by A. fold_authorisation(.., expected_issuer = B) ignores the event (its author is A, not B); fold_authorisation(.., expected_issuer = A) drops the body at the validate filter (body names B). The signed object is unreachable in every fold.
  • rate_den = 0: validate rejects it as ZeroRateDenominator, so the fold drops it — yet it sits on the issuer's chain as a signed version, and (per the fold's version-selection order: filter first, then max-by-version) it does not shadow a good lower version, but it does permanently occupy its sequence slot and reads as a real signed authorisation to any consumer that decodes without folding.

This is the sign-side half of the contract the artifact module already closed in #5623 ("validate known-kind bodies in SignedArtifact::sign / sign_after"): a signed object should be validated as strictly at creation as it is at consumption, so the mistake surfaces at sign time — where the caller can fix it — instead of as a silent no-op in every later fold.

Evidence (verified this run, origin/main a2b4345e80)

  • git grep -n "validate" pkgs/id/src/currency.rs returns exactly three sites: the definition (currency.rs:153), the fold filter (currency.rs:299), and one test (currency.rs:513). sign_authorisation and sign_revoke contain no validation call.
  • Probe (laptop, worktree of origin/main at a2b4345e80, integration test pkgs/id/tests/probe_currency_sign.rs in worktree ~/git/fleet/probe-currency-sign-wt, run with cargo test --test probe_currency_sign -- --nocapture): sign(issuer-mismatch body) -> Ok, sign(rate_den=0 body) -> Ok, sign(valid body) -> Ok (control), and the mismatched body folds to None for both the signing issuer and the named issuer — dead on arrival, exactly as the code reading predicts. (The probe test asserts the desired refusal, so it fails on current main at the issuer-mismatch assertion; the printed lines above are the observed behavior.)
  • Existing test coverage signs only a valid body (signed_events_round_trip_through_the_fold), so the gap produces no red signal today.

Why it matters

sign_authorisation is the only sanctioned way to create these objects (Phase 1 of #2290; the conversion engine #5536 builds on it). An LP tool that mis-sets issuer_id (e.g. copies a counterparty's body as a template) or a rate with a zero denominator gets a successful signature, a chain event, and a reference hash to hand out — and the authorisation simply never exists for any consumer. That is the worst failure shape for a signed-object system: creation reports success, consumption silently ignores. Failing at sign time with the existing ValidateError vocabulary costs one call; the artifact precedent (#5623) shows the fleet's chosen shape for exactly this fix.

Acceptance criteria

  • sign_authorisation refuses a body that validate would reject for the signing key's issuer (issuer mismatch, rate_den = 0), surfacing the refusal through SignError (a new variant wrapping ValidateError is fine); no event is produced for an invalid body.
  • Tests: signing an issuer-mismatched body and a rate_den = 0 body both error; the existing valid-body round-trip test still passes unchanged.
  • The fold's behavior is unchanged (invalid bodies from other/legacy signers are still ignored, not folded).
  • Out of scope: extending validate's rule set — that is id(currency): RateAuthorisation::validate accepts internally inconsistent bodies — min>max, empty/inverted window, fee_bps>10000, empty/same ledgers #4071, open separately. This issue only enforces the two rules that already exist, at sign time.

Notes

Activity

  1. developing-today commented on Nov 6, 2026

    @developing-today
    Contributor
  2. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/small-fixQueue packet kind: bounded fix with reproduction and targeted tests
    source/creatorFiled by the new-issue-creator job
    priority/normalOrdinary priority (the default)
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 6, 2026
  3. dezren39 commented on Nov 6, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  4. dezren39 commented on Nov 6, 2026

    @dezren39
    MemberAuthor

    Agent note — task 10861 handed off (lane code-n)

  5. added
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 6, 2026
  6. 57 remaining items

  7. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 6, 2026
  8. dezren39 commented on Nov 6, 2026

    @dezren39
    MemberAuthor

    Handed off to opencode claimant — task 10861, issue #6422.

    Lane code-r claimed task 10861; §1b lane assignment returned opencode. Re-verified fresh via gh (2026-11-06): issue #6422 is OPEN with no hard-stop labels (held, user/owned, do-not-merge, do-not-auto-edit absent) and no open PR found.

    Checkpointed handed_off with execution_lane=opencode; parking task 10861 for 1200s pending the claimant launching the bound opencode session. Attempts are now 5/8. If the claimant has not launched by lease expiry, task 10861 returns to the queue.

  9. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 6, 2026
  10. developing-today commented on Nov 6, 2026

    @developing-today
    Contributor

    🤖 Agent note · picked up

  11. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 6, 2026
  12. developing-today commented on Nov 6, 2026

    @developing-today
    Contributor

    🤖 Agent note · handed off

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    app/idPackage/app: id (pkgs/ or apps/ in this repo)kind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)queue/parkedQueue task is parked (waiting on CI, time, or an event)source/creatorFiled by the new-issue-creator jobsource/design-architectFiled by the repo design architect jobstatus/parkedDeliberately paused with a recorded resume conditionstatus:to-dotask/small-fixQueue packet kind: bounded fix with reproduction and targeted testswork/assignedQueue worker lifecycle state

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions