Skip to content

Firestore Rules emulator: denial message concatenates every match block's evaluation, and "Variable is not bound in path template." omits the variable name #11239

Open

Description

@agc-63

Environment

  • firebase-tools 15.22.4 (bundled Firestore emulator), macOS arm64
  • Driven by @firebase/rules-unit-testing (initializeTestEnvironment) from the JS SDK

What happens

When a LIST (or GET) is denied, the emulator returns one FirebaseError whose message concatenates the evaluation output of every matching match block into a single comma-joined string. Two problems:

  1. No structure. Errors from different match blocks (a specific rule and a wildcard rule) are glued into one string, so it is impossible to tell which evaluation produced the final deny or what each fragment refers to. With or() queries the same block's error repeats once per disjunctive branch, so a real ruleset easily produces 6+ fragments interleaving unrelated line numbers.
  2. Variable is not bound in path template. omits the variable name — note the double space; the name is empty. The message points at a line but not at which variable or which path template. In our case the variable is the recursive-wildcard binding (match /tenants/{tenantId}/{document=**} used as document[0]), a standard documented pattern, so the error reads as if the rule were malformed.

The combined effect sends debugging to the wrong line: you chase the wildcard line (or your own rule) while the operative deny may be a completely different evaluation (e.g. a missing document field), and vice versa. We lost a full triage session to this: two failing tests were stale seeds missing a field, but the message interleaved them with the wildcard's Variable is not bound, so the first hypothesis was a broken security rule / emulator bug.

Minimal repro

Rules (11 lines):

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /tenants/{tenantId}/docs/{docId} {
      allow list: if resource.data.accessLevel == 'public';
    }
    match /tenants/{tenantId}/{document=**} {
      allow read: if document[0] != 'docs';
    }
  }
}

Seed with security rules disabled: document tenants/T1/docs/a = { departmentIds: [] } (note: no accessLevel field).

Then, as an authenticated member of T1:

await getDocs(query(
  collection(db, 'tenants/T1/docs'),
  or(
    where('departmentIds', '==', []),
    where('departmentIds', 'array-contains-any', ['d1']),
  ),
));

Actual message (verbatim, one FirebaseError):

Property accessLevel is undefined on object. for 'list' @ L5, Variable  is not bound in path template. for 'list' @ L8
  • @ L5 is the specific match's real cause: the seeded document lacks accessLevel.
  • @ L8 is the wildcard block erroring while evaluating document[0] during the list demonstration — with the variable name missing from the message.

Against our production ruleset (~2.5k lines) the same shape produces six comma-joined fragments interleaving two unrelated line numbers, e.g.:

evaluation error at L1294:24 for 'list' @ L1294, Variable  is not bound in path template. for 'list' @ L2491, evaluation error at L1294:24 for 'list' @ L1294, Variable  is not bound in path template. for 'list' @ L2491, Property accessLevel is undefined on object. for 'list' @ L1294, Variable  is not bound in path template. for 'list' @ L2491

Expected

One structured error per failing evaluation (or a structured list), each including:

  • the match block path that was evaluated,
  • line/column,
  • for Variable is not bound in path template.: the variable name and the path template it refers to,

and ideally which evaluation(s) actually caused the final deny.

Notes

  • The deny decision in the repro is correct (fail-closed: the document lacks the field). This report is only about the diagnostic value of the message.
  • Possibly related runtime quirk worth a look: document[0] inside a {param=**} match evaluates fine for single-document GETs in our suite (hundreds of passing rules tests against this ruleset) but yields Variable is not bound in path template. during list demonstration.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      ⌂ Proxy