Initial: Tibi-Doku auf Starlight-Basis (Nova-Theme, Docker-Stack, Sync-Skripte)

This commit is contained in:
apairon committed 2026-10-16 11:43:04 +00:00
commit 58502aa3c8
78 files changed
+22860

No files matched your search

+636
View File
@@ -0,0 +1,636 @@
---
title: 'Security Audit — Tibi-Server'
---
# Security Audit — Tibi-Server
> **Audit-Datum:** 27. Februar 2026
> **Scope:** Vollständiges Security-Audit des tibi-server Quellcodes
> **Methode:** Statische Code-Analyse aller sicherheitsrelevanten Komponenten
---
## Zusammenfassung
| Schweregrad | Anzahl | Status |
|-------------|--------|--------|
| **KRITISCH** | 3 | Offen |
| **HOCH** | 6 | Offen |
| **MITTEL** | 7 | Offen |
| **NIEDRIG** | 5 | Offen / Teilweise gelöst |
| **Gelöst** | 1 | ✅ |
---
## Security Architecture
### Authentication Layer
| Mechanism | Scope | Header/Cookie |
|-----------|-------|---------------|
| JWT (HMAC-SHA256) | User authentication | `X-Auth-Token` header |
| Refresh Token | Token renewal | `tibi-refresh-token` cookie (HttpOnly, SameSite=Strict) |
| Admin Tokens | Server-level CRUD, management & namespace collection access | `X-Admin-Token` header or `Authorization: Bearer` |
| Token Permissions | API key access | Configurable header/query parameter |
### Authorization Layers
1. **Role-Based:** Admin (role=0) bypasses all checks
2. **Project-Based:** Users assigned to projects via `project.users[]`
3. **Collection-Based:** Per-collection permission sets with method restrictions
4. **Pre-Filters:** Automatic query filters based on permission context
5. **Projection Restrictions:** Limit visible fields per permission level
### Data Isolation
- **Database:** Each project gets its own MongoDB database (`{prefix}_{namespace}`)
- **Filesystem:** suexec UID/GID isolation (Linux, requires root)
- **Realtime:** Each project has its own Hub instance
### Input Validation
- Collection field validation (type, required, allowZero, in, eval)
- MongoDB filter operator whitelist (dangerous operators blocked)
- File path sanitization (`CleanFilePath`)
- User input validation (username alphanumeric, password min 3 chars)
### Bulk Operation Security
Bulk operations (`POST` with JSON array body, `PUT`/`DELETE` without `:id`) require **two** permission checks:
1. The standard method permission must be granted (`post`, `put` or `delete` with `allow: true`)
2. The `bulk` flag must be set to `true` in the object form of the permission
Method permissions support two formats:
- **Boolean shorthand:** `post: true` — allows single-document POST only (no bulk)
- **Object form:** `post: {allow: true, bulk: true}` — allows both single and bulk POST
This applies equally to PUT and DELETE. Without explicit `bulk: true`, bulk endpoints return **403 Forbidden**. This ensures that existing projects are not automatically exposed to bulk operations when upgrading.
---
## Findings
### CRITICAL
#### ~~C1. MD5 Password Hashing~~ ✅ BEHOBEN (2025-02-27)
**Location:** `internal/models/login.go`, `internal/models/user.go`
**CVSS-Schätzung:** 9.1 (Kritisch) → **behoben**
**Description:** User passwords wurden zuvor mit MD5 gehasht, was kryptographisch gebrochen ist.
**Fix:** Passwort-Hashing wurde auf bcrypt (`golang.org/x/crypto/bcrypt`, DefaultCost) umgestellt mit abwärtskompatibler MD5-Migration:
- `user.go:ValidateData()` — neue Passwörter werden mit bcrypt gehasht
- `login.go:LoginCheck()` — Login prüft per `CheckPassword()` sowohl bcrypt als auch legacy-MD5
- Transparente Migration: bei erfolgreichem Login mit MD5-Hash wird automatisch auf bcrypt re-gehasht und in der DB aktualisiert
- Neue Hilfsfunktionen: `IsBcryptHash()`, `CheckPassword()` (exportiert für Tests)
- Unit-Tests: `TestIsBcryptHash`, `TestCheckPassword` + alle bestehenden Tests grün
---
#### C2. No Rate Limiting on Login Endpoint — ✅ BEHOBEN
**Location:** `internal/ratelimit/ratelimit.go`, `internal/controller/ratelimit_middleware.go`, `internal/controller/setup.go`
**CVSS-Schätzung:** 8.8 (Hoch–Kritisch)
**Description:** The `POST /api/v1/login` endpoint has no rate limiting or account lockout mechanism. Combined with MD5 password hashing and a minimum password length of only 3 characters, this makes the system highly vulnerable to brute-force attacks.
**Impact:** An attacker can attempt unlimited login attempts without any delay, lockout, or CAPTCHA. With a 3-character minimum password, the search space is trivially small.
**Lösung implementiert:**
- In-Memory Rate Limiter mit exponentiellem Backoff (1s → 2s → 4s → ... → 60s Cap)
- Duales Tracking: per IP und per Username (getrennt)
- Automatischer Reset nach 15 Minuten Inaktivität
- HTTP 429 + `Retry-After` Header bei Blockierung
- Konfigurierbar via `config.yml` (`rateLimit`-Sektion) und Environment-Variablen
- Standardmäßig aktiviert, kann via `rateLimit.enabled: false` deaktiviert werden
- Hintergrund-Cleanup-Goroutine für abgelaufene Einträge
---
#### C3. Admin Token Comparison Vulnerable to Timing Attacks — ✅ BEHOBEN
**Location:** `internal/config/admin_token.go`, `internal/controller/admin_reload.go`
**CVSS-Schätzung:** 7.5 (Hoch) → **behoben**
**Description:** Admin tokens wurden mit Go's `==` Operator verglichen, was nicht constant-time ist und Timing-Angriffe ermöglicht.
**Lösung implementiert:**
- Alle 3 Token-Vergleichsstellen auf `crypto/subtle.ConstantTimeCompare()` umgestellt:
- `config/admin_token.go:CheckAdminToken()` — Server-Admin-Token-Prüfung
- `config/admin_token.go:GetAdminTokenInfo()` — Admin-Token-Metadaten-Abfrage
- `controller/admin_reload.go:ProjectReload()` — Projekt-Admin-Token-Prüfung
- Constant-Time-Vergleich verhindert Rückschlüsse auf Token-Inhalte durch Zeitmessung
---
### HIGH
#### H1. Secrets in Plain Text Configuration
**Location:** `config.yml`, `internal/config/server.go`
**CVSS-Schätzung:** 7.5
**Description:** JWT secrets, SMTP passwords, and LLM API keys are stored in plain text in the configuration file. The shipped `config.yml` contains a hardcoded JWT secret:
```yaml
api:
jwtSecret: 237uiejakymxeou3zhakN # Hardcoded production secret in repo!
```
**Impact:** Anyone with read access to the repository or config file can forge JWT tokens, access SMTP credentials, and use LLM API keys.
**Mitigation:** Environment variable substitution is available (`${ENV_VAR}` syntax).
**Recommendation:**
- Replace all hardcoded secrets in `config.yml` with env-var references
- Warn on startup if `jwtSecret` does not use `${...}` syntax
- Add `.gitignore` entry for production configs
- Consider adding a vault integration
---
#### H2. Token Authentication via Query Parameters — ℹ️ AKZEPTIERTES RISIKO
**Location:** `internal/models/permission.go`
**CVSS-Schätzung:** 7.0
**Status:** Akzeptiertes Risiko (bewusste Design-Entscheidung) + Timing-Attack-Fix
**Description:** Token-based permissions check both headers and query parameters. Query parameters appear in server logs, browser history, referrer headers, and proxy logs, exposing authentication tokens.
**Bewertung:** Die Query-Parameter-Unterstützung ist ein bewusstes Feature für Anwendungsfälle, in denen Header nicht gesetzt werden können (z.B. direkte URL-Aufrufe, Webhooks). Das Risiko ist dokumentiert und dem Betreiber bekannt.
**Timing-Attack-Fix (2025-02-27):** Token-Vergleiche in `GetTokenPermissionSet()` wurden von `==` auf `subtle.ConstantTimeCompare()` umgestellt, um Timing-Angriffe auf Token-Werte zu verhindern.
**Empfehlung für Betreiber:**
- Token-Werte über Environment-Variablen konfigurieren (`"x-api-key:${API_KEY}"`)
- Proxy/Loadbalancer so konfigurieren, dass Query-Parameter nicht geloggt werden
- Wo möglich, Header-basierte Token-Übergabe bevorzugen
---
#### H3. Refresh Token Cookie Missing Secure Flag — ✅ BEHOBEN
**Location:** `internal/models/login.go`, `internal/config/server.go`
**CVSS-Schätzung:** 7.0 → **behoben**
**Description:** The refresh token cookie had `Secure: false` hardcoded.
**Lösung implementiert:**
- Neuer Config-Eintrag `api.secureCookies` (bool, Default: `true`)
- Environment-Variable: `API_SECURE_COOKIES`
- Beide `SetCookie`-Aufrufe (Login + Logout) verwenden jetzt `config.Server.API.IsSecureCookies()`
- Default `true` → Cookies werden nur über HTTPS gesendet
- Für lokale Entwicklung (HTTP) kann `secureCookies: false` gesetzt werden
---
#### H5. SSRF via http.fetch in Hooks
**Location:** `internal/hook/context_http.go`
**CVSS-Schätzung:** 6.5
**Description:** The `http.fetch` and `http.fetchStream` functions in hooks allow requests to arbitrary URLs without restriction. A hook can fetch internal network resources (e.g. `http://169.254.169.254/` for cloud metadata, `http://localhost:27017/` for MongoDB).
```go
request, err := http.NewRequest(method, url, reqBody)
// No URL validation — any scheme, any host, any port
```
**Impact:** Server-Side Request Forgery (SSRF) allowing access to internal services, cloud metadata endpoints, and potentially the MongoDB instance.
**Recommendation:**
- Implement an optional URL whitelist/blocklist at config level
- Block requests to private IP ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `127.0.0.0/8`) by default
- Document SSRF risks for project administrators
---
#### H6. Unrestricted Shell Command Execution in Hooks
**Location:** `internal/hook/context_exec.go`
**CVSS-Schätzung:** 6.5
**Description:** The `exec.command` function executes any binary without path or name restrictions. While arguments are passed as separate `args[]` (safe from shell injection), the command name is controlled by hook code:
```go
cmd := exec.CommandContext(timeoutCtx, command, cmdArgs...)
```
The `dir` option also allows setting arbitrary working directories:
```go
if dir, ok := options["dir"].(string); ok {
cmd.Dir = dir
}
```
**Mitigation:** Commands execute under project UID via suexec isolation. Default dir is project config directory.
**Impact:** Arbitrary code execution on the host with project-level privileges. Without suexec (non-root deployment), commands run as the server process user.
**Recommendation:**
- Implement a command whitelist at the project config level
- Restrict `dir` option through `CleanFilePath` validation
- Log all command executions for audit purposes
---
### MEDIUM
#### M1. Error Message Information Disclosure
**Location:** `internal/utils/error.go`, `internal/dbapi/helper.go`
**CVSS-Schätzung:** 5.3
**Description:** By default (`config.response.error.message` not set), the raw error message is included in API responses:
```go
errorInResponse := errorString
if config.Server.Response.Error.Message != "" {
errorInResponse = config.Server.Response.Error.Message
}
```
When `response.error.stack: true` is set, Go stack traces are exposed:
```go
if config.Server.Response.Error.Stack {
o["errorStack"] = errorStack
}
```
**Impact:** Detailed error messages can reveal internal paths, database structure, Go type names, and stack traces.
**Recommendation:**
- Default to generic error messages in production
- Only enable detailed errors when explicitly configured for development
- Never expose stack traces in production
---
#### M2. File Upload: No Size Limit or Type Validation
**Location:** `internal/controller/collection.go:75-140`
**CVSS-Schätzung:** 5.3
**Description:** File uploads via base64-encoded data in JSON have no size limit or content type validation:
```go
if i := strings.Index(srcPrefix, ","); i > 0 &&
strings.HasPrefix(srcPrefix, "data:") &&
strings.Index(srcPrefix, "base64") > 0 { // validate as data
```
The MIME type is extracted from the `data:` URL but not validated against an allowlist. Any file type can be uploaded.
**Impact:**
- Denial of service via extremely large base64 payloads (decoded in memory)
- Storage exhaustion attacks
- Upload of executable files or malicious content
**Recommendation:**
- Implement configurable maximum file size limits
- Add optional MIME type allowlists per collection field
- Consider limiting request body size at the middleware level (Gin's `MaxMultipartMemory`)
---
#### M3. File Upload: Filename Not Sanitized
**Location:** `internal/controller/collection.go:119-121`
**CVSS-Schätzung:** 5.3
**Description:** The original filename from the upload payload is used directly in the file path:
```go
if filename, ok := fileObj["path"].(string); ok {
path += "/" + filename
}
```
While the `path` is within the upload directory tree, the `filename` value is not sanitized. A filename like `../../etc/cron.d/malicious` combined with upload path could potentially escape the intended directory.
**Mitigation:** The upload path structure `uploadPath + "/" + id + "/" + field` provides some containment. The ID is server-generated.
**Recommendation:**
- Sanitize filenames: strip directory separators, use `filepath.Base()`, restrict to alphanumeric + allowed extensions
- Apply `CleanFilePath` validation to constructed upload paths
---
#### M4. ReDoS Risk via $regex Filter Operator
**Location:** `internal/dbapi/filter_valid_functions.go:28`
**CVSS-Schätzung:** 5.3
**Description:** The `$regex` operator is allowed in user-provided query filters:
```go
"$regex": true,
```
MongoDB's `$regex` uses PCRE, which is vulnerable to catastrophic backtracking with malicious patterns. An attacker can craft regex patterns that cause exponential CPU usage on the database server.
**Impact:** Denial of service via CPU exhaustion on MongoDB.
**Recommendation:**
- Set a timeout for MongoDB read operations
- Consider restricting `$regex` to authenticated users only
- Add regex complexity validation (max length, disallow nested quantifiers)
- Document ReDoS risks
---
#### M5. Prometheus /metrics Endpoint Unauthenticated
**Location:** `internal/controller/setup.go:135`
**CVSS-Schätzung:** 5.0
**Description:** The Prometheus metrics endpoint is publicly accessible without authentication:
```go
r.GET("/metrics", gin.WrapH(promhttp.Handler()))
```
**Impact:** Exposes internal metrics including request rates, error rates, latency distributions, and potentially Go runtime metrics, providing reconnaissance data to attackers.
**Recommendation:**
- Add authentication (admin token or basic auth) to `/metrics`
- Or restrict access via network-level controls and document this requirement
---
#### M6. No Request Timeout Middleware
**Location:** `internal/controller/setup.go`
**CVSS-Schätzung:** 5.0
**Description:** A TODO exists for request timeout middleware that was never implemented:
```go
// TODO: timeout middleware
```
**Impact:** Long-running requests (e.g. complex queries, large file uploads, slow hooks) can tie up server goroutines indefinitely, enabling resource exhaustion attacks.
**Recommendation:** Implement `http.TimeoutHandler` or Gin timeout middleware with configurable duration (e.g. 30s default, configurable per collection).
---
#### M7. JWT Token Contains Full User Object
**Location:** `internal/models/login.go:95-103`
**CVSS-Schätzung:** 4.3
**Description:** The JWT access token includes the full user object (minus password), including arbitrary `meta` data:
```go
userCopy := *user
userCopy.InsertTime = nil
userCopy.UpdateTime = nil
userCopy.Meta = nil
tokenData = &userCopy
```
The `Meta` is set to `nil`, but `Permissions`, `LLMBudgets`, and other fields are included. The token is not encrypted, only signed.
**Impact:** Token leakage exposes user metadata and internal permission structures. Tokens are also long-lived (60 minutes) without revocation support.
**Recommendation:**
- Minimize token claims to `{id, username, role}`
- Load full user data from DB when needed
- Consider shorter token lifetime with more frequent refresh
- Implement token revocation (e.g. via token blacklist or `iat` validation)
---
### LOW
#### L1. Typo in Log Output (Unchanged)
**Location:** `internal/controller/setup.go:62`
**Description:** Log message reads `"senty gin activated"` instead of `"sentry gin activated"`.
---
#### L2. Weak Password Policy
**Location:** `internal/models/user.go:38`
**CVSS-Schätzung:** 3.7
**Description:** Password minimum length is 3 characters:
```go
Password *string `json:"password,omitempty" bson:"password" validator:"required,min=3"`
```
**Impact:** Extremely weak passwords are permitted, compounding the brute-force risk.
**Recommendation:** Increase minimum to 8+ characters. Consider complexity requirements.
---
#### L3. `allowUpperPaths: true` in Default Config
**Location:** `config.yml:15`
**Description:** The default config allows traversal above config directory:
```yaml
security:
allowAbsolutePaths: false
allowUpperPaths: true
```
This enables `fs.readFile("../../etc/passwd")` from hooks (though constrained by suexec UID).
**Recommendation:** Default to `false` for `allowUpperPaths` and only enable when explicitly needed.
---
#### L4. Hook Cookie Functions Without Scope Restrictions
**Location:** `internal/hook/context_cookie.go`
**Description:** The `cookie.set` function in hooks can set cookies with arbitrary names, paths, domains, and security flags. A malicious hook could overwrite authentication cookies or set cookies for different domains.
**Recommendation:** Document this as a trust boundary — hook code has the same trust level as server-side code. Consider restricting cookie domain to the current request's domain.
---
#### L5. LLM Quota Race Condition
**Location:** `internal/llm/quota.go:61-104`
**Description:** LLM budget checking and recording happens in two separate operations (read, then write). Under concurrent requests, a user could exceed their token budget before the write propagates:
```go
// 1. Check budget (read)
if err := llm.CheckBudget(budgets, req.Provider); err != nil { ... }
// 2. Use tokens (API call happens here)
// 3. Record usage (write)
_ = qm.RecordUsage(auth.ID, req.Provider, resp.Model, usage, budgets)
```
**Impact:** Minor over-budget usage under high concurrency; not exploitable for significant financial impact.
**Recommendation:** Accept current behavior with documentation, or use MongoDB atomic `$inc` for budget tracking.
---
### RESOLVED
#### ~~R1. Commented-Out Collection Count Endpoint~~
**Location:** `internal/controller/setup.go`, `internal/controller/collection_count.go`
The dedicated `GET /_/:project/:collection/_count` endpoint is now implemented with full permission pipeline.
---
## Security Posture Assessment
### Strengths ✅
1. **MongoDB Operator Whitelist:** Filter operators are validated against an explicit allowlist (`filter_valid_functions.go`), blocking dangerous operators like `$where`
2. **Suexec Process Isolation:** Projects run with dedicated UID/GID on Linux, providing filesystem-level sandboxing
3. **Permission System Depth:** Multi-layered permission model (public → token → user → custom) with pre-filters and projection restrictions
4. **Hook Timeout Support:** JS hooks have configurable timeouts to prevent infinite loops
5. **Audit Logging:** Comprehensive audit trail for admin actions with token identification
6. **Bulk Operation Guard:** Requires explicit `bulk: true` config, preventing accidental mass operations
7. **File Path Sanitization:** `CleanFilePath` provides directory traversal protection with configurable strictness
8. **Error Code Tracking:** Each error gets a unique code for correlation without exposing internals (when `response.error.message` is configured)
9. **Env-Var Substitution:** Secrets can be externalized via `${ENV_VAR}` syntax
10. **Admin Token Granularity:** Fine-grained permission system with wildcards for server admin tokens
### Weaknesses ❌
1. **Cryptographic:** ~~MD5 password hashing~~ ✅ behoben (bcrypt), no salt ✅ behoben (bcrypt nutzt eingebauten Salt), weak password policy
2. **Network Security:** No TLS termination (expected to be behind proxy), ~~no rate limiting~~ ✅ behoben, ~~Cookie Secure=false~~ ✅ behoben (konfigurierbar, Default: true)
3. **Token Security:** ~~Timing-attack-vulnerable comparisons~~ ✅ behoben (constant-time compare), query parameter leakage ℹ️ akzeptiertes Risiko, no revocation
4. **Input Validation Gaps:** No file upload size limits, filename not sanitized, ReDoS via `$regex`
5. **Execution Safety:** Unrestricted shell commands and HTTP fetch in hooks (SSRF)
6. **Missing Middleware:** No request timeout, ~~no rate limiting~~ ✅ behoben (Login-Rate-Limiting), no body size limits
---
## Prioritized Remediation Plan
### Phase 1 — Critical (Immediate, 1-2 Weeks)
| # | Finding | Effort | Impact |
|---|---------|--------|--------|
| ~~C1~~ | ~~Replace MD5 with bcrypt + migration path~~ | ~~Medium~~ | ✅ **ERLEDIGT** — bcrypt + MD5-Migration implementiert |
| ~~C2~~ | ~~Add login rate limiting~~ | ~~Low~~ | ✅ **ERLEDIGT** — Exponentielles Backoff Rate Limiting (IP + Username) implementiert |
| ~~C3~~ | ~~Use `subtle.ConstantTimeCompare` for tokens~~ | ~~Low~~ | ✅ **ERLEDIGT** — Alle Token-Vergleiche auf constant-time umgestellt |
| H1 | Remove hardcoded JWT secret from config.yml | Low | Prevents token forgery from source |
### Phase 2 — High (2-4 Weeks)
| # | Finding | Effort | Impact |
|---|---------|--------|--------|
| H3 | Make cookie `Secure` flag configurable | Low | ✅ **ERLEDIGT** — `api.secureCookies` Config-Eintrag (Default: true) |
| L2 | Increase minimum password length to 8 | Low | Reduces brute-force surface |
| M1 | Default to generic error messages | Low | Prevents info disclosure |
| M6 | Implement request timeout middleware | Medium | Prevents resource exhaustion |
### Phase 3 — Medium (1-2 Months)
| # | Finding | Effort | Impact |
|---|---------|--------|--------|
| ~~H2~~ | ~~Deprecate query-parameter tokens~~ | ~~Medium~~ | ℹ️ **AKZEPTIERTES RISIKO** — bewusste Design-Entscheidung, Timing-Fix angewendet |
| H5 | Add SSRF protection for http.fetch | Medium | Blocks internal network access |
| H6 | Add exec command whitelist config | Medium | Limits command execution |
| M2 | Add file upload size/type validation | Medium | Prevents resource exhaustion |
| M3 | Sanitize uploaded filenames | Low | Prevents path manipulation |
| M4 | Add regex complexity limits | Medium | Prevents ReDoS on MongoDB |
| M5 | Protect /metrics endpoint | Low | Hides internal metrics |
| M7 | Minimize JWT token claims | Medium | Reduces exposure surface |
### Phase 4 — Hardening (Ongoing)
| # | Finding | Effort | Impact |
|---|---------|--------|--------|
| L3 | Default `allowUpperPaths` to false | Low | Tighter default security |
| L4 | Document hook trust boundaries | Low | Operational awareness |
| L5 | Atomic LLM budget tracking | Low | Prevents minor over-use |
---
## Security Best Practices
### Configuration
```yaml
api:
jwtSecret: ${JWT_SECRET} # Always use env vars for secrets
cors:
allowOrigins:
- https://yourdomain.com # Never use "*" in production
allowCredentials: true
security:
allowAbsolutePaths: false
allowUpperPaths: false # Restrict to config directory
response:
error:
message: "An error occurred" # Generic error for production
stack: false # Never in production
```
### Permissions
```yaml
permissions:
public:
methods:
get: true # Minimal public access
filter:
published: true # Always pre-filter public data
user:
methods:
get: true
post: true
put: true
filter:
author:
$subst:
expr: "auth.id" # Users only see own documents
validProjections:
- summary # Restrict visible fields
```
### Token Permissions
```yaml
permissions:
"x-api-key:${API_KEY}": # Use env vars for token values
methods:
get: true
filter:
public: true
```
### Hooks Security
```js
// Always validate input
var req = request();
var body = json.parse(req.body());
if (!body.email || body.email.indexOf("@") === -1) {
throw { status: 400, json: { error: "invalid_email" } };
}
// Use bcrypt for password hashing in custom auth flows
var hash = bcrypt.hash(body.password, { cost: 12 });
// Sanitize exec commands
var allowedCommands = ["convert", "ffmpeg", "python3"];
if (allowedCommands.indexOf(command) === -1) {
throw { status: 400, json: { error: "command_not_allowed" } };
}
```