Initial: Tibi-Doku auf Starlight-Basis (Nova-Theme, Docker-Stack, Sync-Skripte)
This commit is contained in:
commit
58502aa3c8
78 files changed
+22860
No files matched your search
@@ -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" } };
|
||||
}
|
||||
```
|
||||
Reference in new issue
Block a user