Group C — kei-cortex daemon security hardening (post-audit 2026-05-02).
- fal_ssrf.rs (new): validate_fal_url whitelist (fal.ai/.media/.run only).
Applied to upload_url, file_url, status_url, images[0].url,
and download_image. Closes SSRF where compromised fal response
could direct daemon to fetch IMDSv1 (169.254.169.254) and
stream cloud creds.
- fal_pipeline.rs (new): HTTP step functions extracted from fal.rs; fal.rs trimmed
to thin orchestrator (101 LOC, was over 200 LOC limit).
- auth.rs: save_token now writes to <path>.<nanos>.tmp + sync_all + rename. Was
non-atomic OpenOptions truncate+write — crash mid-write produced empty
token file -> bootstrap rotated -> stale clients locked out.
- routes.rs + routes_auth.rs (new): explicit DefaultBodyLimit per route — chat 256 KiB,
tool/apply 11 MiB, pet/interaction 64 KiB, tts 32 KiB.
Bearer auth middleware extracted to routes_auth.
- handlers/chat.rs: validate_body enforces MAX_MESSAGE_CHARS = 50_000. Closed cost
amplification where 1.99 MiB chat message billed 500K tokens
($1.50/turn at Sonnet pricing) on every send.
- anthropic_sse.rs: SseParser MAX_BUF = 1 MiB cap; was unbounded — peer streaming
1 GB without \\n\\n would OOM daemon.
- http_helpers.rs (new): HTTP_CLIENT: Lazy<reqwest::Client> shared across handlers
(was per-request Client::new() => 100-300ms TLS handshake
per chat turn, no HTTP/2 multiplexing, fd leak risk on
macOS TIME_WAIT).
- http_helpers.rs::read_capped: per-response body cap (16 KiB error / 64 MiB success).
Applied to anthropic, anthropic_invoker, elevenlabs,
fal_pipeline. Closed unbounded resp.text() / .bytes()
pattern that compromised upstream could exploit.
Test results: 462 passed; 0 failed (single-threaded). cargo check clean.
2 pre-existing port-binding flakes in openai_loop_wiring tests are unrelated.
Findings consensus: fal SSRF + body-size + bearer-token-atomicity appeared in
Wave-A retest; chat message cap + SSE buf cap appeared in Wave-A only. Would have
been missed by single audit pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
67 lines
2.1 KiB
TOML
67 lines
2.1 KiB
TOML
[package]
|
|
name = "kei-cortex"
|
|
version = "0.1.0"
|
|
edition.workspace = true
|
|
rust-version.workspace = true
|
|
description = "Local HTTP daemon exposing cortex state for UI consumption"
|
|
authors = ["Denis Parfionovich <info@greendragon.info>"]
|
|
|
|
[[bin]]
|
|
name = "kei-cortex"
|
|
path = "src/main.rs"
|
|
|
|
[lib]
|
|
name = "kei_cortex"
|
|
path = "src/lib.rs"
|
|
|
|
[dependencies]
|
|
axum = { version = "0.7", features = ["multipart", "ws"] }
|
|
tokio = { workspace = true }
|
|
tokio-util = { version = "0.7", features = ["rt"] }
|
|
tower = { workspace = true }
|
|
tower-http = { version = "0.5", features = ["cors", "trace"] }
|
|
serde = { workspace = true }
|
|
serde_json = { workspace = true }
|
|
clap = { workspace = true }
|
|
thiserror = { workspace = true }
|
|
rusqlite = { workspace = true }
|
|
anyhow = { workspace = true }
|
|
rand = "0.8"
|
|
reqwest = { workspace = true }
|
|
tokio-stream = { workspace = true }
|
|
futures = { workspace = true }
|
|
uuid = { version = "1", features = ["v4"] }
|
|
async-stream = "0.3"
|
|
toml = { workspace = true }
|
|
bytes = { workspace = true }
|
|
tempfile = { workspace = true }
|
|
dashmap = { workspace = true }
|
|
walkdir = { workspace = true }
|
|
which = "6"
|
|
once_cell = "1"
|
|
regex = { workspace = true }
|
|
portable-pty = { workspace = true }
|
|
# Wave 44a — tool-sandbox hardening
|
|
shell-words = { workspace = true }
|
|
url = { workspace = true }
|
|
lru = { workspace = true }
|
|
# Wave 44b — symlink-safe writes
|
|
nix = { workspace = true }
|
|
# Wave 44d — calendar usage boundaries
|
|
chrono = { workspace = true }
|
|
kei-pet = { path = "../kei-pet" }
|
|
kei-router = { path = "../kei-router" }
|
|
kei-shared = { path = "../kei-shared" }
|
|
kei-ledger = { path = "../kei-ledger" }
|
|
# Wave 55 Stage 2 — universal model registry. `default_model()` in
|
|
# `anthropic.rs` consults this for the `kei-cortex-default` role before
|
|
# falling back to the literal pin.
|
|
kei-model = { path = "../kei-model" }
|
|
# Phase 2 — per-turn token telemetry. Every chat handler fires a
|
|
# fire-and-forget `Store::record_event` after Done so sleep-report has
|
|
# real data. Open lazily on AppState init; tracker IO failures must
|
|
# never break the chat call.
|
|
kei-token-tracker = { path = "../kei-token-tracker" }
|
|
|
|
[dev-dependencies]
|
|
reqwest = { workspace = true, features = ["blocking"] }
|