Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

:bug: eventing: in multi-tenant mode, group-member push suppression is defeated by omitting ce_userkey

Abierto
#904 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
3/5
Tiempo estimado
1-2 días
Aptitud para principiantes
70/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
python
Área
security

Línea de trabajo

Start in eventbridge/ntfy.py at _store_for(event) and follow how StoreRegistry.for_event picks a store from ce_userkey; then read owner_index.py (OwnerIndex.owner_of) and how owners= is threaded through handlers(...) in __main__.py to mirror that pattern with an owners= parameter on the publisher. Done means a member frame with ce_userkey omitted routes to the real owner's store and is suppressed in multi-tenant mode, while the existing test test_a_member_event_that_only_claims_a_group_is_not_trusted_as_a_member still passes. Add the described stores= passthrough on the test fixture's RecordingNtfy and the multi-store suppression test.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

From the review of #903 (@aslom's comment on eventbridge/ntfy.py:222).

#903 closed the single-tenant omission forgery for group-member push suppression (#891): membership now resolves from group_members via group_of(correlationid) instead of trusting the event's ce_groupid. But which store that table is looked up in is still chosen by an attacker-supplied attribute — in multi-tenant mode, ce_userkey.

The hole

__main__.py wires the publisher as NtfyPublisher(..., stores=stores if topics.multi else None), so with EB_TENANCY_MODE=multi, _store_for(event) resolves through StoreRegistry.for_event(event), which reads getter(ce.EXT_USERKEY) — and a missing userkey is an accepted state there, not a rejected one: it routes to shared/ and increments unattributed.

So a frame that keeps ce_groupid and omits ce_userkey:

  1. for_event → the shared/ store (_multi and not is_valid_userkey → for_userkey(None));
  2. group_of(corr) on shared/ → None, because the genuine membership row was written to users/<userkey>/ by submit_members;
  3. groupid is None → return False → not suppressed.

That reproduces #891's impact exactly: a forged terminal phase=error frame pages at priority 5 with NTFY_GROUP_NOTIFY_ERRORS off, and kafka_in.py's _on_member_event gate (d.get("groupid")) is passed so nothing recounts the member either — the batch arithmetic disagrees with the page, same as before.

Reachable on the same terms as #891: signing.py — "absent attributes are omitted rather than written empty" — so a holder of any key the keyset trusts signs an event that never carried userkey, and the signature verifies.

Not a regression from #903: the pre-change code called self._store_for(event) too, so multi-tenant was equally bypassable before. #903 closes it completely for single-tenant, which is most deployments.

The fix (from the review)

Resolve the store from the ownership index instead of the event: OwnerIndex.owner_of(correlationid) -> (userkey, known) is the global correlationid → userkey index (owner_index.py, DESIGN_PHASE3 §2.6/§6.2), written by Minter at mint time — genuinely not attacker-supplied — and seeded from disk at startup. owners is already threaded to handlers(..., owners=owners if topics.multi else None), so an owners= parameter on the publisher follows the established pattern. With known=False meaning "never minted here", the unrecorded-claimant case still notifies — the property test_a_member_event_that_only_claims_a_group_is_not_trusted_as_a_member pins.

Tests to come with it: a stores= passthrough on the test fixture's RecordingNtfy, and the multi-store case from the review (a member frame with ce_userkey deleted must not buy a push). Until then, the docstring in ntfy.py is scoped to single-tenant with a pointer here.

Lenguaje dominante
Python
Estrellas
11
Forks
42
Merge medio
22 h 22 min
PR fusionados (30 d)
20

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de rossoctl/examples

Todos los issues de rossoctl/examples

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.