8dcca64e41
Introduces internal/security/SafeProvider — a provider.Provider decorator that scans outgoing messages and the system prompt through the firewall before delegating to the inner provider. Tool-result redaction stays in the engine because it needs per-tool context the boundary lacks. FirewallRef provides a late-binding atomic.Pointer[Firewall] so the wrapper can be installed before NewFirewall runs in main. A nil or unset ref makes SafeProvider a pass-through — preserves the current init order without lock contention or panics. Wave 1 of the post-audit hardening plan (docs/superpowers/plans/2026-05-19-security-wave1-safeprovider.md). Closes the architectural critique that secret scanning only ran inside engine.buildRequest(), leaving SLM/summarizer/hook/routerStreamer paths to send raw payloads. This commit only ships the wrapper; W1-2 and W1-3 will wire it through main and the four bypass sites.
29 lines
920 B
Go
29 lines
920 B
Go
package security
|
|
|
|
import "sync/atomic"
|
|
|
|
// FirewallRef is a late-binding holder for *Firewall.
|
|
//
|
|
// Construction order in gnoma builds provider arms before the firewall
|
|
// exists. SafeProvider takes a *FirewallRef at construction time, then
|
|
// resolves the current *Firewall on each call. This lets the wiring be
|
|
// installed before NewFirewall runs without any locking on the hot path.
|
|
//
|
|
// A nil *FirewallRef or a *FirewallRef whose pointer has not been Set
|
|
// is interpreted by SafeProvider as "no firewall installed yet" —
|
|
// requests pass through unmodified.
|
|
type FirewallRef struct {
|
|
p atomic.Pointer[Firewall]
|
|
}
|
|
|
|
// Set installs fw as the active firewall. Safe for concurrent use.
|
|
func (r *FirewallRef) Set(fw *Firewall) {
|
|
r.p.Store(fw)
|
|
}
|
|
|
|
// Get returns the currently installed firewall, or nil if none has been
|
|
// Set. Safe for concurrent use.
|
|
func (r *FirewallRef) Get() *Firewall {
|
|
return r.p.Load()
|
|
}
|