diff --git a/TODO.md b/TODO.md index 21f6599..887609c 100644 --- a/TODO.md +++ b/TODO.md @@ -2,6 +2,13 @@ Active plans, newest first: +- **[`docs/superpowers/plans/2026-05-19-security-wave1-safeprovider.md`](docs/superpowers/plans/2026-05-19-security-wave1-safeprovider.md)** + — post-audit hardening, Wave 1. Closes the four firewall-bypass + call sites (SLM classifier, summarizer, prompt hook, routerStreamer) + by introducing `security.SafeProvider` at the provider boundary. + **In progress on `feat/security-wave1-safeprovider`** — implementation + complete; ADR and merge pending. Waves 2 (incognito coherence) and + 3 (scanner + path hygiene) are scoped but not yet drafted. - **[`docs/superpowers/plans/2026-05-19-post-slm-unlock.md`](docs/superpowers/plans/2026-05-19-post-slm-unlock.md)** — outstanding work after the SLM unlock session. Phases A (two-stage tool routing), B (CLI agent binary override), C (user profiles), and diff --git a/cmd/gnoma/main.go b/cmd/gnoma/main.go index 4412f4e..f477652 100644 --- a/cmd/gnoma/main.go +++ b/cmd/gnoma/main.go @@ -803,7 +803,10 @@ func main() { // Create engine eng, err := engine.New(engine.Config{ - Provider: prov, + // Wrap even though the engine's own buildRequest scans inline — + // belt-and-suspenders so a future engine path that bypasses + // buildRequest still routes through the firewall. + Provider: security.WrapProvider(prov, fwRef), Router: rtr, Classifier: engineClassifier, Tools: reg, diff --git a/internal/engine/engine.go b/internal/engine/engine.go index eca316c..ed7bef3 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -265,6 +265,13 @@ func (e *Engine) Usage() message.Usage { } // SetProvider swaps the active provider (for dynamic switching). +// +// Callers must pass a provider that has already been wrapped with +// security.WrapProvider — the engine's buildRequest scans inline today, +// but the boundary contract is "every Stream call routes through a +// SafeProvider." Passing a raw provider here would silently open a +// firewall bypass for any engine path that calls Provider.Stream +// without going through buildRequest. func (e *Engine) SetProvider(p provider.Provider) { e.mu.Lock() e.cfg.Provider = p