[codex] Harden plugin runtime invocation scope (#6547)

## Thinking Path

> - Paperclip orchestrates AI-agent companies through a company-scoped
control plane.
> - Plugins extend that control plane, but plugin workers still call
back into host APIs.
> - Those worker-to-host calls need the same company boundary guarantees
as normal API routes.
> - Plugin action handlers also need authenticated actor context from
the host instead of trusting caller-supplied params.
> - This pull request hardens plugin bridge/action scope and keeps
plugin operation issues out of normal issue surfaces.
> - The benefit is safer plugin execution with clearer authorization
boundaries and better test coverage.

## What Changed

- Added host-owned invocation context plumbing for nested plugin worker
calls.
- Added actor context to plugin `performAction` calls and test harness
helpers.
- Enforced company invocation scope on worker-to-host calls and filtered
company lists to the active invocation scope.
- Extended plugin action route tests for board and agent actor context,
spoofed company params, and cross-company rejection.
- Extended plugin worker manager coverage for invocation-scope
propagation.
- Filtered typed and legacy plugin operation issue origins from default
issue/inbox lists.

## Verification

- `pnpm --filter @paperclipai/plugin-sdk build`
- `NODE_ENV=test pnpm exec vitest run
packages/plugins/sdk/tests/host-client-factory.test.ts
packages/plugins/sdk/tests/testing-actions.test.ts
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/plugin-worker-manager.test.ts
server/src/__tests__/issues-service.test.ts`

Note: embedded Postgres issue-service tests reported host-level Postgres
init skip for 47 tests; the non-embedded targeted tests passed.

## Risks

- Medium: plugin host authorization paths are sensitive, and external
plugins may rely on previously loose company params.
- Mitigation: the change only tightens calls when the host attached a
company invocation scope and includes explicit tests for board, agent,
and nested worker calls.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI GPT-5 Codex via `codex_local`, tool-enabled coding session;
exact context window not exposed by this runtime.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
This commit is contained in:
Dotta 2026-05-22 09:16:24 -05:00 committed by GitHub
parent 38c185fb8b
commit a1835cfa5e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 749 additions and 30 deletions

View file

@ -233,6 +233,8 @@ export const PLUGIN_RPC_ERROR_CODES = {
TIMEOUT: -32003,
/** The worker does not implement the requested optional method. */
METHOD_NOT_IMPLEMENTED: -32004,
/** The worker→host call attempted to escape the current invocation company scope. */
INVOCATION_SCOPE_DENIED: -32005,
/** A catch-all for errors that do not fit other categories. */
UNKNOWN: -32099,
} as const;
@ -368,6 +370,28 @@ export interface GetDataParams {
*
* @see PLUGIN_SPEC.md §13.9 `performAction`
*/
export type PluginPerformActionActorType = "user" | "agent" | "system";
export interface PluginPerformActionActorContext {
/** Authenticated principal type resolved by the Paperclip host. */
type: PluginPerformActionActorType;
/** Authenticated board user id when `type === "user"`, otherwise null. */
userId: string | null;
/** Authenticated agent id when `type === "agent"`, otherwise null. */
agentId: string | null;
/** Authenticated heartbeat/run id when available. */
runId: string | null;
/** Company id authorized by the host bridge for this action, when applicable. */
companyId: string | null;
}
export interface PluginPerformActionContext {
/** Immutable authenticated actor context supplied by the host. */
actor: Readonly<PluginPerformActionActorContext>;
/** Convenience alias for `actor.companyId`. */
companyId: string | null;
}
export interface PerformActionParams {
/** Plugin-defined action key (e.g. `"resync"`). */
key: string;
@ -375,6 +399,8 @@ export interface PerformActionParams {
companyId?: string | null;
/** Action parameters from the UI. */
params: Record<string, unknown>;
/** Authenticated actor context resolved by the host, never by caller params. */
actorContext?: PluginPerformActionActorContext | null;
/** Optional launcher/container metadata from the host render environment. */
renderEnvironment?: PluginLauncherRenderContextSnapshot | null;
}