Skip to content

feat(plugins): enforce host-function permissions (WI-1821) - #421

Merged
stefan-ernst merged 1 commit into
mainfrom
wi-1821-plugin-permissions
Nov 2, 2026
Merged

stefan-ernst merged 1 commit into
mainfrom
wi-1821-plugin-permissions

Conversation

@stefan-ernst

Copy link
Copy Markdown
Contributor

Summary

Plugin host functions were registered for every plugin, so a plugin that declared a minimal capability set still received process execution, outbound HTTP/SMTP, KV, comment and SCM access (security finding F2, the capability half of H2).

Host-function access is now default-deny and driven by a new, closed permissions field in the plugin manifest. capabilities stays the open product-feature advertising list consumed by HasCapability/GetCapabilities and /api/features.

What changed

  • internal/plugins/capabilities.go — closed catalog and mapping: process.exec → cli_exec, network.egress → http_fetch, mail.send → smtp_send, storage.kv → kv_get/kv_set/kv_delete, comments.write → create_comment, scm.write → scm_create_branch/scm_create_item_link. log is always available. Unknown names fail the load.
  • Manager.requirePermission gates every host function through one helper using the calling plugin's effective permissions. An absent plugin context denies; each function returns its own typed denial (http_fetch → 403).
  • License precedence is permission and license: an unlicensed plugin keeps loading but gets no host-function permissions.
  • NewManager validates the registered host-function set against the catalog so a new host function cannot ship unguarded.
  • Load-time warning names the plugin and the host functions it can no longer call.
  • GET /admin/plugins reports requested_permissions vs effective_permissions; Module settings renders a badge per permission.

Breaking change

A plugin that declares no permissions now gets only log. Existing plugins must add the permissions they rely on to their manifest. The Pro plugin calls no host functions and needs no manifest change.

Out of scope

Per-plugin operator grant/deny overrides (WI-1821 item 5, optional) and create_comment author impersonation (the other half of H2).

Tests

Covered by the core-tests branch wi-1821-plugin-permissions:

  • mapping guard, normalizePermissions, deny/allow, unknown-name load failure, license gating, log-still-works
  • admin plugin-list requested vs effective permissions
  • browser spec plugin-permission-badge

Also verified: golangci-lint, gofmt, go build (with and without noplugins), full ./internal/handlers/..., svelte-check, locale guard, and the plugin-route-auth-badge / collection-map-swimlanes e2e specs. PostgreSQL was not run; the permission logic is engine-independent.

Plugin host functions were registered for every plugin, so a plugin that
declared a minimal capability set still received process execution, outbound
HTTP/SMTP, KV, comment and SCM access (security finding F2).

- New manifest field `permissions` with a closed catalog: process.exec ->
  cli_exec, network.egress -> http_fetch, mail.send -> smtp_send, storage.kv ->
  kv_get/kv_set/kv_delete, comments.write -> create_comment, scm.write ->
  scm_create_branch/scm_create_item_link. `log` stays always available.
- Unknown permission names fail the load; declared permissions are normalized
  and deduplicated.
- Every gated host function checks the calling plugin's effective permissions
  and returns a typed denial instead of executing. An absent plugin context
  denies.
- License precedence is permission AND license: an unlicensed plugin keeps
  loading but gets no host-function permissions.
- The admin plugin list reports requested vs effective permissions, and module
  settings renders a badge per permission.
- A load-time warning names the plugin and the host functions it can no longer
  call.
- NewManager validates the registered host function set against the catalog so a
  new host function cannot ship unguarded.
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown

Size Change: 0 B 🆕

Total Size: 0 B

compressed-size-action

@stefan-ernst
stefan-ernst merged commit aad2aac into main Nov 2, 2026
20 of 21 checks passed
@stefan-ernst
stefan-ernst deleted the wi-1821-plugin-permissions branch November 2, 2026 09:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant