How External Plugins Access Runtime Environment Variables in GeoLibre: A Complete Guide to AI Assistant API Keys
External plugins in GeoLibre access runtime environment variables through a secure, allow-listed snapshot mechanism where the Rust backend reads OS environment variables at startup and exposes them via the @geolibre/plugins runtime API.
GeoLibre's plugin architecture enforces strict security boundaries between the desktop application and external code. Since webview contexts cannot access process.env directly, the framework implements a controlled pipeline for sensitive credentials like AI assistant API keys. This design ensures that only explicitly permitted variables reach plugin code while preventing environment leakage into browser builds.
Why Direct Environment Access Is Blocked
The GeoLibre desktop application runs a webview front-end hosted by a Tauri Rust backend. This architecture creates a fundamental security boundary: the JavaScript runtime inside the webview has no native access to the host operating system's environment variables. According to the source code in apps/geolibre-desktop/src/lib/assistant/os-env.ts, attempting to read process.env from plugin code would return an empty or mocked object, making a backend-mediated solution essential.
The OS Environment Loading Pipeline
GeoLibre resolves this limitation through a three-stage pipeline that loads, caches, and exposes environment variables.
Stage 1: Rust Backend Variable Extraction
The Tauri backend exposes a command called read_env_vars that accepts an array of allowed variable names and returns their values from the host OS. This command is invoked exclusively from loadOsEnvVars() in apps/geolibre-desktop/src/lib/assistant/os-env.ts:
// From apps/geolibre-desktop/src/lib/assistant/os-env.ts
export async function loadOsEnvVars(): Promise<RuntimeEnv> {
// Only invoke the Tauri backend once; subsequent calls reuse the same promise.
loadPromise ??= readOsEnvVars();
return loadPromise;
}
This singleton pattern prevents redundant IPC calls. The function caches the promise in loadPromise, ensuring all concurrent requests share the same asynchronous operation.
Stage 2: Allow-List Enforcement
Security is enforced through the OS_ENV_VAR_NAMES constant defined in assistant/provider.ts. This whitelist restricts readable variables to AI-provider credentials only:
| Variable | Purpose |
|---|---|
GEMINI_API_KEY |
Google Gemini primary key |
GOOGLE_API_KEY |
Google general AI key |
GOOGLE_GENAI_API_KEY |
Google GenAI-specific key |
OPENAI_API_KEY |
OpenAI API access |
OPENAI_COMPATIBLE_BASE_URL |
Custom endpoint URL |
OPENAI_COMPATIBLE_API_KEY |
Custom endpoint key |
OPENAI_COMPATIBLE_MODEL |
Custom endpoint model |
The Rust backend rejects any request for variables outside this list, preventing plugins from accessing system-sensitive values like PATH, HOME, or SSH_PRIVATE_KEY.
Stage 3: Browser-Safe Caching
Once retrieved, environment values are stored in a global cache at window.__GEOLIBRE_OS_ENV__ for synchronous access:
// Simplified from os-env.ts implementation
const cached = window.__GEOLIBRE_OS_ENV__;
if (cached) return cached;
// Otherwise load and cache
const env = await loadOsEnvVars();
window.__GEOLIBRE_OS_ENV__ = env;
return env;
If the application runs outside Tauri—such as in web builds or Jupyter embeddings—the function immediately resolves to an empty object {}, ensuring zero secret exposure in browser contexts.
Accessing Environment Variables from Plugin Code
External plugins retrieve runtime environment variables through the @geolibre/plugins package rather than any global object.
The getRuntimeEnv() Method
The plugin runtime API exposes a getRuntimeEnv() function that returns the merged RuntimeEnv object:
import { getRuntimeEnv } from '@geolibre/plugins';
export function init() {
const env = getRuntimeEnv(); // RuntimeEnv object
const apiKey = env.OPENAI_API_KEY; // string | undefined
// Plugin initialization logic
}
This approach provides several advantages:
- Consistency: Same interface across desktop and web builds (web returns empty object)
- Type safety:
RuntimeEnvis fully typed with optional fields for all allow-listed variables - Testability: Plugins can mock
getRuntimeEnv()without stubbing global objects
Complete Working Example
Here's production-ready plugin code for making authenticated AI requests:
import { getRuntimeEnv } from '@geolibre/plugins';
export async function runAIQuery(prompt: string) {
const {
OPENAI_API_KEY,
OPENAI_COMPATIBLE_BASE_URL
} = getRuntimeEnv();
if (!OPENAI_API_KEY) {
throw new Error(
'OpenAI API key not configured. Set OPENAI_API_KEY in your OS environment.'
);
}
const baseUrl = OPENAI_COMPATIBLE_BASE_URL ?? 'https://api.openai.com';
const response = await fetch(`${baseUrl}/v1/chat/completions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }]
})
});
if (!response.ok) {
throw new Error(`AI request failed: ${response.status} ${response.statusText}`);
}
return await response.json();
}
Runtime Variable Merging
The hook useRuntimeEnvironmentVariables in apps/geolibre-desktop/src/hooks/useRuntimeEnvironmentVariables.ts combines multiple variable sources into the final RuntimeEnv passed to plugins:
- OS environment snapshot from
readOsEnv()(desktop only) - Deployment-time variables from build configuration or remote config services
This merging allows administrators to override OS variables via deployment pipelines without modifying host machine environments.
Error Handling and Resilience
The pipeline includes graceful degradation for edge cases. From apps/geolibre-desktop/src/lib/assistant/os-env.ts lines 66-73:
// Error recovery: reset promise to allow retry on next call
loadPromise.catch((err) => {
console.error('Failed to load OS environment variables:', err);
loadPromise = undefined; // Clear cache so next attempt retries
return {}; // Return empty object, don't break app
});
If the IPC channel fails or the Rust backend returns an error, the promise is reset for subsequent retry attempts and the application continues with an empty environment object rather than crashing.
Security Architecture Summary
GeoLibre's environment variable access implements defense in depth:
- Whitelist enforcement: Rust backend only reads explicitly permitted variables
- IPC mediation: No direct OS access from JavaScript/webview
- Build-time safety: Browser builds receive empty objects by design
- Cache isolation: Values stored in closure-scoped promise, not persistent storage
- No logging: API keys are never written to application logs or crash reports
Summary
- External plugins access environment variables through
getRuntimeEnv()from@geolibre/plugins, not throughprocess.envor global objects. - Only allow-listed AI-provider variables are readable, enforced by the
OS_ENV_VAR_NAMESconstant inassistant/provider.tsand the Rustread_env_varscommand. - Variables are loaded once at startup by
loadOsEnvVars()inapps/geolibre-desktop/src/lib/assistant/os-env.tsand cached for synchronous access. - Non-desktop builds receive empty objects, ensuring secrets never leak into browser contexts.
- The
useRuntimeEnvironmentVariableshook merges OS and deployment variables before exposing them to the plugin runtime.
Frequently Asked Questions
Can plugins request additional environment variables beyond the AI provider list?
No. The whitelist in assistant/provider.ts is the authoritative source, and the Rust backend enforces this restriction at the IPC boundary. Plugin developers wishing to access other variables must request changes to the core OS_ENV_VAR_NAMES constant or implement custom configuration through the plugin's own settings system.
What happens if an API key contains special characters or newlines?
The environment reading pipeline preserves raw string values exactly as returned by the OS. GeoLibre performs no transformation or validation on key contents—this responsibility falls to the plugin's HTTP client. The fetch-based example above handles standard Bearer token formatting automatically.
Is there any performance cost to calling getRuntimeEnv() repeatedly?
No. The implementation uses a singleton promise pattern where loadOsEnvVars() executes the expensive Tauri IPC call exactly once. Subsequent calls return the cached promise, and synchronous reads from window.__GEOLIBRE_OS_ENV__ have negligible overhead. Plugins may safely call getRuntimeEnv() on every AI request without performance concerns.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →