Skip to content

fix(plugins): scope capability calls to activated sessions and bound net.fetch memory#594

Merged
rmyndharis merged 1 commit into
mainfrom
fix/plugin-capability-session-scope
Jul 2, 2026
Merged

fix(plugins): scope capability calls to activated sessions and bound net.fetch memory#594
rmyndharis merged 1 commit into
mainfrom
fix/plugin-capability-session-scope

Conversation

@rmyndharis

Copy link
Copy Markdown
Owner

Summary

Two plugin-runtime hardening fixes: tenant scoping of capability calls, and a memory bound on plugin fetches.

Changes

  • Per-session activation is now enforced on capability calls. Every capability verb (messages.sendText/reply, engine reads, conversation.send, handover.set, mappings.upsert/get/getByProvider) gated only on the static manifest.sessions (default ['*']). A general adapter necessarily ships ['*'] and is meant to be scoped by operator activation, so a plugin activated for one session could still send/read/flip handover on any other. assertSessionActive now ANDs the operator-set activeSessions (the same gate hook dispatch uses) with the manifest check. Defaults (activeSessions ?? ['*'], sessionScoped:false) preserve every single-tenant and global-built-in flow — it only ever tightens.
  • Global concurrency cap on net.fetch. Each plugin fetch buffers up to the body cap host-side (outside the worker heap cap), so many concurrent fetches across plugins/workers could OOM the host. A global reject-when-full semaphore bounds total buffering to MAX_INFLIGHT_FETCHES × body cap, mirroring the existing sandbox in-flight-cap pattern.

Notes

Residual (separate follow-up): the activation gate is per-plugin, not per-instance — a full-admin provisioning the same plugin for multiple tenants leaves it active for all of them; true per-instance isolation would bind the capability call's sessionId to the dispatching instance.

Verification

npm run build ✓ · npm test ✓ (1863/1863, +6) · lint ✓. New tests: capability calls denied when deactivated for the session (messages + engine), allowed when activated, allowed when never restricted, global plugin unaffected; net.fetch rejects at the concurrency cap and recovers after slots free.

…net.fetch memory

- Enforce per-session activation on every plugin capability call. assertSessionActive now checks the
  operator-set activeSessions in addition to the static manifest.sessions, across all capability verbs
  (messages.sendText/reply, engine reads, conversation.send, handover.set, mappings.upsert/get/
  getByProvider). Previously the capability path gated only on manifest.sessions (default ['*']), so a
  plugin activated for one session could send/read/flip handover on any other. Defaults
  (activeSessions ?? ['*'], sessionScoped:false) preserve every single-tenant and global-built-in flow.
- Bound concurrent plugin net.fetch buffering with a global reject-when-full semaphore, so total
  host-side response buffering stays at most MAX_INFLIGHT_FETCHES × the body cap regardless of plugin or
  worker count (previously each of many concurrent fetches could buffer the full cap and OOM the host).
@rmyndharis rmyndharis merged commit a943694 into main Jul 2, 2026
2 of 3 checks passed
@rmyndharis rmyndharis deleted the fix/plugin-capability-session-scope branch July 2, 2026 13:47
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