feat(plugins): Add universal plugin support#113
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
f70f2bf to
925d589
Compare
92df14a to
7b2c0fe
Compare
7b2c0fe to
a9a6f7b
Compare
a9a6f7b to
73edb08
Compare
73edb08 to
73884c2
Compare
Skip malformed marketplace files during plugin discovery so conventional fallback paths can still resolve valid plugins. When removing plugins, use the removed config block as ownership evidence in addition to lockfile entries. This lets remove clean up installed plugin bundles and projections even if agents.lock is missing that plugin row. Co-Authored-By: Codex <noreply@openai.com>
Remove the broad runtime-auth QA reference and keep runtime guidance focused on plugin verification. Collapse repeated unmanaged plugin manifest tests into one table-driven case while preserving per-runtime coverage. Co-Authored-By: Codex <noreply@openai.com>
Generate plugin marketplace sources relative to each marketplace file directory so projected marketplace entries resolve when consumed directly. Co-Authored-By: OpenAI Codex <noreply@openai.com>
Allow marketplace-local plugin selectors to resolve relative to nested marketplace files while still enforcing source-root containment. Update generated marketplace expectations and plugin specs to match the file-relative path contract. Co-Authored-By: OpenAI Codex <noreply@openai.com>
Only local marketplace selectors need file-relative traversal. Keep unsupported extension source paths on the stricter component path schema and clarify the discovery docs. Co-Authored-By: OpenAI Codex <noreply@openai.com>
Allow non-local marketplace source objects without local paths so unsupported entries do not invalidate the entire marketplace file. Keep local source objects strict and add regression coverage for mixed marketplace files. Co-Authored-By: OpenAI Codex <noreply@openai.com>
Exercise the unsupported-source skip path with a matching plugin name before the valid local marketplace entry. Co-Authored-By: OpenAI Codex <noreply@openai.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 3d25295. Configure here.
Aggregate project-scope plugin doctor diagnostics so same-project declarations do not hide missing external plugin installs. Co-Authored-By: OpenAI Codex <noreply@openai.com>
Keep the existing plugin backup when replacing an installed plugin fails and rollback cannot restore it. This avoids deleting the only recoverable copy after a partial install failure. Also convert missing plugin source roots into a controlled error that preserves the original filesystem cause. Co-Authored-By: OpenAI Codex <noreply@openai.com>
Assert plugin source-root realpath failures use the controlled error path and preserve the original filesystem cause. Co-Authored-By: OpenAI Codex <noreply@openai.com>
| written += await writeComponentProjections("opencode", agentIds, selected, projectRoot, warnings); | ||
| written += await writeComponentProjections("pi", agentIds, selected, projectRoot, warnings); |
There was a problem hiding this comment.
Plugin bundle symlinks escape containment and expose arbitrary host files to AI agents
A third-party plugin (e.g. a git-sourced bundle) can include a symlink such as agents/evil.md -> ../../../../../etc/passwd or -> /proc/self/environ; installPluginBundle copies it verbatim (cp defaults to dereference:false) and writeComponentProjections links .opencode/agents/evil.md to it without a realpath boundary check, so agents reading the projected component follow the chain out of the plugin root to arbitrary host files. Dereference/realpath containment should be enforced on plugin component files during install and projection.
Evidence
copyDir(packages/dotagents-lib/src/utils/fs.ts) callscp(src, dest, { recursive: true, filter })with nodereference, and grep showsdereferenceis never set anywhere, so symlinks inside a plugin bundle are copied with their original (possibly out-of-bundle) targets byinstallPluginBundleinstore.ts.markdownComponentLinksacceptsentry.isSymbolicLink()entries and builds aComponentLink{ sourcePath: join(agentsDir, entry.name) }with no realpath check against the plugin root;skillComponentLinkslikewise allows symlink dir entries needing only a resolvableSKILL.md.writeManagedComponentLinkcreates.opencode/agents/<name>as a symlink tosourcePath, inheriting the escaping chain to the out-of-bundle target.assertInsideSourceRoot(realpath-based) is applied to the plugin directory during discovery, not to individual symlinked files underagents//skills/, so relative/absolute traversal symlinks in the bundle are never contained.- Boundary: an untrusted plugin author should be confined to the plugin bundle; the chain lets projected components read arbitrary developer files (SSH keys, env, /etc).
Also found at 1 additional location
packages/dotagents/src/plugins/store.ts:171
Identified by Warden security-review · EPX-SL3

This adds first-class plugin support using
.agents/pluginsas the canonical local layout while treating the Codex plugin manifest and marketplace shape as the generalized input model. Plugin declarations now flow through config loading, lockfiles, install, sync, list, doctor, gitignore handling, and specs/docs.The implementation separates the host runtime concerns so the plugin work is easier to review and maintain:
targets/owns agent definitions plus MCP/hook config writers,subagents/owns subagent source/runtime handling, andplugins/owns plugin schema, storage, target selection, and runtime projection. The oldagents/index.tsremains as a compatibility barrel, but the implementation no longer concentrates plugins, subagents, target definitions, and config writers in one overloaded module tree.Plugin runtime output stays intentionally narrow per target. Claude and Cursor get generated native manifests inside each canonical plugin bundle plus runtime marketplaces whose local sources resolve relative to their marketplace files (
../.agents/plugins/<name>). Codex gets the repo-scoped.agents/plugins/marketplace.jsonwith file-relative{ "source": "local", "path": "./<name>" }entries plus a generated.codex-plugin/plugin.json; Grok gets a managed bundle copy. OpenCode does not get generated JavaScript or TypeScript plugin modules from dotagents bundles; supported bundle skills are symlinked into.opencode/skills/and Markdown agents into.opencode/agents/. Pi support is limited to the surface it can consume today: plugin skills are symlinked into.agents/skills/.The review and QA passes tightened the edge cases around that model: unsupported marketplace extension sources are skipped instead of crashing discovery, marketplace-local selectors are resolved from the marketplace file directory while still enforcing source-root containment, frozen wildcard lockfile skill names are validated before resolution/copy, plugin discovery rejects symlinks that resolve outside the source root, runtime component paths are checked before manifest/projection generation, OpenCode and Pi component projections avoid unmanaged collisions and repair dangling managed links,
.agents/.gitignoreignores projected Pi skill symlinks without hiding user-authored colliding skills or same-project plugin bundles, install preserves resolved lock entries before canonical subagent file writes, failed plugin replacement keeps the backup if rollback cannot restore it, missing plugin source roots now report controlled errors with the filesystem cause attached, and Claude plugin manifests no longer project the invalidagentsfield.Validation includes focused plugin runtime/store/schema regressions covering unsafe component paths, marketplace-file-relative local selectors, Pi skill-name traversal, canonical/explicit/marketplace plugin symlink escapes, failed plugin replacement backup preservation, and missing source-root controlled error reporting; affected install/sync/remove/doctor/gitignore tests including the same-project plugin gitignore and concurrent doctor error cases;
pnpm qa:example; Docker-backed OpenCode QA provingopencode debug skillandopencode agent listcan see projected plugin components;pnpm --filter @sentry/dotagents test src/plugins src/cli/commands/doctor.test.ts --testTimeout 90000;pnpm --filter @sentry/dotagents lint; andpnpm --filter @sentry/dotagents typecheck. Garfield passed after the latest checkpoints; Garfield Codify did not recommend new tooling beyond the regressions already added.