Skip to content

Plugin System

An Infinia plugin is a self-contained .fyp package that adds both UI and backend capability without touching the host process. The defining design rule is isolation: a plugin's code never runs inside the host Spring context, and its UI never shares the host's DOM tree.

The .fyp package

A .fyp file is a zip archive with three parts:

PathContents
manifest.jsonPlugin metadata, permissions, and the AI tools it exposes
ui/The micro-frontend assets, served by the host under /plugin-runtime/{id}/**
backend/worker.jarThe worker executable, spawned as its own process

For the manifest field reference, see Manifest.

Out-of-process workers

The worker is the plugin's backend. It speaks JSON-RPC 2.0 over stdio and is spawned as a separate operating-system process — it never lives in the host Spring context. The host invokes it through POST /api/plugin-runtime/{id}/invoke with a {method, params} body; the host's PluginProcessManager:

  • spawns and owns the worker process for the plugin's lifetime,
  • dispatches each JSON-RPC call to that process,
  • resolves any ref_* FileRefs in the parameters to absolute filesystem paths before dispatch, so the worker receives real paths it can open directly.

Because workers are out-of-process, a worker crash or hang cannot take down the host, and a worker cannot reach into host beans or the JPA session.

Process isolation backends

Workers and AI-authored commands are wrapped by ProcessSandbox, which selects a native isolator per platform. The GET /api/security/process-isolation endpoint reports the active backend and surfaces compatibilityMode: true when no isolator is available.

PlatformBackendProcess-tree terminationFilesystem isolationNetwork isolation
PlatformBackendProcess-tree terminationFilesystem isolationNetwork isolation
---------------
Linuxbubblewrap (bwrap --die-with-parent --new-session)kernelfull — minimal read-only view (OS/runtime trees + JDK + the plugin's own package + its classpath roots — not $HOME); writes confined to plugin-owned rootsisolated unless declared
macOSsandbox-exec (Seatbelt profile)tree-killreduced / advisory — deny-sensitive (allow-default, then deny reads of host credentials ~/.ssh ~/.aws ~/.config/gcloud ~/.gnupg etc. and the whole FengYu runtime root; writes confined to plugin-owned roots + /tmp). A plugin can still read non-allowlisted user files, so this is NOT reported as full isolation.isolated unless declared
Windowswindows-job (Win32 Job Object, KILL_ON_JOB_CLOSE)reliable — the Job kills the whole tree on handle close or TerminateJobObjectnot enforced (known gap)not enforced (known gap)
Other / no isolatornonehost shutdown hook + tree-kill backstopnone — explicit approval onlynone — explicit approval only

Honest reporting. Only Linux reports sandboxed: true (full isolation). macOS reports sandboxed: false, reduced: true — its profile is deny-sensitive rather than a strict deny-default (a JVM cannot launch under deny-default on macOS; it reads/writes under ~/Library for caches/preferences), so a plugin can still read non-allowlisted user files. The host does NOT claim macOS is fully sandboxed; the chat approval gate treats it as reduced. Linux bwrap uses a minimal read-only view that excludes the user home, so ~/.fengyu and user secrets are invisible to the plugin there. A true minimal allowlist on macOS (deny-default + an explicit JDK/runtime read set) is a tracked follow-up that would flip macOS to full isolation.

On Windows the Job Object backend is a process-layer isolation only: it guarantees the worker and any descendants (e.g. a pip subprocess) terminate reliably when the host closes the job handle, which removes the long-standing gap where ProcessHandle.descendants() could miss orphans on Windows. It does not confine filesystem writes or block network the way bwrap and sandbox-exec do on Linux and macOS — that gap is intentional and documented; the explicit-approval gate still guards every effect on Windows.

Because a Job Object is not a security sandbox, the host reports Windows honestly as compatibilityMode: true / sandboxed: false (with lifecycleIsolation: job-object) and the chat approval gate treats Windows like any other no-security-sandbox platform — it does not relax approval just because a backend is active. The unsandboxedPlugins Settings toggle is therefore shown on Windows (it is in compatibility mode); ON = workers run bare with app-equivalent privileges, OFF = Job-Object lifecycle isolation only (no filesystem/network boundary). Real OS filesystem/network isolation on Windows (AppContainer / restricted token + ACL + network capability) is a tracked follow-up.

Sandboxed UI

The plugin's ui/ micro-frontend is served by the host as static assets under a strict Content Security Policy at /plugin-runtime/{id}/** — these asset paths are the only plugin URLs that bypass the token filter, so the UI can bootstrap without a credential. The micro-frontend host loads the entry page in a sandboxed iframe; no plugin code is imported into the host JavaScript realm.

Inside the iframe, the SDK @infinia/plugin-sdk provides a FengYuClient that bridges to the host over postMessage. The plugin uses this client to call its own worker (which the host forwards as JSON-RPC) and to request file access — it never talks to the OS directly.

Installed plugin descriptor

The host exposes installed plugins through InstalledPluginDescriptor. Its fields are:

FieldNotes
idUnique plugin id
nameDisplay name
descriptionShort description
categoryCategory id (see the category tree)
iconIcon identifier
versionPlugin version string
uiEntryResolved UI entry URL
authorAuthor string
permissionsDeclared permissions (e.g. files.read, files.write)
enabledWhether the plugin is currently enabled
iconStyleHardcoded "BLUE"
supportsAitrue when aiTools is non-empty
sourceOFFICIAL or THIRD_PARTY

GET /api/plugin-runtime returns the array of enabled descriptors; the SPA's plugins store consumes them.

How the pieces connect

┌─────────────────────────┐        postMessage         ┌──────────────────────────┐
│  Plugin UI micro-frontend│  ◄──────────────────────►  │  Host SPA (loader.ts)    │
│  (sandboxed iframe,      │       FengYuClient          │  mounts via MF host       │
│   @infinia/plugin-sdk)    │                             └──────────┬───────────────┘
└──────────────────────────┘                                        │ HTTP (token-gated)

                                                    ┌──────────────────────────────┐
                                                    │  Host Spring backend         │
                                                    │  PluginProcessManager        │
                                                    └──────────┬───────────────────┘
                                                               │ JSON-RPC 2.0 (stdio)

                                                    ┌──────────────────────────────┐
                                                    │  Worker process              │
                                                    │  backend/worker.jar          │
                                                    └──────────────────────────────┘

Next steps

Released under the GPL-3.0 License.