feat(security): wrap engine.Config.Provider + SetProvider doc (W1 follow-up)
Advisor flagged that engine.Config.Provider stayed raw, so the safety
property was 'every call goes through buildRequest' instead of the
stronger 'every Stream call routes through a SafeProvider.' Wrap it
even though buildRequest still scans inline — at worst this costs one
extra idempotent scan pass; it removes the 'someone adds a fifth engine
Stream site that skips buildRequest' failure mode.
Engine.SetProvider gets a doc comment establishing the wrap contract
for callers. No active callers today, but documenting it now prevents
the future bypass.
Confirmed elf engines inherit the wrap automatically:
- elf.Manager.Spawn passes arm.Provider (already *SafeProvider after
W1-3a)
- elf.Manager.SpawnWithProvider has no callers — dead code path
Added the Wave 1 plan to TODO.md under active plans.
This commit is contained in:
@@ -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
|
||||
|
||||
+4
-1
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user