0.28.0 — operational hardening (plan D1–D5): CI deploy gate, versioned images, rate limiting, resource limits, health probes
D1: deploy.sh CI gate — read-only SHA via git ls-remote, Gitea commit-status
poll, portable mkdir deploy lock (macOS, no flock), TOCTOU guard, token
passed via curl --config - (not argv), graceful misconfig tolerance.
D2: version-tagged images — OIKOS_VERSION=v$VERSION, keep-last-3 prune derived
from 'docker compose config --images'; VERSION read after pull.
D3: per-IP rate limiting — new internal/httpapi/ratelimit.go (x/time/rate),
rightmost-XFF, /healthz exempt, ctx-driven sweep; disabled by default.
D4: mem_limit/cpus on all 10 compose services.
D5: staleness-aware health probes — new internal/health package wired into
scheduler (:8093) and notifier (:8094); nomos already had :8092.
Two /review passes hardened the deploy lock, TOCTOU guard, token hygiene,
and XFF handling.
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
# 2026-08-05 — Backend evaluation: architecture, security, and reliability improvements
|
||||
|
||||
Status: **In Progress** — Phase 0 code changes complete (B1, B2, B4, B5, B6, B7); B3
|
||||
is a post-deploy operational step.
|
||||
Status: **In Progress** — Phase 0 (B1, B2, B4, B5, B6, B7) and Phase 2 (D1–D5)
|
||||
complete; D1–D5 were hardened across two `/review` passes (deploy lock, TOCTOU
|
||||
guard, token hygiene, XFF rightmost-hop, ctx-driven sweep). B3 is a post-deploy
|
||||
operational step. Remaining: Phase 1 security (C1–C3), Phase 3 code quality
|
||||
(E1–E5), and Phase 4–6 backlog.
|
||||
|
||||
Scope: full evaluation of the oikos backend (Go binaries `oikos`, `nomos`, `webhook`,
|
||||
Postgres/TimescaleDB, Docker deployment, MCP server) excluding frontend clients
|
||||
(`web/` SPA and `desktop/` Wails app). Research-only pass — no code changes.
|
||||
(`web/` SPA and `desktop/` Wails app). Began as a research-only pass; Phase 0 (B)
|
||||
and Phase 2 (D) have since been implemented as code changes — see each item's
|
||||
"Status".
|
||||
|
||||
Method: four parallel research passes (Go backend structure, database schema,
|
||||
deployment/infrastructure, API/MCP design) plus Infisical secrets audit and
|
||||
@@ -211,35 +216,81 @@ binary reads secrets from env vars or plaintext files.
|
||||
## D. Operational improvements (high)
|
||||
|
||||
### D1. Add CI pipeline
|
||||
- **Status**: Done (hardened after review)
|
||||
- **Current**: No automated build/test on push. `make lint test generate-check`
|
||||
exists but is manual.
|
||||
- **Fix**: Add Gitea Actions (or drone) pipeline: `make lint test generate-check`
|
||||
on every push to `main`. Block deploy if pipeline fails.
|
||||
- **What changed**: The Gitea Actions pipeline already exists
|
||||
(`.gitea/workflows/ci.yml`). Added the missing deploy gate as step [1/8] in
|
||||
`scripts/deploy.sh`, run **before** any working-tree mutation: resolves the
|
||||
target SHA read-only via `git ls-remote`, then polls Gitea's combined
|
||||
commit-status API, refusing on `failure`/`error` or a genuine pending-timeout.
|
||||
Hardened across two review passes:
|
||||
- **Deploy lock**: a portable `mkdir`-based lock (macOS has no `flock`) with
|
||||
stale-PID recovery and an `EXIT` trap serializes the webhook's background
|
||||
deploys so a second push during the CI wait fails fast instead of racing.
|
||||
- **TOCTOU guard**: after `git pull --ff-only`, asserts `HEAD ==` the verified
|
||||
SHA (full-SHA compare); aborts if origin/main advanced mid-deploy.
|
||||
- **Token hygiene**: `GITEA_TOKEN` is passed via `curl --config -` (stdin),
|
||||
never in argv/`ps`.
|
||||
- **Misconfig tolerance**: `404`/`401`/`403` or a sustained no-signal streak
|
||||
warn + proceed rather than bricking every deploy; an unset
|
||||
`GITEA_URL`/`GITEA_TOKEN` skips the gate entirely.
|
||||
- **Risk class**: config_mutation
|
||||
|
||||
### D2. Version Docker images
|
||||
- **Status**: Done
|
||||
- **Current**: All images built as `:latest`. Rollback requires full rebuild.
|
||||
- **Fix**: Tag images with `v$VERSION` from the VERSION file in deploy.sh. Keep
|
||||
last 3 versions. Enable `docker compose up` to pin a version tag.
|
||||
- **What changed**: Every built compose service now carries an `image:
|
||||
oikos-<svc>:${OIKOS_VERSION:-latest}` tag. `deploy.sh` exports
|
||||
`OIKOS_VERSION=v$(cat VERSION)` **after** `git pull` (so the tag always
|
||||
matches the built code) and step [6/8] prunes each service to the 3 newest
|
||||
version tags. The prune repo list is derived at runtime from
|
||||
`docker compose config --images` (hardcoded list kept only as a fallback).
|
||||
- **Risk class**: config_mutation
|
||||
|
||||
### D3. Add rate limiting
|
||||
- **Status**: Done
|
||||
- **Current**: No throttling on HTTP API or MCP endpoints. An agent in a loop
|
||||
could hammer the API or exhaust DB connections.
|
||||
- **Fix**: Add `golang.org/x/time/rate` middleware to chi router. Per-IP or
|
||||
per-token rate limit with burst allowance. Separate limits for API vs MCP.
|
||||
- **What changed**: New `internal/httpapi/ratelimit.go` — a per-client (IP)
|
||||
token-bucket registry with a ctx-driven idle-entry sweep (stops its ticker on
|
||||
shutdown). Wired into `NewHandler` before CORS/auth; `/healthz` is exempt.
|
||||
Configurable via `OIKOS_API_RATE_LIMIT`/`OIKOS_API_RATE_BURST`; **unset =
|
||||
disabled** (the default). Returns RFC 9457 429 + Retry-After. `x/time`
|
||||
promoted to a direct dependency. `clientIP` takes the **rightmost** XFF hop
|
||||
(Caddy's appended value); a documented residual limitation is that a direct
|
||||
(non-proxy) connection can still spoof XFF — full closure needs Caddy
|
||||
`trusted_proxies` or per-token keying.
|
||||
- **Risk class**: config_mutation
|
||||
|
||||
### D4. Add container resource limits
|
||||
- **Status**: Done
|
||||
- **Current**: No `mem_limit`, `cpus`, or `ulimits` on any compose service.
|
||||
- **Fix**: Add memory and CPU limits to all services in docker-compose.yml.
|
||||
Suggested: API 512MB, scheduler 256MB, notifier 128MB, nomos 512MB.
|
||||
- **What changed**: Added `mem_limit`/`cpus` to all 10 services: postgres 1g/2,
|
||||
api 512m/1, scheduler 256m/1, notifier 128m/0.5, nomos 512m/1, web 64m/0.25,
|
||||
redis 128m/0.5, infisical 512m/1, migrate/seed 512m/1.
|
||||
- **Risk class**: config_mutation
|
||||
|
||||
### D5. Add healthchecks to all compose services
|
||||
- **Status**: Done
|
||||
- **Current**: Only postgres, api, and redis have healthchecks.
|
||||
- **Fix**: Add `healthcheck` to scheduler, notifier, and nomos. Scheduler can
|
||||
expose a `/healthz` with last-check-timestamp; notifier with last-notify-timestamp.
|
||||
- **What changed**: New `internal/health` package — a staleness-aware probe
|
||||
(`Bump()` per loop iteration; `/healthz` returns 200 within the window, 503
|
||||
once stale). Wired into `scheduler.Run` (:8093, 3× interval) and
|
||||
`notifier.Run` (:8094, 2 min); nomos already served `:8092/healthz`. Added
|
||||
compose healthchecks for scheduler, notifier, and nomos. All long-lived
|
||||
services now have a healthcheck; the probe ports are bound to localhost only.
|
||||
- **Risk class**: config_mutation
|
||||
|
||||
## E. Code quality (medium)
|
||||
|
||||
@@ -349,7 +400,7 @@ Priority packages (currently 0% coverage):
|
||||
2. **Phase 1 — Security** (C1–C3, B5): Nomos auth, pg_dump failure, CORS default,
|
||||
SSH host keys (now stored in Infisical per B5).
|
||||
3. **Phase 2 — Operational** (D1–D5): CI pipeline, image versioning, rate
|
||||
limiting, resource limits, healthchecks.
|
||||
limiting, resource limits, healthchecks. **Done.**
|
||||
4. **Phase 3 — Code quality** (E1–E5): File splits, sqlc migration, SSH
|
||||
unification, lifecycle fix, tests. E1–E3 are large refactors — do one
|
||||
file/area per commit.
|
||||
|
||||
@@ -21,7 +21,7 @@ went sideways, open an investigation.
|
||||
| 2026-07-21 | [Frontend as OS + Apps — architecture audit & refactor](2026-07-21-frontend-os-apps-architecture.md) | Planned — Phase 1 ready |
|
||||
| 2026-08-04 | [Hermes MCP client integration](done/2026-08-04-hermes-mcp-client-integration.md) | Done — deployed |
|
||||
| 2026-08-05 | [Agent execution safety: QEMU guest agent gate + host-mutation guard](done/2026-08-05-agent-execution-safety-qemu-guest-agent-gate.md) | Done — implemented (1b9c761) |
|
||||
| 2026-08-05 | [Backend evaluation: architecture, security, and reliability improvements](2026-08-05-backend-evaluation-improvements.md) | Planned — 6 phases, security items first |
|
||||
| 2026-08-05 | [Backend evaluation: architecture, security, and reliability improvements](2026-08-05-backend-evaluation-improvements.md) | In Progress — Phase 0 (B) + Phase 2 (D1–D5) done; Phase 1 (C) pending |
|
||||
|
||||
## Done
|
||||
|
||||
|
||||
Reference in New Issue
Block a user