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

:bug: eventing: no owned policy for who may read which alert topic — alert authorization lives in ntfy's auth db

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

Los mantenedores suelen responder en 6 días

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
3/5
Tiempo estimado
Medio día
Aptitud para principiantes
62/100
Tipo de issue
Documentación
Claridad
Bastante claro
Estado de actividad
Activo
Stack tecnológico
python

Línea de trabajo

Start with DESIGN_PHASE3.md §4.1–§4.3 and §8.7 (the documented-pattern precedent), then skim eventbridge/registry.py's User dataclass and NtfyPublisher to confirm the issue's claims about where authorization does and does not live. The concrete deliverable is the first checkbox: one added paragraph in DESIGN_PHASE3.md §4 stating that alert authorization is ntfy's, not ours, and that per-user revocation does not exist — done when the doc reads true against the code. The second checkbox (a test that provisioned ACLs match the registry, or an explicit note that none exists) is the stretch goal; the third (choosing option A vs B) is a maintainer decision, not yours.

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

Descripción

Summary

There is no policy object, anywhere in this system, that says who may read which
notification topic
. What separates two users' alerts today is possession of an
unguessable topic name, optionally backed by ACLs in ntfy's SQLite auth database,
provisioned by hand with kubectl exec. The authorization decision for alerts is not one
we own, evaluate, or can audit.

This is not a claim that the design is wrong — DESIGN_PHASE3.md §4.1–§4.3 is explicit
that the name is a capability and that auth-default-access: deny-all is "the control
that makes this isolation rather than obscurity". It is a request to record the gap as a
gap, because no document currently states it plainly and the question comes up
immediately when anyone asks how permissions work.

What exists, and what enforces it

Three layers, and they are not equally real:

Layer Where it lives Enforced by
Who may submit work EB_ALLOWED_USERS (env) or EB_USER_REGISTRY_PATH (ConfigMap) our code — ghauth.is_allowed, registry.lookup
Which tenant a request belongs to derived userkey → topics, per-user store our code (resolve_caller, store_registry)
Who may read which alert topic ntfy's auth db ntfy — not us

Verified: grep for ACL / subscription-authorization logic across eventbridge/*.py and
shared/*.py returns nothing. eventbridge/handlers.py has no ntfy references at all.
The only notification-side code is NtfyPublisher, which publishes, and still reads a
single self._cfg.topic for every event — Phase 3 §4.2's per-user _topic_for is design
only.

So on ntfy.sh, the topic name is the entire access control: tenancy.ntfy_topic gives
130 bits of unguessability, and anyone who learns the name reads the stream — the prompt,
the assistant's reply and the stats footer, because compose_body deliberately packs all
of it in so a phone that cannot reach EventBridge still shows the result. §4.1 says this
outright ("the feature that makes the notification good is what makes the leak complete").

What the registry does and does not model

eventbridge/registry.py:64 is the closest thing to a role model:

@dataclass(frozen=True)
class User:
    issuer: str
    userid: str
    userkey: str
    tier: str = TIER_SHARED      # shared | isolated
    agent: str = ""
    limits: Limits = ...

tier is a placement decision (shared vs dedicated runner), not a permission. agent
selects an AgentSpec. limits is rate and size. Nothing expresses "principal P may
read topic T"
— because the model is implicitly one-to-one: a user reads their own
topic and no one else's, and that relation is re-derived from the userkey rather than
stored.

That is a defensible MVP. It becomes insufficient the moment any of these is wanted, and
all three are ordinary asks:

  • An on-call rotation seeing another user's approval requests (directly relevant to
    #897, where a sleeping approver is the designed-for failure case — §6 exists entirely
    because of it).
  • A team topic several people read.
  • Revocation. Today, removing someone's access to alerts means rotating
    EB_NTFY_TOPIC_SECRET_PATH, which rotates every user's topic at once (§4.2 names
    this as a feature for leak response, and it is — but it is the only lever, so
    per-user revocation does not exist).

Why "it's in ntfy's ACLs" is not sufficient as an answer

Even in incluster mode, where ACLs are real:

  1. The grants live outside the declared state. §4.3's own caveat: "Losing the PVC
    loses the ACLs, not just the cache — the auth database lives there."
    k8s_tenant.py apply --all is the recovery path, which makes the registry the source
    of truth for provisioning while the enforcement point is a cache of it that can
    silently drift.
  2. Provisioning is imperative and not idempotent. §4.3: the commands "only work on the
    server itself — hence kubectl exec", and "user add fails if the user exists". The
    declarative alternative is explicitly rejected because removing a user from the config
    file deletes them from the database.
  3. Nothing verifies the grants match the registry. There is no reconcile loop and no
    test. An ACL that was never applied, or applied to the wrong literal topic, fails
    open (the user reads nothing → looks like a broken notification) or, if a prefix
    grant is used by mistake, fails catastrophically (§4.3 warns: a kev1-* read grant
    "would hand them everyone's notifications").
  4. We cannot audit it. There is no endpoint, page or log line that answers "who can
    currently read alice's alerts?" The answer is in a SQLite file in a pod.

Point 3 is the one that matches this repository's own pattern: DESIGN_PHASE2.md §8.5.3
asks what happens when a control is half-configured, and here the half-configured case is
silent, in the direction of either no notifications or everyone's notifications.

Suggested direction (not a prescription)

Two shapes, and they are genuinely different bets:

A. Keep ntfy as the transport, own the policy. Add a notification-access relation to
the registry — subjects, topics, and a verb — and make k8s_tenant.py reconcile ntfy's
ACLs from it rather than emit missing grants once. Add a read-only endpoint that reports
the effective grants so the question is answerable. Cheap, incremental, and leaves
enforcement in ntfy.

B. Stop treating the ntfy topic as the authorization boundary. Notifications are
fetched through EventBridge (which already authenticates, already knows the owner, and is
already the PEP per §2.7), and ntfy carries only a content-free wake-up. Alert access then
becomes an owner-scoped read like every other read in §6.2, enforced by code we test.
Bigger change; removes the whole category.

B composes better with #897's approval flow, because an approval decision is a privileged
action and §5.4 already has to bind it to a user and a tool input — at which point the
notification channel being merely a transport is the simpler story. But A is a real option
and the cheaper one, and the choice should be deliberate rather than defaulted.

What would make this concrete

  • State the gap in DESIGN_PHASE3.md §4 — one paragraph saying alert authorization is
    ntfy's, not ours, and that per-user revocation does not exist. §8.7's pattern: leave
    the design as written, add what the code does.
  • A test that the provisioned ACLs match the registry, or an explicit note that none
    exists.
  • Decide A vs B before EB_TENANCY_MODE=multi is recommended for anything real.

Scope note

Phase 3, not Phase 4, and filed separately from #897 for the same reason as the sibling
issue: the finding belongs with the code it describes. #897 depends on this — its §3
enrollment hands a user a topic whose access control is exactly what this issue is about,
and its §3.5 already records the narrower bound ("enrollment proves the GitHub account
asked for a topic [...] not that this topic reaches only that person's phone").

Verification

Statements about what the code does were checked against eventing/ at dc1d432:
NtfyPublisher publishes to self._cfg.topic with no per-user routing; no ACL or
subscription-authorization logic exists in eventbridge/ or shared/; registry.User
carries no permission field. Statements about ntfy's behaviour and the provisioning
caveats are quoted from DESIGN_PHASE3.md §4.3, which records them as design, and were
not re-verified against a running ntfy — no cluster was involved.

Lenguaje dominante
Python
Estrellas
11
Forks
42
Merge medio
4 d 11 h
PR fusionados (30 d)
40

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.