Skip to content

AccountId must be a non-nullable Guid: the write guard and the #562 token walk are both fail-open for any other shape #673

Description

@mforce

Found by the tenant-isolation adversary seat reviewing #562 / PR #671 (2026-09-02). Latent — no such property exists today — but both layers are fail-OPEN for it, and the guard that should catch it mirrors the same predicate.

The shape

Both write-side layers key on AccountId being a non-nullable Guid:

So a future tenant-owned entity mapped with public Guid? AccountId (or a strongly-typed id with a converter) reopens exactly the #562 shape: a detached Update(stub)/Remove(stub) with AccountId = null emits UPDATE … WHERE "Id" = @id alone and the database relabels or deletes another farm's row — with every test green.

PR #671's fix round tightened AccountIdConcurrencyTokenModelTests so that the guard now selects carriers by name and non-key only and asserts ClrType == typeof(Guid) — a nullable AccountId reddens the model test instead of vanishing from it. That is a guard, not a mechanism.

Close it by construction

  1. Make the walk fail closed: an AccountId property whose CLR type is not exactly Guid throws at model build (InvalidOperationException naming the entity), so a mis-typed AccountId fails every boot and every test rather than passing unguarded.
  2. Make the interceptor fail closed on the same condition: a non-Guid AccountId value on an Added/Modified/Deleted entry under a resolved tenant throws TenantWriteMismatchException (or a dedicated exception) rather than returning.

Verify

A model-only test that maps a throwaway entity with Guid? AccountId into a ModelBuilder and asserts the walk throws; a mutation that deletes the throw must redden it. For the interceptor, a stub whose AccountId property is boxed as null on a Modified entry must be refused.

Activity

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

    area:apiAPI/endpoint layer

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions