2

I have defined a realtime database rule as follows:

{
  "rules": {
    ".read": false,
    ".write": false,
    "devices": {
      ".read": "auth.uid != null && query.orderByChild == 'ownerUid' && query.equalTo == auth.uid",
      "$device": {
        ".read": "data.child('ownerUid').val() == auth.uid",
        "nickname": {
          ".write": "data.parent().child('ownerUid').val() == auth.uid",
          ".validate": "newData.isString() && newData.val().length < 30"
        },
        "ownerUid": {
          ".validate": "root.hasChild('users/' + newData.val())"
        },
        ... additional fields here
      }
    }
  }
}

In my web application, using reactfire and firebase npm modules, I have queried for a device as follows:

const devicesRef = useDatabase()
    .ref(`devices`)
    .orderByChild('ownerUid')
    .equalTo(user.uid);
const { data: devices, status } = useDatabaseListData<Device>(devicesRef, { idField: 'id' });

This appears to work, but if I look in the network tab, I can see all of the data come back, not just the data that is supposed to come back. The data returned to my code is the data that I would expect.

Note in the screenshot below that all data comes back, even data that does not have ownerUid defined.

Network tab showing data leak

I am using the example from the documentation almost exactly: https://firebase.google.com/docs/database/security/rules-conditions#query-based_rules

Am I doing something wrong? or is this a bug in Firebase?

  • this might be happening due to Read and Write Rules Cascade. Rules work top-down. please check if https://firebase.google.com/docs/database/security/core-syntax#read_and_write_rules_cascade this is causing for your data leak? – Amod Gokhale Jun 24 '21 at 05:24
  • In this case, `devices` is at the top level. The only thing I can think of is if the query based rules inadvertently grants access to all of the documents if you apply the correct filters. However, the language in the query based filters section is as follows: ```Although you can't use rules as filters, you can limit access to subsets of data by using query parameters in your rules. Use query. expressions in your rules to grant read or write access based on query parameters.``` – Harrison Lambeth Jun 24 '21 at 05:30
  • To me, this implies that the cascading should not apply in that way. i.e. it should only grant access to the sections that match the query. – Harrison Lambeth Jun 24 '21 at 05:31
  • it can't be the rules since rules do no filter data - the issue should be with the query itself - I have a suspicion that it's related to the `equalto` not resolving correctly – DIGI Byte Jun 24 '21 at 06:12
  • @HarrisonLambeth - to rule out possibility of cacade rule. Can you try to change order of structure and test it out? – Amod Gokhale Jun 24 '21 at 07:32
  • @DIGIByte Unless I'm not understanding you correctly, I think the query itself is correct, as it returns the correct data within the application. It's just the network tab that shows additional data. Also, if I try querying without the equalTo and orderBy, I get a permission error. I suppose it's possible the library I'm using has an issue, but I wouldn't expect firebase to ever return non-permitted data. – Harrison Lambeth Jun 24 '21 at 08:14
  • @AmodGokhale I'm not sure of a way to simplify or change the structure as you are suggesting. The schema consists of a collection of devices at the root of the database, so there are no additional elements above in the hierarchy. Can you give me a hint about what type of change you think could be made to eliminate that possibility? – Harrison Lambeth Jun 24 '21 at 08:18
  • It might be that you are allowing the read based on the query's value being the logged-in users, but you aren't comparing it to the child property on the line `"devices": { ".read"` – DIGI Byte Jun 24 '21 at 09:06
  • Can you share an export of your database? Like a sample JSON object so we can test it ? – Dharmaraj Jun 24 '21 at 13:09

1 Answers1

0

I discovered the solution after upgrading my firebase client version and getting some new errors from it. It turns out the issue was that I was missing an index on ownerUid.

The new rules look like this:

{
  "rules": {
    ".read": false,
    ".write": false,
    "devices": {
      ".indexOn": ["ownerUid"],
      ".read": "auth.uid != null && query.orderByChild == 'ownerUid' && query.equalTo == auth.uid",
      "$device": {
        ".read": "data.child('ownerUid').val() == auth.uid",
        "nickname": {
          ".write": "data.parent().child('ownerUid').val() == auth.uid",
          ".validate": "newData.isString() && newData.val().length < 30"
        },
        "ownerUid": {
          ".validate": "root.hasChild('users/' + newData.val())"
        },
        ... additional fields here
      }
    }
  }
}