Skip to content

ExportedUserRecord.password_hash returns the redacted placeholder instead of None #1007

Open

Description

@0z5a

Describe your environment

  • Operating System version: macOS 26 (also expected on any platform)
  • Firebase SDK version: firebase-admin 7.8.0 (main at 740b9ee)
  • Firebase Product: auth
  • Python version: 3.11

Describe the problem

When the service account is not allowed to read password hashes, the Auth backend returns base64("REDACTED"), i.e. the JSON string "UkVEQUNURUQ=", in the passwordHash field of accounts:batchGet. PR #352 added code so that ExportedUserRecord.password_hash returns None in that case, and the docstring says: "If no password is set, or if the service account doesn't have permission to read the password, then this is None."

This does not work. The marker is defined as B64_REDACTED = base64.b64encode(b'REDACTED'), which is a bytes object (b'UkVEQUNURUQ='). The value from the decoded JSON response is a str, and in Python 3 'UkVEQUNURUQ=' == b'UkVEQUNURUQ=' is False. So password_hash returns 'UkVEQUNURUQ=', which looks like a real base64 hash. The existing unit test passes a bytes value into ExportedUserRecord, so it never covers the real JSON path.

The Node.js SDK compares against a string: const B64_REDACTED = Buffer.from('REDACTED').toString('base64'); and then response.passwordHash === B64_REDACTED.

Impact: code that follows the docs and checks user.password_hash is None to skip accounts without a readable hash (for example, when exporting users for import_users()) instead gets the placeholder, and may import the literal bytes REDACTED as the password hash.

Steps to reproduce:

import base64
from firebase_admin import auth

user = auth.ExportedUserRecord({
    'localId': 'user',
    'passwordHash': base64.b64encode(b'REDACTED').decode(),  # what the JSON response contains
})
print(repr(user.password_hash))  # 'UkVEQUNURUQ=', expected None

The same happens end to end through auth.list_users() when the mocked HTTP response body contains "passwordHash": "UkVEQUNURUQ=". A real hash such as "aGFzaA==" is returned unchanged, as expected.

Relevant Code:

firebase_admin/_user_mgt.py:

B64_REDACTED = base64.b64encode(b'REDACTED')
...
        if password_hash == B64_REDACTED:
            return None

This change was drafted with Claude Code (claude-opus-5-5); the test results above are from the runs described.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions