Skip to content

flow roles: grant-window start time has no signature or author filter — anyone can back-date a role grant #1063

Description

@data-bot-coasys

Summary

role_grant_timestamps (rust-executor/src/perspectives/flow_evaluator.rs:477-491) computes the start of a role-grant eligibility window as .min() over the timestamps of every link on the role instance that names the DID. That filter chain is:

.filter(|l| names_did(&l.data.target))
.filter_map(|l| parse_link_timestamp(&l.timestamp).map(|dt| (dt, l.timestamp)))
.min()

No signature check. No author check. Compare the revocation branch twelve lines below (:503), which does check both — proof.valid == Some(true) plus the authority filter applied by revocation_authorised:

.filter(|l| l.proof.valid == Some(true) && names_did(&l.data.target))

The asymmetry runs in the permissive direction. An unsigned or forged revocation is correctly ignored; an unsigned or forged grant link silently back-dates the window.

Attack

Role SDNA says author: did:admin. roles.rs enforces that author condition on the role instance itself and, via revocation_authorised, on tombstones — but never on the grant timestamp.

  1. Admin grants Bob the role at 10:00.
  2. Bob voted at 09:00 — correctly ineligible, the vote does not count.
  3. Anyone at all (not the admin, no signature needed) writes a second didProperty link on that instance naming Bob, stamped 08:00.
  4. .min() moves the left edge of the window to 08:00. Bob's 09:00 vote is now eligible.

The roles.rs module doc accepts a back-dated revocation by an admin as in-authority. A back-dated grant by a non-granter is not in that accepted set.

Why the obvious fix is wrong

Adding proof.valid == Some(true) to mirror the revocation branch is not sufficient and can land more permissive than the bug:

Scope for the fix

All three together, or not at all:

  1. Author filter on grant links, consistent with how roles.rs gates the instance and tombstones.
  2. Signature predicate, symmetric with revocations.
  3. Fallback semantics — decide explicitly what granted_at: None means now that it can be produced by a rejected link rather than only by an absent one. Falling back to the instance timestamp is not defensible once filtering is real; this should fail closed.

Needs a regression test proving a broken grant signature collapses the window rather than widening it, and one proving a non-granter cannot move the left edge.

Sequencing

Blocked on PR 1 of the #1046 split (proof.valid: None must round-trip as None, not Some(false)). Deliberately kept out of #1062: fixing it there would give that PR a dependency on #1046 landing first, which it does not have today.

Provenance

Found by @lucksus during review of #1062; independently confirmed and correctly re-located (it is flow_evaluator.rs, not roles.rs — that line in roles.rs is test code) by Marvin in #1062 (review), who also identified the fallback trap above.

Activity

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

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions