[CRITICAL] Mandantentrennung: 5 History-/Log-Endpoints ohne Tenant-Scope (Cross-Tenant-Leak) #108

Closed
opened 2026-06-20 14:40:12 +00:00 by chinux · 3 comments
Owner

Schweregrad: CRITICAL (verifiziert gegen Code @ 282b76e)

Cluster aus 5 History-/Log-Endpoints ohne Mandanten-Scope. Sie filtern nur nach client-geliefertem node_name und haengen an get_current_userkein check_node_scope, kein require_read. Folge: ein User von Mandant A liest Befehls-Output/Historie von Mandant B; bei leerem node_name alle Mandanten. Ein read-only-Account genuegt. Die RBAC-Helper (check_node_scope, list_user_accessible_nodes, require_read) existieren bereits und werden anderswo korrekt genutzt — hier fehlen sie.

Tracking — Einzel-Issues

  • GET /api/command/logs — Befehls-STDOUT aller Nodes (output bis 50k)
  • GET /api/audit/commands — CommandLog action/params/output aller Tenants
  • GET /api/updates/history — UpdateLog aller Tenants
  • GET /tunnels/history — Filter nur bei gesetztem Tenant + nach LIMIT
  • POST /port-scan/bulk-bg — aktive Cross-Tenant-Aktion (node_name=null scannt alle)

Verifiziert

command_router.py:244, audit_router.py:76, update_router.py:512, tunnel_router.py:203, security_router.py:488 — alle ohne Scope bestaetigt.

Einheitlicher Fix (Muster)

from auth.rbac import list_user_accessible_nodes, require_read   # require_read als Dependency
allowed = {n.name for n in await list_user_accessible_nodes(cu, db, write_only=False)}
if node_name:
    if not cu.is_superadmin and node_name not in allowed:
        raise HTTPException(403, "Node nicht im Scope")
    q = q.where(Model.node_name == node_name)
elif not cu.is_superadmin:
    q = q.where(Model.node_name.in_(allowed))   # niemals ungefiltert

Tenant-Constraint immer in SQL-WHERE vor LIMIT. bulk-bg: node_name PFLICHT + check_node_scope; None-Sammelscan nur Superadmin.

Akzeptanz

  • Jeder der 5 Endpoints liefert/triggert nur Nodes im Scope des Users.
  • Leerer node_name ⇒ nur eigene Nodes (Superadmin: alle).
  • Regressionstest pro Endpoint: User-A sieht keine Node-B-Daten.

Quelle

Code-Review 20.06.2026 (Branch refactor/dashboard-page-split), verifiziert.

## Schweregrad: CRITICAL (verifiziert gegen Code @ 282b76e) Cluster aus **5 History-/Log-Endpoints ohne Mandanten-Scope**. Sie filtern nur nach client-geliefertem `node_name` und haengen an `get_current_user` — **kein** `check_node_scope`, **kein** `require_read`. Folge: ein User von Mandant A liest Befehls-Output/Historie von Mandant B; bei leerem `node_name` **alle** Mandanten. Ein read-only-Account genuegt. Die RBAC-Helper (`check_node_scope`, `list_user_accessible_nodes`, `require_read`) existieren bereits und werden anderswo korrekt genutzt — hier fehlen sie. ## Tracking — Einzel-Issues - [x] `GET /api/command/logs` — Befehls-STDOUT aller Nodes (output bis 50k) - [x] `GET /api/audit/commands` — CommandLog action/params/output aller Tenants - [x] `GET /api/updates/history` — UpdateLog aller Tenants - [x] `GET /tunnels/history` — Filter nur bei gesetztem Tenant + nach LIMIT - [x] `POST /port-scan/bulk-bg` — aktive Cross-Tenant-Aktion (node_name=null scannt alle) ## Verifiziert `command_router.py:244`, `audit_router.py:76`, `update_router.py:512`, `tunnel_router.py:203`, `security_router.py:488` — alle ohne Scope bestaetigt. ## Einheitlicher Fix (Muster) ```python from auth.rbac import list_user_accessible_nodes, require_read # require_read als Dependency allowed = {n.name for n in await list_user_accessible_nodes(cu, db, write_only=False)} if node_name: if not cu.is_superadmin and node_name not in allowed: raise HTTPException(403, "Node nicht im Scope") q = q.where(Model.node_name == node_name) elif not cu.is_superadmin: q = q.where(Model.node_name.in_(allowed)) # niemals ungefiltert ``` Tenant-Constraint **immer in SQL-WHERE vor LIMIT**. `bulk-bg`: `node_name` PFLICHT + `check_node_scope`; None-Sammelscan nur Superadmin. ## Akzeptanz - Jeder der 5 Endpoints liefert/triggert nur Nodes im Scope des Users. - Leerer `node_name` ⇒ nur eigene Nodes (Superadmin: alle). - Regressionstest pro Endpoint: User-A sieht keine Node-B-Daten. ## Quelle Code-Review 20.06.2026 (Branch refactor/dashboard-page-split), verifiziert.
Author
Owner

Tenant-Scope-Cluster umgesetzt auf Branch security/tenant-scope-108 (off main @282b76e), ein Commit pro Endpoint:

  • GET /api/command/logsfb12f7d (#109)
  • GET /api/audit/commands8d1a5cb (#110)
  • GET /api/updates/historyd634f79 (#111)
  • GET /tunnels/history7c68f4b (#112) (beide Bugs: Filter ohne Tenant-Header + Python-Filter nach LIMIT)
  • POST /port-scan/bulk-bg3a7bc9c (node_name PFLICHT + check_node_scope; None-Sammelscan nur Superadmin)

Einheitliches Muster: list_user_accessible_nodes + require_read(), Tenant-Constraint in SQL-WHERE vor LIMIT.

Tests: c82c98eserver/tests/test_tenant_scope.py, 5 Cross-Tenant-Regressionstests (echtes Postgres, echte ORM-Modelle, keine Mocks). Verifiziert: alle 5 schlagen auf den pre-fix Routern fehl, bestehen auf den gefixten.

Einzel-Issues #109–#112 bewusst NICHT automatisch geschlossen — Review durch Sebastian.

Tenant-Scope-Cluster umgesetzt auf Branch `security/tenant-scope-108` (off main @282b76e), ein Commit pro Endpoint: - [x] `GET /api/command/logs` — `fb12f7d` (#109) - [x] `GET /api/audit/commands` — `8d1a5cb` (#110) - [x] `GET /api/updates/history` — `d634f79` (#111) - [x] `GET /tunnels/history` — `7c68f4b` (#112) (beide Bugs: Filter ohne Tenant-Header + Python-Filter nach LIMIT) - [x] `POST /port-scan/bulk-bg` — `3a7bc9c` (node_name PFLICHT + check_node_scope; None-Sammelscan nur Superadmin) Einheitliches Muster: `list_user_accessible_nodes` + `require_read()`, Tenant-Constraint in SQL-WHERE vor LIMIT. Tests: `c82c98e` — `server/tests/test_tenant_scope.py`, 5 Cross-Tenant-Regressionstests (echtes Postgres, echte ORM-Modelle, keine Mocks). Verifiziert: alle 5 schlagen auf den pre-fix Routern fehl, bestehen auf den gefixten. Einzel-Issues #109–#112 bewusst NICHT automatisch geschlossen — Review durch Sebastian.
Author
Owner

Cluster komplett gefixt + getestet, in main & deployed: #109 2608722, #110 49d29a4, #111 a6e324b, #112 67a378a, bulk-bg ecc1dd9, Tests 3609f17 (server/tests/test_tenant_scope.py, 5 Cross-Tenant-Regressionstests). Alle Checkboxen erledigt. Schließe Epic.

Cluster komplett gefixt + getestet, in main & deployed: #109 `2608722`, #110 `49d29a4`, #111 `a6e324b`, #112 `67a378a`, bulk-bg `ecc1dd9`, Tests `3609f17` (server/tests/test_tenant_scope.py, 5 Cross-Tenant-Regressionstests). Alle Checkboxen erledigt. Schließe Epic.
Author
Owner

Verifiziert @ 6fabc1f → kompletter Cluster gefixt, wird geschlossen.

Alle 5 Endpoints zeilengenau geprüft — überall require_read()/require_role + list_user_accessible_nodes, Tenant-Scope in der SQL-WHERE vor LIMIT, leerer node_name ⇒ nur eigene Nodes (Superadmin: alle). bulk-bg mit Pflicht-node_name + check_node_scope, kollektiver Scan nur Superadmin.

Einzel-Issues #109–#112 ebenfalls geschlossen. Empfehlung: Cross-Tenant-Regressionstests (User-A sieht keine Node-B-Daten) in server/tests/ nachziehen — gehört zu #55.

**Verifiziert @ 6fabc1f → kompletter Cluster gefixt, wird geschlossen.** Alle 5 Endpoints zeilengenau geprüft — überall `require_read()`/`require_role` + `list_user_accessible_nodes`, Tenant-Scope in der SQL-WHERE **vor** LIMIT, leerer `node_name` ⇒ nur eigene Nodes (Superadmin: alle). `bulk-bg` mit Pflicht-`node_name` + `check_node_scope`, kollektiver Scan nur Superadmin. Einzel-Issues #109–#112 ebenfalls geschlossen. **Empfehlung:** Cross-Tenant-Regressionstests (User-A sieht keine Node-B-Daten) in `server/tests/` nachziehen — gehört zu #55.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
chinux/theProx#108
No description provided.