# Code-Audit Mitai Jinkendo

| Feld | Wert |
|------|------|
| **Datum** | 2026-04-04 |
| **Bezeichnung** | Vollständige Code-Analyse (Backend FastAPI, Frontend React, Konfiguration) |
| **Umfang** | Stichproben + gezielte Suche (Security-Patterns, Auth, SQL, Hardcoding, Duplikate) |
| **Einschränkung** | Kein Penetrationstest, keine Laufzeit-Fuzzing-Tests; statische Analyse und Architektur-Review |

---

## Kurzfassung

Die Codebasis ist insgesamt strukturiert (modulare Router, parametrisierte SQL-Queries, bcrypt, Rate-Limits auf sensiblen Auth-Endpunkten). Es gibt jedoch **kritische Autorisierungslücken** rund um `X-Profile-Id` und die öffentlichen `/api/profiles`-Routen sowie eine **fehlende Objekt-Zuordnung bei Foto-Downloads**. Zusätzlich fallen **Konfigurations- und Versions-Inkonsistenzen**, **Legacy-/Duplikat-Code** und **Abweichungen von den eigenen Coding Rules** (direktes `fetch` im Frontend) auf.

**Gitea-Issue-Vorlagen** für die wichtigsten Punkte liegen unter `gitea/`.

---

## Kritisch (P0) – Sicherheit

### 1. `X-Profile-Id` ohne Abgleich mit der Session (`get_pid`)

**Datei:** `backend/routers/profiles.py` (Hilfsfunktion `get_pid`)

```20:29:backend/routers/profiles.py
def get_pid(x_profile_id: Optional[str] = Header(default=None)) -> str:
    """Get profile_id - from header for legacy endpoints."""
    if x_profile_id:
        return x_profile_id
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute("SELECT id FROM profiles ORDER BY created LIMIT 1")
        row = cur.fetchone()
        if row: return row['id']
    raise HTTPException(400, "Kein Profil gefunden")
```

Ist der Header gesetzt, wird **jede** authentifizierte Session auf die **beliebig gewählte** Profil-ID umgebogen. `get_pid` wird in zahlreichen Routern genutzt (Gewicht, Aktivität, Ernährung, Fotos-Upload/Liste, Vitals, Insights, …). Ein Angreifer mit gültigem Nutzer-Token kann damit **fremde Daten lesen und schreiben** (klassische IDOR-Schwachstelle).

**Empfehlung:** Profil-Kontext ausschließlich aus `session['profile_id']` ableiten; `X-Profile-Id` nur für **Admins** erlauben und gegen eine explizite Admin-Prüfung validieren – oder entfernen, wenn nicht mehr benötigt.

**Vorlage:** `gitea/TEMPLATE_P0-profile-scoping-x-profile-id.md`

---

### 2. `/api/profiles`-Endpunkte ohne Admin-Pflicht

**Datei:** `backend/routers/profiles.py`

Endpunkte wie `GET/POST /api/profiles`, `GET/PUT/DELETE /api/profiles/{pid}` nutzen `Depends(require_auth)`, **nicht** `require_admin`. Damit kann jeder eingeloggte Nutzer u. a. **alle Profile auflisten**, **Profildaten abrufen** und unter Umständen **Profile löschen** (wenn mehr als eines existiert).

Das Frontend nutzt `GET /api/profiles` in `ProfileContext.jsx` für die Profilliste – funktional erklärbar, **sicherheitstechnisch** aber nur akzeptabel, wenn ausschließlich Admins je Instanz ein Konto haben (Annahme, die im Code nicht erzwungen wird).

**Empfehlung:** Öffentliche/„current user“-API von Admin-CRUD trennen; Listen/Löschen/Anlegen fremder Profile nur mit `require_admin` und expliziter Policy.

**Vorlage:** `gitea/TEMPLATE_P0-profiles-router-authorization.md`

---

### 3. Foto-Abruf ohne Profil-Ownership-Prüfung

**Datei:** `backend/routers/photos.py` – `GET /api/photos/{fid}`

Die Route lädt die Datei nur anhand der Foto-`id`, **ohne** zu prüfen, ob `photos.profile_id` zur Session passt. UUIDs erschweren Raten, bieten aber **keinen** Zugriffsschutz, wenn IDs bekannt sind oder geleakt werden.

**Empfehlung:** `SELECT ... WHERE id=%s AND profile_id=%s` mit `session['profile_id']` (bzw. adminseitig explizite Ausnahme).

**Vorlage:** `gitea/TEMPLATE_P1-photo-ownership-enforcement.md`

---

## Hoch (P1)

### 4. CORS: Default `allow_origins=["*"]`

**Datei:** `backend/main.py`

Wenn `ALLOWED_ORIGINS` nicht gesetzt ist, erlaubt die Middleware **`'*'`** in Kombination mit `allow_credentials=True`. Das ist in Browsern problematisch (kann je nach Client zu unerwartetem Verhalten führen) und in Produktion **riskant**, falls die Umgebungsvariable fehlt.

**Empfehlung:** In Produktion kein Wildcard-Default; Startfehler oder restriktive Default-Liste für Dev dokumentieren.

**Vorlage:** `gitea/TEMPLATE_P1-cors-allowed-origins-hardening.md`

---

### 5. Session-Token in der URL (Fotos)

**Datei:** `frontend/src/utils/api.js` – Foto-URL mit `?token=...` für `<img>`-Tags; Backend: `require_auth_flexible` mit Query-Parameter.

Token in URLs landen in **Logs, Referer, Browser-Historie** – erhöhtes Leak-Risiko.

**Empfehlung:** Kurzlebige signierte URLs, Cookie-Session für Same-Origin, oder dedizierter Download-Endpoint mit Header-Auth und Blob-URL im Frontend (mit Aufwand abzuwägen).

---

### 6. Veraltete / widersprüchliche Versionsangaben

| Ort | Befund |
|-----|--------|
| `backend/main.py` | `FastAPI(..., version="3.0.0")` – wirkt wie API-/Framework-Version, nicht App-Version |
| `backend/version.py` | `APP_VERSION = "0.9l"` |
| `backend/routers/auth.py` `/api/auth/status` | `"version": "v9b"` – offensichtlich veraltet |
| `CLAUDE.md` | erwähnt `frontend/src/version.js` – **Datei im Repo nicht vorhanden** (Dokumentationsdrift) |

**Empfehlung:** Eine Quelle (`version.py` + optional Endpoint `/api/version`), Frontend aus derselben Quelle oder Build-Inject.

---

## Mittel (P2) – Inkonsistenzen & Wartung

### 7. Frontend: direktes `fetch` statt `api.js`

Laut `.claude/rules/CODING_RULES.md` sollen alle API-Calls über `api.js` laufen. Tatsächlich gibt es u. a.:

- `frontend/src/context/AuthContext.jsx`, `ProfileContext.jsx`, `PasswordRecovery.jsx`
- `frontend/src/pages/AdminPanel.jsx`, `SettingsPage.jsx`

Risiko: inkonsistente Header/Fehlerbehandlung, schwerer zu auditieren.

**Vorlage:** `gitea/TEMPLATE_P2-consolidate-api-client.md`

---

### 8. Duplikat- und Legacy-Code

| Befund | Hinweis |
|--------|---------|
| `backend/main_old.py` | Großes, nicht eingebundenes Modul – verwirrt Reviews und Suche |
| `backend/calculations/*` vs `backend/data_layer/*` | Parallelstruktur; Phase-0c nutzt `data_layer`, aber z. B. `placeholder_resolver.py` importiert noch `calculations.scores` |
| `backend/apply_v9c_migration.py` in `startup_event` | Legacy-Hook neben `db_init` – technische Schuld |

**Vorlage:** `gitea/TEMPLATE_P2-dead-code-and-metrics-dedup.md`

---

### 9. Hardcodierte Werte (Auswahl)

- **Tests:** `tests/dev-smoke-test.spec.js` – Fallback-Passwort `'5112'` wenn `TEST_PASSWORD` fehlt (nur für lokale/CI-Umgebung akzeptabel, sollte in Doku stehen und nie in Prod-Daten).
- **Playwright:** `playwright.config.js` – feste `baseURL: 'https://dev.mitai.jinkendo.de'`.
- **Admin-UI:** `AdminPanel.jsx` – Beispielzeile mit `APP_URL=http://192.168.2.49:3002` (Dokumentation im UI).
- **Farben:** Viele Hex-Werte in JSX (z. B. `NutritionCharts.jsx`, `History.jsx`) – Verstoß gegen die eigene CSS-Variablen-Regel, aber eher UX/Wartbarkeit als Security.

### 10. Passwortlänge: `/api/auth/pin` vs Registrierung

Registrierung verlangt mindestens **8** Zeichen; PIN-/Passwort-Änderung über `/api/auth/pin` erlaubt **4** Zeichen. Inkonsistent für Sicherheitskommunikation.

### 11. Dynamische SQL-`UPDATE`-Fragmente

Muster `UPDATE ... SET {', '.join(f'{k}=%s' for k in d)}` erscheint in mehreren Routern. **Soweit geprüft**, werden Keys aus kontrollierten Dicts/Pydantic-Modellen gebaut – **kein** offensichtlicher SQL-Injection-Pfad durch Rohtext. Risiko steigt, wenn später unkontrollierte Keys eingespeist werden. Whitelist pro Endpoint wäre robuster.

---

## Positiv hervorgehoben

- Parametrisierte Queries (`%s`) durchgängig üblich; keine `execute(f"...{userinput}")` in den geprüften kritischen Pfaden.
- `bcrypt` + Migration von Legacy-SHA256 in `auth.py` / Login.
- Rate Limiting auf Login, Forgot-Password, Register (`slowapi`).
- Forgot-Password gibt keine E-Mail-Existenz preis.
- Admin-Router (`/api/admin/*`) konsequent mit `require_admin`.
- Modulare Router-Registrierung in `main.py`.

---

## Mapping: Vorlagen → Themen

| Priorität | Thema | Datei unter `gitea/` |
|-----------|--------|----------------------|
| P0 | IDOR `X-Profile-Id` / `get_pid` | `TEMPLATE_P0-profile-scoping-x-profile-id.md` |
| P0 | `/api/profiles` ohne Admin | `TEMPLATE_P0-profiles-router-authorization.md` |
| P1 | Foto-Ownership | `TEMPLATE_P1-photo-ownership-enforcement.md` |
| P1 | CORS-Default | `TEMPLATE_P1-cors-allowed-origins-hardening.md` |
| P2 | `fetch` vs `api.js` | `TEMPLATE_P2-consolidate-api-client.md` |
| P2 | Legacy/Duplikate | `TEMPLATE_P2-dead-code-and-metrics-dedup.md` |

---

## Nächste Schritte (empfohlen)

1. **Sofort:** P0-Issues planen und `get_pid` / Profil-Router designen (Session-first, Admin-Override explizit).
2. **Kurzfristig:** Foto-GET mit `profile_id`-Check; CORS in allen Umgebungen prüfen.
3. **Mittelfristig:** Versionierung vereinheitlichen; `fetch`-Calls bündeln; `main_old.py` und Metrik-Duplikate bereinigen oder archivieren.

---

*Erstellt im Rahmen einer statischen Code-Analyse; bei Änderungen am Code bitte Report-Datum und Befunde aktualisieren.*
