Search code examples
firebasefirebase-realtime-databasereactfire

Firebase realtime database leaks protected data in websocket connection?


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?


Solution

  • 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
          }
        }
      }
    }