How External Plugins Load in GeoLibre Desktop vs Web Build: Security Model Explained

External plugins in GeoLibre load through a unified core system that adapts to each platform—desktop supports ZIP archives and remote URLs via Tauri's filesystem API, while the web build supports only remote URLs with no-op filesystem operations; both enforce a manifest-based trust model where untrusted plugins are quarantined until explicitly approved by the user.

GeoLibre's plugin architecture distinguishes between built-in plugins shipped with the application and external plugins that users add at runtime. Whether you're running the Tauri-based desktop build or the browser-based web build, the core loading logic lives in apps/geolibre-desktop/src/hooks/usePlugins.ts. This article breaks down how external plugin loading differs by platform and how the security model protects users from untrusted code.

Built-In Plugin Loading Foundation

Before examining external plugins, understanding the built-in system provides necessary context.

Built-in plugins reside in the packages/plugins workspace and export from packages/plugins/src/index.ts. The Vite plugin bundled-plugins.ts scans apps/geolibre-desktop/public/plugins/ at build time, generating a virtual module:

import { bundledPluginManifestPaths } from "virtual:bundled-plugins";

The usePlugins.ts hook iterates these manifest URLs, fetches each, converts responses to Blob URLs, and dynamically imports them with import(/* webpackIgnore: true */ blobUrl). Each imported plugin registers with the central PluginManager via manager.registerAll([...]).

This Blob-based dynamic import pattern—critical for isolation—carries over to external plugins on both platforms.

Desktop Build: Full Filesystem and Remote Support

The Tauri-based desktop build offers the complete external plugin feature set. The relevant functions import into usePlugins.ts from apps/geolibre-desktop/src/lib/external-plugins.ts:

import {
  installWebPluginArchive,
  listInstalledWebPlugins,
  loadExternalPlugins,
  reloadExternalUrlPlugin,
  resolvePluginAssetUrlForLoadedPlugin,
  uninstallWebPlugin,
  unloadFilesystemPlugin,
  unloadRemovedUrlPlugins,
} from "../lib/external-plugins";

ZIP Archive Installation

The installWebPluginArchive function handles desktop-only ZIP installation:

  • Extracts plugin.json manifest from the archive
  • Validates manifest structure and required fields
  • Copies plugin files to the user's data directory via Tauri's filesystem API
  • Records the plugin as "installed" in local state

After installation, loadExternalPlugins() reads stored manifests and applies the trust partition.

Trust Partitioning Before Execution

The desktop build implements ../lib/plugin-trust to separate trusted from untrusted plugins before any code executes. The partitionProjectPluginManifestUrls function categorizes manifests based on:

  • Previously approved URLs stored in user preferences
  • New URLs requiring explicit user review

Only trusted plugins proceed to dynamic import. Untrusted plugins populate a quarantined list visible in the ProjectPluginTrustDialog.tsx UI. Users must explicitly grant trust—untrusted plugins never run.

// Desktop: Installing and loading a plugin from ZIP
await installWebPluginArchive(zipFilePath);
await loadExternalPlugins(); // trust check happens here

Web Build: Remote-Only with Restricted Filesystem

The pure web build uses the identical API surface but with filesystem functions as no-ops. Browser security policies prevent writes to local directories, so:

  • installWebPluginArchive → no-op
  • unloadFilesystemPlugin → no-op
  • File-based plugin persistence → unavailable

Remote URL loading remains fully functional. The loadExternalPlugins function fetches plugin archives over HTTPS, validates manifests, and imports via Blob URLs—exactly as on desktop. The trust enforcement layer operates identically: untrusted remote plugins remain disabled until user approval through the same ProjectPluginTrustDialog.tsx.

// Web/Desktop: Loading a remote plugin by URL
await reloadExternalUrlPlugin('https://example.com/my-plugin/manifest.json');
// User approves manifest URL in trust dialog
await loadExternalPlugins();

Security Model: Four-Layer Protection

GeoLibre implements a sandboxed plugin trust model with these defenses:

1. Manifest-Based Trust Anchoring

Every external plugin requires a plugin.json manifest. The manifest URL serves as the trust anchor—partitionProjectPluginManifestUrls tracks approval per-URL, not per-plugin-version, preventing substitution attacks.

2. User-Controlled Trust Decisions

No external plugin executes without explicit user approval. The ProjectPluginTrustDialog.tsx presents:

  • Manifest origin and requested permissions
  • Approve/Revoke actions with persistent storage

Untrusted plugins load in a disabled state with zero code execution.

3. Blob URL Isolation

Dynamic imports use import(/* webpackIgnore: true */ blobUrl) construction. This technique:

  • Isolates plugin code from the main application bundle
  • Enables runtime-controlled lifecycle (unload via URL revocation)
  • Prevents plugins from interfering with core module resolution

4. Defensive Error Handling

The reportPluginError mechanism in usePlugins.ts catches and routes failures to the diagnostics panel. Exceptions during plugin initialization or command execution cannot crash the host application.

Key Files and Responsibilities

File Location Purpose
usePlugins.ts apps/geolibre-desktop/src/hooks/ Central hook for plugin registration, external loading, and PluginManager access
bundled-plugins.ts apps/geolibre-desktop/vite-plugins/ Build-time scan of public/plugins/ generating virtual:bundled-plugins
external-plugins.ts apps/geolibre-desktop/src/lib/ Installation, loading, unloading, and trust handling for external plugins
plugin-trust.ts apps/geolibre-desktop/src/lib/ Trust partitioning and user decision tracking
ProjectPluginTrustDialog.tsx apps/geolibre-desktop/src/components/layout/ UI for approving/rejecting external plugin manifests

Platform Comparison Summary

Capability Desktop (Tauri) Web Build
ZIP archive installation ✓ Full support ✗ No-op (browser restriction)
Remote URL loading ✓ Full support ✓ Full support
Local filesystem persistence ✓ Tauri API ✗ Unavailable
Trust dialog enforcement ✓ Identical ✓ Identical
Blob URL isolation ✓ Identical ✓ Identical

Both builds converge on the same security posture: external code requires explicit user trust and executes in an isolated, revocable sandbox.

Summary

  • Desktop builds support full external plugin lifecycle—ZIP installation, filesystem persistence, and remote URLs—via Tauri's native APIs
  • Web builds support only remote URL loading with filesystem operations disabled, maintaining API compatibility through no-op stubs
  • Security model enforces manifest-based trust with user approval, Blob URL isolation, and defensive error handling across both platforms
  • Core implementation lives in usePlugins.ts with platform-specific behavior delegated to external-plugins.ts functions
  • Untrusted plugins are quarantined and never executed, with revocation possible at any time

Frequently Asked Questions

How does GeoLibre prevent malicious plugins from running automatically?

Plugins require a valid plugin.json manifest and explicit user trust approval through ProjectPluginTrustDialog.tsx. The partitionProjectPluginManifestUrls function in plugin-trust.ts separates untrusted manifests before any dynamic import occurs. Untrusted plugins remain in a disabled state with no code execution path.

Can web users install plugins from ZIP files like desktop users?

No. Browser security policies prevent local filesystem writes, so installWebPluginArchive and related filesystem functions are no-ops in the web build. Web users can only load plugins from remote HTTPS URLs that pass the same trust verification process.

What happens if a trusted plugin becomes untrusted or is revoked?

The PluginManager tracks loaded plugins by their Blob URLs. When trust is revoked, unloadRemovedUrlPlugins or direct URL revocation removes the plugin from the active set. The plugin's code is no longer reachable through its Blob URL, and subsequent loadExternalPlugins calls exclude it from registration.

Where is the plugin trust state persisted across sessions?

User trust decisions for manifest URLs are stored persistently and retrieved by partitionProjectPluginManifestUrls during each loadExternalPlugins invocation. The exact storage mechanism (Tauri store for desktop, browser storage for web) is abstracted by the trust layer, with the same approval UI presented regardless of platform.

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 →