feat(plugin): trust-on-first-use manifest pinning

Plugins are now verified against ~/.config/gnoma/plugins.pins.toml at
load time. Each plugin's plugin.json bytes are hashed (SHA-256) and:

- recorded automatically on first load (TOFU) with a prominent warning
- compared on subsequent loads
- refused with a clear error if the hash drifted, without overwriting
  the pin so the user can review and re-enrol deliberately

Pin-store I/O failures degrade to load-without-pinning rather than
locking the user out of previously-trusted plugins.

Closes audit finding C2. See ADR-003 for the decision rationale and
docs/plugins-trust.md for the end-user trust model.
This commit is contained in:
2026-05-19 16:44:09 +02:00
parent c44db99b41
commit dc438ea181
9 changed files with 546 additions and 12 deletions
+7 -1
View File
@@ -534,7 +534,13 @@ func main() {
logger.Warn("plugin discovery error", "error", err)
}
enabledSet := resolveEnabledPlugins(cfg.Plugins, discoveredPlugins)
pluginResult, err := pluginLoader.Load(discoveredPlugins, enabledSet)
pinStorePath := filepath.Join(gnomacfg.GlobalConfigDir(), "plugins.pins.toml")
pinStore, pinErr := plugin.NewFilePinStore(pinStorePath)
if pinErr != nil {
logger.Warn("plugin pin store unavailable; plugins will load without trust pinning", "error", pinErr)
pinStore = nil
}
pluginResult, err := pluginLoader.Load(discoveredPlugins, enabledSet, pinStore)
if err != nil {
logger.Warn("plugin load error", "error", err)
}