diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md index 981702b..26859c9 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -2,25 +2,30 @@ ## Текущий фокус -**VAN-инициализация** — Memory Bank создан, задача не определена. +**Task:** `settings-ui-and-runtime-logs` +**Complexity:** Level 4 +**Phase:** **CREATIVE complete** → **`/build`** -## Статус +## Creative decisions (кратко) -- Memory Bank: создан и заполнен базовым контекстом проекта -- Активная задача: отсутствует (ожидается описание от пользователя) +| CP | Решение | +|----|---------| +| CP-1 | `/tenant-settings` + Tabs; mainNav «Параметры»; `/settings` = frontend | +| CP-2 | Monitoring tab `runtime-logs`; sub-tabs `files` / `audit` | +| CP-3 | Cleanup default `truncate`; max 512 MiB; tail 200 lines (max 2000), 256 KiB cap | +| CP-4 | Regex `^[a-z0-9][a-z0-9_.-]*\.log$` + EvalSymlinks + root prefix | -## Наблюдаемый контекст (git) +## Коммиты creative (local, no push) -Незакоммиченные файлы, вероятно из предыдущей сессии: - -- `web/src/lib/components/monitoring/MaintenancePoliciesTab.svelte` -- `web/src/lib/maintenance/policy-schedule.ts` - -Область: UI политик обслуживания PostgreSQL (расписание cron, пресеты, CRUD). +1. `ceb6f2f` — CP-1 tenant settings UI +2. `e27936c` — CP-2 runtime logs UI +3. `493575a` — CP-3 cleanup & tail +4. (pending) — CP-4 path safety ## Следующий шаг -Определить задачу и уровень сложности (1–4), затем маршрутизация: +``` +/build Phase 1 +``` -- Level 1 → `/build` -- Level 2–4 → `/plan` +OpenAPI + migration `000026` + store (см. `tasks.md`). diff --git a/memory-bank/creative/creative-runtime-logs-path-safety.md b/memory-bank/creative/creative-runtime-logs-path-safety.md new file mode 100644 index 0000000..2fd523d --- /dev/null +++ b/memory-bank/creative/creative-runtime-logs-path-safety.md @@ -0,0 +1,93 @@ +# Creative: Runtime Logs Path Safety (CP-4) + +📌 **CREATIVE PHASE START: Filesystem Path Hardening** +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +## 1️⃣ PROBLEM + +**Description:** HTTP API принимает `{filename}` и читает/удаляет файлы под `EVOBGP_RUNTIME_LOGS_DIR`. Без жёсткой политики возможны path traversal, чтение произвольных файлов при symlink attack, доступ к не-log артефактам. + +**Requirements:** +- Только файлы внутри configured root directory +- Только basenames из list API (no subdirs) +- Имена как у sidecar: `postgres.log`, `evobgp-all.log`, … +- Fail closed при любой аномалии + +**Constraints:** +- Root: `/opt/evobgp/runtime-logs` (prod) или `./runtime-logs` (dev) +- API disabled если root unset или not `evobgp-all` + +## 2️⃣ OPTIONS + +| Option | Описание | +|--------|----------| +| **A** | Strict basename allowlist regex + `filepath.Join(root, base)` + prefix check | +| **B** | Только list-then-operate: handler хранит cache allowed names из ListDir | +| **C** | Regex only, без EvalSymlinks | +| **D** | Regex + EvalSymlinks + `os.SameFile` root check | + +## 3️⃣ ANALYSIS + +| Criterion | A | B | C | D | +|-----------|---|---|---|---| +| Traversal resistance | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | +| Symlink safety | ⭐⭐⭐ | ⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐⭐ | +| Simplicity | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | +| No TOCTOU list cache | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | + +## 4️⃣ DECISION + +**Selected: Option D** — regex + resolved path under root. + +### Filename allowlist + +```regexp +^[a-z0-9][a-z0-9_.-]*\.log$ +``` + +- Длина: 3–128 +- Запрещено: `..`, `/`, `\`, null +- Примеры valid: `evobgp-all.log`, `postgres.log`, `bird2.log` +- URL path param: только unescaped basename; handler rejects `%2e%2e` + +### Resolution algorithm (`SafePath(root, filename)`) + +1. Reject if `filename != filepath.Base(filename)` or fails regex +2. `candidate := filepath.Join(root, filename)` +3. `resolved, err := filepath.EvalSymlinks(candidate)` — if not exist for new file, use `filepath.Clean(candidate)` for delete target that exists +4. `rootAbs := filepath.Clean(root)` (must be absolute after config load) +5. Require `strings.HasPrefix(resolved+string(os.PathSeparator), rootAbs+string(os.PathSeparator))` OR `resolved == rootAbs` (reject) +6. Reject if `resolved` is directory + +### ListDir + +- `os.ReadDir(root)` only — **no recursion** +- Skip subdirectories, non-matching names, hidden files (prefix `.`) +- Return only entries passing regex + +### Guard (`Enabled()`) + +```text +EVOBGP_RUNTIME_LOGS_DIR != "" +AND filepath.IsAbs(dir) OR dir cleaned to absolute at startup +AND EVOBGP_SERVICE == "evobgp-all" +AND os.Stat(root) is directory +``` + +Otherwise handlers return `503` type `runtime_logs_unavailable`. + +### Config load + +- `EVOBGP_RUNTIME_LOGS_DIR` trimmed; default empty (disabled) +- At bootstrap: `filepath.Abs(dir)`; log warning if not exists (list returns empty, not error) + +## 5️⃣ IMPLEMENTATION NOTES + +- Package: `internal/runtimelogs/safe.go` — `ValidateFilename`, `ResolveLogPath` +- Tests: `../../../etc/passwd`, `foo/../bar.log`, symlink escape (skip on Windows if needed), valid names +- OpenAPI `filename` path param pattern + description +- **Never** accept absolute paths or globs from client +- List response `name` field = basename only; UI passes same string back + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📌 **CREATIVE PHASE END: Path Safety** diff --git a/memory-bank/progress.md b/memory-bank/progress.md index 5541e18..c6f1e3e 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -1,16 +1,22 @@ # Memory Bank: Progress -## VAN Initialization — 2026-06-12 +## settings-ui-and-runtime-logs -| Шаг | Статус | -|-----|--------| -| Platform detection | ✅ Windows / PowerShell | -| Memory Bank structure | ✅ Создан | -| Core files populated | ✅ Базовый контекст EvoBGP | -| Task analysis | ⏳ Ожидает описание задачи | -| Complexity determination | ⏳ — | -| Route to workflow | ⏳ — | +| Фаза | Статус | +|------|--------| +| VAN | ✅ | +| PLAN | ✅ | +| CREATIVE | ✅ (4/4 docs, 4 commits) | +| BUILD P1 OpenAPI+store | ⏳ | +| BUILD P2 FS layer | ⏳ | +| BUILD P3 HTTP | ⏳ | +| BUILD P4 Deploy | ⏳ | +| BUILD P5 Tenant UI | ⏳ | +| BUILD P6 Runtime logs UI | ⏳ | +| BUILD P7 QA | ⏳ | +| REFLECT | ⏳ | +| ARCHIVE | ⏳ | ## Реализация -_Нет активной реализации._ +_Не начата._ diff --git a/memory-bank/tasks.md b/memory-bank/tasks.md index 1d44cc9..22b6b6b 100644 --- a/memory-bank/tasks.md +++ b/memory-bank/tasks.md @@ -8,7 +8,7 @@ |------|----------| | **Task ID** | `settings-ui-and-runtime-logs` | | **Complexity** | **Level 4** | -| **Status** | **PLAN complete** → `/creative` | +| **Status** | **CREATIVE complete** → `/build` | | **Дата VAN** | 2026-06-12 | | **Дата PLAN** | 2026-06-12 | @@ -320,7 +320,7 @@ graph TD - [x] **CP-1 uiux** — `creative-tenant-settings-ui.md` ✅ Tabs `/tenant-settings`, nav «Параметры» - [x] **CP-2 uiux** — `creative-runtime-logs-ui.md` ✅ Monitoring tab `runtime-logs` + sub-tabs files/audit - [x] **CP-3 algorithm** — `creative-runtime-logs-cleanup.md` ✅ truncate default, max 512MiB, tail 200/2000 lines, 256KiB -- [ ] **CP-4 architecture** — `creative-runtime-logs-path-safety.md` +- [x] **CP-4 architecture** — `creative-runtime-logs-path-safety.md` ✅ regex + EvalSymlinks + root prefix check --- @@ -340,7 +340,7 @@ graph TD - [x] VAN - [x] PLAN -- [ ] CREATIVE (4 docs) +- [x] CREATIVE (4 docs) - [ ] BUILD Phase 1–7 - [ ] REFLECT - [ ] ARCHIVE