OfficeCLI Plugin Discovery Mechanism: 4-Step Resolution for External Extensions

OfficeCLI resolves external plugins through a deterministic four-step hierarchy—environment variables, user directories, bundled directories, and PATH lookups—implemented in PluginRegistry.cs with strict security checks and manifest verification.

OfficeCLI extends its native document support through a flexible plugin architecture that enables external handling of additional formats like .docx, .xlsx, and .pptx. The plugin discovery mechanism, formally specified in plugins/plugin-protocol.md and implemented in src/officecli/Core/Plugins/PluginRegistry.cs, applies a fixed resolution order to locate executables while maintaining security through filesystem validation and performance through intelligent caching.

The Four-Step Discovery Hierarchy

The host binary locates a plugin for a given kind (e.g., dump-reader, exporter, format-handler) and file extension by applying a strict priority order defined in the plugin protocol. This algorithm ensures deterministic behavior while allowing multiple override strategies.

Step 1: Environment Variable Override

The highest priority location is a dedicated environment variable pointing directly to an executable. The variable name follows the pattern $OFFICECLI_PLUGIN_<KIND>_<EXT>, where the kind is converted to kebab-case and the extension (without the leading dot) is upper-cased.


# Explicitly specify a dump-reader plugin for .doc files

export OFFICECLI_PLUGIN_DUMP_READER_DOC=/opt/myplugins/officecli-doc
officecli view report.doc

This method bypasses all other discovery mechanisms and is useful for development or testing specific plugin versions.

Step 2: User Plugin Directory

If no environment variable is set, OfficeCLI checks the per-user well-known location at ~/.officecli/plugins/<kind>/<ext>/plugin(.exe). This directory structure supports cross-platform executable extensions automatically.


# Install a plugin for the current user

mkdir -p ~/.officecli/plugins/dump-reader/doc
cp mydumpreader-plugin ~/.officecli/plugins/dump-reader/doc/plugin

# Verify discovery

officecli plugins list | grep doc

Step 3: Bundled Plugin Directory

The third location searches alongside the main binary in <app-dir>/plugins/<kind>/<ext>/plugin(.exe). This enables application distributors to ship plugins as part of the core package.


# Assuming the main binary resides at /usr/local/bin/officecli

mkdir -p /usr/local/bin/plugins/exporter/pdf
cp mypdf-exporter /usr/local/bin/plugins/exporter/pdf/plugin

# The plugin is automatically discoverable

officecli view presentation.pptx pdf --out out.pdf

Step 4: PATH Lookup with Security Filtering

Finally, the system searches the PATH environment variable for executables named officecli-<kind>-<ext> or the shorthand officecli-<ext>. Critical security checks prevent execution from relative paths or world-writable directories.


# Install a binary named officecli-exporter-pdf in PATH

sudo cp officecli-pdf-export /usr/local/bin/officecli-exporter-pdf
officecli view file.docx pdf --out file.pdf

Core Implementation in PluginRegistry.cs

The discovery logic resides in src/officecli/Core/Plugins/PluginRegistry.cs, which orchestrates candidate generation, caching, and safety validation according to the protocol specification.

Candidate Generation via CandidatePaths()

The CandidatePaths(kind, ext) method (lines 90-108) yields the four locations in strict priority order. This method serves as the single source of truth for the discovery algorithm, ensuring consistent behavior across the application.

Process-Wide Caching Strategy

To avoid repeated filesystem probing, the registry maintains a process-wide dictionary _cache (lines 24-31) storing both positive and negative hits. When a new plugin is installed programmatically or externally, callers can invalidate the cache by invoking InvalidateCache() (lines 52-59), forcing a fresh discovery on the next request.

Extension Normalization Rules

Before comparison, extensions undergo normalization in lines 28-33 and 35-36: they are forced to lower-case, prefixed with a dot if absent, and compared case-insensitively. This ensures that .DOC and .doc resolve identically while maintaining internal consistency.

Security Checks and World-Writable Protection

The PathCandidates implementation (lines 71-78) applies strict security filters during PATH traversal. It skips relative directory entries and ignores any directory with the Unix "other-write" bit set, preventing privilege escalation through hijacked executables in world-writable locations.

Plugin Verification and Manifest Validation

Once a candidate executable is discovered, OfficeCLI verifies its compatibility before execution. The registry invokes <plugin> --info with a 5-second timeout to retrieve a JSON manifest describing the plugin's capabilities.

The manifest must declare a protocol version matching the host's supported version (currently 1), along with compatible kinds and extensions arrays. Only if these values align with the discovery request does the plugin become the resolved result. This verification prevents incompatible or malformed plugins from crashing the host process.

The manifest parsing logic utilizes PluginManifest.cs for deserialization, while PluginProcess.cs manages the plugin's lifecycle after successful discovery, including idle-timeout watchdogs and IPC handling.

Summary

  • Four-step hierarchy: OfficeCLI checks environment variables, user directories (~/.officecli/plugins/), bundled directories (<app-dir>/plugins/), and PATH entries in strict order as defined in plugins/plugin-protocol.md.
  • Security first: The discovery mechanism ignores world-writable directories and relative PATH entries to prevent executable hijacking.
  • Performance optimized: A process-wide cache in PluginRegistry.cs stores discovery results, with InvalidateCache() available for manual refresh.
  • Strict validation: Discovered plugins must respond to --info with a valid manifest matching protocol version 1 and declaring compatible kinds/extensions.
  • Normalization: Extensions are normalized to lower-case with dot prefixes before comparison, ensuring case-insensitive matching.

Frequently Asked Questions

How does OfficeCLI determine which plugin to use when multiple are installed?

OfficeCLI follows a strict priority order defined in CandidatePaths() within PluginRegistry.cs. It checks environment variables first, then user-specific directories, followed by bundled directories, and finally PATH entries. The first valid, verified executable wins, and subsequent locations are ignored unless the cache is invalidated.

What security measures prevent malicious plugins from executing?

The discovery mechanism implements multiple safeguards. During PATH lookup (step 4), PathCandidates skips relative directories and any directory with the Unix "other-write" bit set (lines 71-78). Additionally, every discovered plugin must pass manifest verification by responding to --info with a protocol version matching the host's expected version (currently 1), ensuring only compatible and explicitly designed plugins execute.

Can I force OfficeCLI to reload plugins without restarting the process?

Yes. The PluginRegistry class exposes InvalidateCache() (lines 52-59), which clears the internal _cache dictionary storing discovery results. Call this method after installing new plugins or modifying existing ones to force a fresh discovery on the next plugin request, eliminating the need to restart the OfficeCLI process.

Why does my plugin in PATH not get discovered?

First, ensure the executable follows the naming convention officecli-<kind>-<ext> or officecli-<ext>. Second, verify the containing directory is not world-writable and is not a relative path entry, as PluginRegistry.cs filters these for security (lines 71-78). Finally, confirm the plugin responds to --info with a valid JSON manifest declaring the correct protocol version and supported extensions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →