---
title: "Ejemplos de reglas de seguridad de Firebase: 11 atacadas"
description: "Reglas de seguridad de Firebase probadas en el emulador: qué dejaba hacer cada una a un invitado u otro usuario, qué fugas siguieron abiertas y cómo las cerré."
url: "https://madsalt.tech/es/blog/firebase-security-rules-examples/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/es/about/"
language: "es"
published: 2026-11-03
---

# Ejemplos de reglas de seguridad de Firebase: 11 atacadas

En resumen

Envié las mismas peticiones contra 11 conjuntos de reglas de Firestore como invitado, como usuario anónimo y como tres usuarios con sesión iniciada. `request.auth != null` dejó que un desconocido leyera, cambiara y borrara los pedidos de todos. Una comprobación de propietario solo sobre `resource` dejó que un usuario le pasara un pedido a otra persona. Un `{document=**}` demasiado amplio abrió una colección que tenía una regla de propietario correcta. Y un rol guardado en un documento que el usuario puede escribir lo convirtió en admin con una sola petición. Cada caso tiene abajo su arreglo, y la regla corregida aguantó las mismas peticiones.

Una app de Firebase no tiene ningún servidor suyo entre el navegador y la base de datos. La configuración del proyecto viaja en la página, y las [reglas de seguridad](https://firebase.google.com/docs/rules/insecure-rules) son lo único que separa sus datos de cualquiera que la tenga. Por eso quería ejemplos de reglas con el ataque al lado, no una lista de reglas que parecen correctas.

**Cómo probé.** Firebase Local Emulator Suite de Firebase CLI 15.34.2, el 3 de noviembre de 2026. Cargué cada conjunto de reglas, reinicié los datos y llamé a la API REST de Firestore con tokens reales del emulador de Auth: sin token, un usuario anónimo, los usuarios A y B, y un desconocido que solo se había registrado. Los datos eran inventados: los pedidos `a1` y `a2` de A, y `b1` de B.

## ¿Qué reglas de Firebase tienen fugas?

| Regla                                            | Petición                                  | Resultado    |
| ------------------------------------------------ | ----------------------------------------- | ------------ |
| `if true` (modo de prueba)                       | Un invitado lista los pedidos             | `200`, los 3 |
| `if true` (modo de prueba)                       | Un invitado borra el pedido de B          | `200`        |
| `request.auth != null`                           | Un usuario anónimo lista los pedidos      | `200`, los 3 |
| `request.auth != null`                           | Un desconocido borra el pedido de A       | `200`        |
| Comprobación de propietario solo en `resource`   | A crea un pedido cuyo dueño es B          | `200`        |
| Comprobación de propietario solo en `resource`   | A cambia el `ownerId` de su pedido        | `200`        |
| Regla de propietario más lectura `{document=**}` | A lee el pedido de B                      | `200`        |
| El usuario puede escribir su propio doc `users`  | A pone `role: admin` y lee el pedido de B | `200`        |

## ¿Qué abre realmente el modo de prueba?

```
match /{document=**} {
  allow read, write: if true;
}
```

Todo, a todo el mundo. Un invitado sin token listó los 3 pedidos, borró uno y creó uno nuevo. Nada de esto necesita una cuenta ni un secreto filtrado, solo el id del proyecto.

La versión con fecha que suele usar el modo de prueba falla de otra manera. Con `if request.time < timestamp.date(2026, 1, 1)` y esa fecha ya pasada, el invitado recibió `403` y el usuario A también al leer su propio pedido. La fuga se acabó, y la app también.

## ¿Basta con `request.auth != null`?

No. Solo dice que detrás de la petición hay alguna cuenta.

```
match /orders/{id} {
  allow read, write: if request.auth != null;
}
```

El invitado recibió `403`, y por eso esta regla parece segura en una prueba rápida. Después, un desconocido que solo se había registrado listó los 3 pedidos, cambió el total del pedido de B y borró el pedido de A, todo con `200`. Un usuario anónimo también listó los 3. Si el registro está abierto, esta regla significa "todo el que se moleste en registrarse". La documentación de Firebase también la incluye entre los [patrones inseguros habituales](https://firebase.google.com/docs/rules/insecure-rules).

## ¿Por qué la comprobación de propietario necesita `resource` y `request.resource`?

`resource` es el documento tal como está guardado. `request.resource` es el documento como quedaría tras la escritura. Una regla que mira solo el primero comprueba de quién es la fila ahora, y no dice nada de en qué la convierte el usuario.

```
match /orders/{id} {
  allow read, update, delete: if resource.data.ownerId == request.auth.uid;
  allow create: if request.auth != null;
}
```

Las lecturas se veían bien: A recibió `200` en su pedido y `403` en el de B. Las escrituras no. A creó un pedido con `ownerId` de B y recibió `200`. Luego A cambió el `ownerId` de su propio pedido a B, también `200`, y desde ese momento B podía leerlo. En una app donde los pedidos son facturas o saldos, es un usuario escribiendo en la cuenta de otro.

Esta versión rechazó todo:

```
match /orders/{id} {
  allow read, delete: if request.auth != null
    && resource.data.ownerId == request.auth.uid;
  allow create: if request.auth != null
    && request.resource.data.ownerId == request.auth.uid;
  allow update: if request.auth != null
    && resource.data.ownerId == request.auth.uid
    && request.resource.data.ownerId == resource.data.ownerId;
}
```

| Petición de A                           | Resultado |
| --------------------------------------- | --------- |
| Leer su pedido                          | `200`     |
| Leer, actualizar, borrar el pedido de B | `403`     |
| Crear un pedido cuyo dueño es B         | `403`     |
| Cambiar `ownerId` a B                   | `403`     |

Aun así dejó que A cambiara el `total` de su propio pedido, con `200`. Si el cliente no debe poder fijar ese campo, diga qué campos puede cambiar:

```
allow update: if request.auth != null
  && resource.data.ownerId == request.auth.uid
  && request.resource.data.diff(resource.data).affectedKeys().hasOnly(['note']);
```

Con esto, cambiar `total` devolvió `403` y cambiar `note` devolvió `200`.

## ¿Por qué falló mi consulta con una regla correcta?

Porque [las reglas no son filtros](https://firebase.google.com/docs/firestore/security/rules-query). Con la regla de propietario de arriba, A pidiendo toda la colección recibió `403`, aunque A es dueño de 2 de los 3 pedidos. Firestore juzga la consulta por lo que podría devolver, no por lo que hay.

| Petición de A            | Resultado     |
| ------------------------ | ------------- |
| Listar todos los pedidos | `403`         |
| Consulta `ownerId == A`  | `200`, 2 docs |
| Consulta `ownerId == B`  | `403`         |

Por eso, cuando una lista se rompe tras endurecer las reglas, hay que arreglar la consulta. Aflojar la regla para que la lista funcione es como se escriben las reglas demasiado amplias de arriba.

## ¿Puede una regla amplia anular una correcta?

Sí, y es la que buscaría primero en un proyecto con un archivo de reglas largo. Firestore [permite una petición si cualquier regla que coincida la permite](https://firebase.google.com/docs/firestore/security/rules-structure).

```
match /orders/{id} {
  allow read: if request.auth != null
    && resource.data.ownerId == request.auth.uid;
}
match /{document=**} {
  allow read: if request.auth != null;
}
```

El primer bloque parece una buena regla de propietario. Con el segundo presente, A leyó el pedido de B con `200`, un desconocido listó los 3 pedidos, y ese mismo desconocido leyó el documento de A en `users`. Nada en el primer bloque avisa. Borre la regla amplia; no hay forma de que una regla posterior quite el acceso.

## ¿Cómo se hace admin un usuario?

Este patrón aparece siempre que los roles viven en un documento:

```
function isAdmin() {
  return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
}
match /users/{uid} {
  allow read, write: if request.auth.uid == uid;
}
match /orders/{id} {
  allow read: if resource.data.ownerId == request.auth.uid || isAdmin();
}
```

"Los usuarios pueden editar su propio perfil" y "los admins pueden leer todos los pedidos" son razonables por separado. Juntas son una escalada de privilegios.

![Admin con una sola petición. El usuario A, cuyo propio documento users es escribible. Antes: Usuario A → leer pedido de B → 403; Escribir su rol: Usuario A → role: admin → 200; Después: Usuario A → leer pedido de B → 200.](https://madsalt.tech/charts/es/firebase-security-rules-examples.svg)

*Figura 1. Mi prueba en el emulador de Firebase (CLI 15.34.2), 3 de noviembre de 2026.*

El arreglo es impedir que el cliente escriba ese campo. Con `request.resource.data.role == resource.data.role` añadido a la regla de escritura, la misma petición devolvió `403` y A siguió fuera del pedido de B. Los custom claims fijados desde un servidor son la otra vía, porque un cliente no puede cambiarlos.

## Dos cosas que resultaron estar bien

Las reglas de un documento padre no se filtran a las subcolecciones. Con `allow read` en `/orders/{id}` para cualquier usuario con sesión, A leyó el pedido, y `/orders/b1/notes/n1` debajo devolvió `403`. Es el [comportamiento documentado](https://firebase.google.com/docs/firestore/security/rules-structure): las reglas se aplican solo en la ruta coincidente.

Un invitado tampoco se cuela con una comparación contra un token ausente. `resource.data.ownerId == request.auth.uid` devolvió `403` para el invitado incluso sin un `request.auth != null` aparte. Aun así escribo la comprobación, porque así la regla dice lo que quiere decir.

## ¿Cómo comprobar sus propias reglas?

Ejecute sus peticiones en el emulador como invitado, como desconocido y como un segundo usuario. Eso fue lo que expuso cada caso de arriba; leer las reglas no.

Para una primera pasada, el [revisor de reglas de Firebase](https://madsalt.tech/es/tools/firebase-rules-checker/) gratuito lee reglas pegadas sin tocar su proyecto. En los conjuntos de aquí señaló el modo de prueba, el acceso solo con sesión, la regla amplia y el rol que escribe el propio usuario. Lee solo texto, así que no puede decirle cómo son sus datos ni si una regla llegó a desplegarse.

Su `apiKey` en la página no es el problema por sí sola. La configuración web de Firebase es pública por diseño, y el [escáner de secretos](https://madsalt.tech/es/tools/secret-scanner/) gratuito la marca así, porque lo que protege los datos detrás son las reglas. Los archivos siguen otras reglas, con una fuga que las reglas no pueden detener, que probé en reglas de Firebase Storage.

**Límites.** Solo Firestore, en el emulador local, por REST, con datos inventados. La Realtime Database usa otro lenguaje de reglas y no se probó. App Check, que limita qué apps pueden llamar a su backend, no entra aquí; no sustituye a las reglas. La producción puede diferir del emulador en detalles que no comprobé.

¿No sabe qué dejan pasar sus reglas?

Me gustaría leerlas con usted. Sin pitch.

[Escríbame](https://madsalt.tech/es/#contact)
