#232: plugin allowlist with SHA-256 hash pinning #298

Merged
fable-5 merged 1 commits from issue-232-plugin-hash-pinning into main 2026-07-31 21:43:35 +02:00
Collaborator

Completes M31 — the last open VS-NfD milestone.

  • Hash recording: install computes SHA-256 over the delivered bundle ZIP and stores it (plugins.bundle_hash; migration adds the nullable column, pre-#232 installs show "unknown" until reinstalled).
  • Allowlist plugins.allowlist in instance_settings ({id, sha256}[]): empty (default) = pinning not enforced — existing instances behave exactly as before; non-empty = enforcement for every plugin. Installs of unlisted ids or deviating bundles are rejected with stable codes (plugin_not_pinned / plugin_hash_mismatch, 403, i18n de+en); an installed plugin outside the list or with a deviating hash does not load: absent from pond mount lists, frame/asset routes 404 like a missing plugin.
  • Audited: every rejection (install and load surface) records plugin.rejected — audit catalogue bumped to v1.5 with its row.
  • Re-pin friction: a version bump changes the bundle hash, so it requires an explicit re-pin in the admin UI (ADR 0025's intended friction) — pinned in e2e.
  • Admin UI: per plugin the observed hash, the pinning verdict (not enforced / pinned / unpinned / mismatch — text, not colour), the pinned hash when deviating, and pin / re-pin / unpin actions; hint that pinning the first plugin turns enforcement on for all.
  • Docs: plugin-architecture.md trust section (scope stated honestly: the pin answers "is this the reviewed bundle"; post-install disk tampering is platform integrity per ADR 0019); hardening guide row (+ catalog advisory triage for the #243 fence); residual risk R-03 resolved; plan checkbox ticked.

Tests: 6-case e2e pack (compatibility with empty allowlist, pinned load, unpinned install rejected + audited, tampered bundle rejected, pin drift blocks loading while the admin still sees the mismatch, version bump needs re-pin). Full api suite 101 files / 561 green against the migrated DB.

Issue #232 will be closed manually after green CI + merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AUtYMxwTCMHG9mVHnwbFg8

Completes M31 — the last open VS-NfD milestone. - **Hash recording**: install computes SHA-256 over the delivered bundle ZIP and stores it (`plugins.bundle_hash`; migration adds the nullable column, pre-#232 installs show "unknown" until reinstalled). - **Allowlist** `plugins.allowlist` in `instance_settings` (`{id, sha256}[]`): **empty (default) = pinning not enforced** — existing instances behave exactly as before; non-empty = enforcement for every plugin. Installs of unlisted ids or deviating bundles are rejected with stable codes (`plugin_not_pinned` / `plugin_hash_mismatch`, 403, i18n de+en); an installed plugin outside the list or with a deviating hash does not load: absent from pond mount lists, frame/asset routes 404 like a missing plugin. - **Audited**: every rejection (install and load surface) records `plugin.rejected` — audit catalogue bumped to v1.5 with its row. - **Re-pin friction**: a version bump changes the bundle hash, so it requires an explicit re-pin in the admin UI (ADR 0025's intended friction) — pinned in e2e. - **Admin UI**: per plugin the observed hash, the pinning verdict (not enforced / pinned / unpinned / mismatch — text, not colour), the pinned hash when deviating, and pin / re-pin / unpin actions; hint that pinning the first plugin turns enforcement on for all. - **Docs**: `plugin-architecture.md` trust section (scope stated honestly: the pin answers "is this the reviewed bundle"; post-install disk tampering is platform integrity per ADR 0019); hardening guide row (+ catalog advisory triage for the #243 fence); residual risk **R-03 resolved**; plan checkbox ticked. Tests: 6-case e2e pack (compatibility with empty allowlist, pinned load, unpinned install rejected + audited, tampered bundle rejected, pin drift blocks loading while the admin still sees the mismatch, version bump needs re-pin). Full api suite 101 files / 561 green against the migrated DB. Issue #232 will be closed manually after green CI + merge. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01AUtYMxwTCMHG9mVHnwbFg8
fable-5 added 1 commit 2026-07-31 21:26:56 +02:00
#232: plugin allowlist with SHA-256 hash pinning
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 6m21s
CI / Build container images (pull_request) Successful in 3m59s
CI / Auth e2e pack (pull_request) Successful in 8m35s
CI / Import/export fidelity gate (pull_request) Successful in 1m1s
CD / Build and push images (push) Successful in 17s
CD / Deploy to Test (push) Successful in 14s
CD / Smoke tests against Test (push) Successful in 1m17s
CD / Promote to Int (push) Successful in 12s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 8m27s
CI / Import/export fidelity gate (push) Successful in 58s
CI / Lint, typecheck, test (push) Successful in 6m30s
9b7acab294
The install path records the SHA-256 of the delivered bundle ZIP
(plugins.bundle_hash; pre-#232 installs show it as unknown until
reinstalled). plugins.allowlist in instance_settings names permitted
ids with their pinned hashes: empty (default) = not enforced, existing
instances unchanged; non-empty = installs of unlisted or deviating
bundles are rejected (plugin_not_pinned / plugin_hash_mismatch, 403),
and an installed plugin outside the list or with a deviating hash does
not load — absent from pond mount lists, frame/assets 404. Every
rejection is audited (plugin.rejected, catalogue v1.5). A version bump
changes the hash and therefore requires an explicit re-pin — the
intended friction (ADR 0025). Admin UI shows observed vs pinned hash
per plugin with pin/re-pin/unpin. Scope stated honestly in
plugin-architecture.md: the pin answers "is this the reviewed bundle";
post-install disk tampering is platform integrity (ADR 0019), sandbox
containment stays the sandbox's job. Hardening guide row + catalog
advisory triage; residual risk R-03 resolved. e2e: empty-allowlist
compatibility, pinned load, unpinned and tampered installs rejected and
audited, pin drift blocks loading while the admin still sees the
mismatch, version bump needs re-pin. Full api suite 101 files / 561
green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUtYMxwTCMHG9mVHnwbFg8
fable-5 merged commit 9b7acab294 into main 2026-07-31 21:43:35 +02:00
Sign in to join this conversation.
No description provided.