Capability System
Glyx gates access to native APIs behind capabilities β explicit permissions declared in glyx.config.ts.
Why capabilities exist
A Glyx app can read files, query databases, make network requests, and access the OS keychain. These are powerful APIs. Capabilities ensure:
- No surprise access β an app can't silently exfiltrate files
- Auditable permissions β the config is the source of truth
- Principle of least privilege β declare only what you need
Declaring capabilities
// glyx.config.ts
import { defineConfig } from '@glyx-dev/react'
export default defineConfig({
name: 'my-app',
capabilities: [
'fs', // file system read/write
'db', // SQLite
'network', // fetch, WebSocket, mDNS
'clipboard',
],
})What happens without a capability
The error is thrown at the first call to a gated API, not at build time.
Available capabilities
| Capability | Grants access to |
|---|---|
fs | glyx.fs β read, write, watch files |
db | glyx.db β SQLite queries |
network | glyx.network β fetch, WebSocket, mDNS |
ai | glyx.ai β local model inference |
audio | glyx.audio β playback and recording |
clipboard | glyx.clipboard β read and write |
dialog | glyx.dialog β open/save file dialogs |
notification | glyx.notification β OS notifications |
tray | glyx.tray β system tray icons and menus |
credentials | glyx.credentials β OS keychain |
system | glyx.system β battery, storage, gamepad |
deeplink | glyx.deeplink β URL scheme handler |
camera | glyx.camera β camera device access |
Stable β The capability system API is stable and will not change in v0.x releases.
Capability scoping
The fs capability supports per-path glob scoping to restrict which directories an app can access:
export default defineConfig({
name: 'my-app',
capabilities: [
{ name: 'fs', paths: ['~/Documents/my-app/**', '~/.config/my-app/**'] },
],
})Any fs call targeting a path outside the declared globs throws CapabilityDenied at runtime. Path traversal attempts (e.g. ../../etc/passwd) are rejected before glob evaluation.
Capability providers
Capabilities are delivered in two ways:
Built-in
Core capabilities β fs, db, network, clipboard, dialog, notification, credentials, deeplink β are compiled directly into the Glyx runtime via Cargo feature flags. They add no startup overhead and are always available when declared.
Dynamic modules
Heavy or platform-specific capabilities β audio, ai, camera, gamepad, hid β can be provided as separate native libraries loaded at startup. This keeps the core binary lean and lets you ship only the capabilities your app actually needs.
Glyx looks for capability libraries next to the executable at startup:
| Platform | Filename pattern |
|---|---|
| Windows | glyx_cap_audio.dll, glyx_cap_ai.dll, β¦ |
| macOS | libglyx_cap_audio.dylib, β¦ |
| Linux | libglyx_cap_audio.so, β¦ |
If a library is present and its ABI version matches, Glyx loads it dynamically. The capabilities declaration in glyx.config.ts still controls whether the JS API is accessible β the library provides the implementation.
Hash pinning β automatic
glyx package automatically scans for capability modules next to your project, computes their SHA-256, copies them into the dist folder, and writes a glyx-caps.lock file alongside the binary:
target/glyx/dist/my-app/
my-app.exe
glyx_cap_audio.dll
glyx-caps.lock β auto-generated, commit thisglyx-caps.lock is a JSON file mapping capability names to hashes:
{
"audio": "e3b0c44298fc1c149afb4c8996fb92427ae41e4649b934ca495991b7852b855",
"camera": "a87ff679a2f3e71d9181a67b7542122c"
}At startup, the loader reads glyx-caps.lock from the exe directory and verifies each module's hash before loading. A tampered or mismatched file is rejected and the capability is treated as absent. In dev mode (glyx dev) no lock file exists so modules load unchecked β this is intentional. In release builds, any module without a pinned hash triggers a warning log.
You never need to compute or write hashes manually.
Dynamic modules implement the stable C ABI defined in the glyx-cap-abi crate:
#[repr(C)]
pub struct AudioCap {
pub version: u32,
pub init: extern "C" fn() -> i32,
pub play: extern "C" fn(path: *const c_char, loop_: bool) -> u32,
pub pause: extern "C" fn(id: u32),
pub resume: extern "C" fn(id: u32),
pub stop: extern "C" fn(id: u32),
pub set_volume: extern "C" fn(id: u32, vol: f32),
pub get_volume: extern "C" fn(id: u32) -> f32,
pub get_time: extern "C" fn(id: u32) -> f64,
pub duration: extern "C" fn(id: u32) -> f64,
pub seek: extern "C" fn(id: u32, secs: f64),
pub poll: extern "C" fn() -> *const c_char,
pub shutdown: extern "C" fn(),
}The ABI is versioned (ABI_VERSION) β a version mismatch causes the load to fail cleanly with a warning rather than a crash.