Skip to main content
Capabilities are the FDO security model for privileged SDK features. The FDO host explicitly grants capabilities to each plugin instance at initialization time. If a plugin tries to use a feature that requires a capability it was not granted, the SDK throws immediately — the operation does not proceed silently. This design keeps the host as the real security boundary and makes plugin permissions auditable and explicit.

Why capabilities exist

Without a capability model, any installed plugin could access persistent storage, execute system processes, or modify the hosts file — with no way for the host to control or audit which plugins do what. Capabilities solve this by requiring the host to explicitly grant each privileged feature before a plugin can use it. The host configures the granted set in the PLUGIN_INIT message payload, and the SDK enforces it at every privileged call site.

Available capabilities

Capability string: "storage.json"Required to call PluginRegistry.useStore("json"). Without this capability, requesting the JSON store throws:
Error: Capability 'storage.json' is required to use the JSON persistent store.
The JSON store persists data to a file under the plugin’s scoped storage root. It requires an explicit storage root configured via PluginRegistry.configureStorage({ rootDir }) or the FDO_SDK_STORAGE_ROOT environment variable.
// Requires storage.json capability
const store = PluginRegistry.useStore("json");
store.set("lastRun", new Date().toISOString());
Capability string: "sudo.prompt"Required to call runWithSudo(...). This capability allows your plugin to prompt the user for sudo credentials to run privileged system commands.
import { runWithSudo } from "@anikitenko/fdo-sdk";

// Requires sudo.prompt capability
await runWithSudo("some-privileged-command", ["--arg"]);
Capability string: "system.hosts.write"Required for host-mediated updates to the system hosts file via createHostsWriteActionRequest(...) and requestPrivilegedAction(...). The host validates, audits, and applies the change — plugins never write directly.
import {
    createHostsWriteActionRequest,
    requestPrivilegedAction,
} from "@anikitenko/fdo-sdk";

// Requires system.hosts.write capability
const request = createHostsWriteActionRequest({
    records: [{ address: "127.0.0.1", hostname: "myapp.local" }],
});
const response = await requestPrivilegedAction(request);
Capability string: "system.process.exec"Required for host-mediated process execution via createProcessExecActionRequest(...) or operator tooling helpers. Always paired with a scope capability (system.process.scope.<scope-id>) that defines which executables, working directories, and arguments are permitted.
import {
    createProcessExecActionRequest,
    requestPrivilegedAction,
} from "@anikitenko/fdo-sdk";

// Requires system.process.exec + system.process.scope.docker-cli
const request = createProcessExecActionRequest({
    scope: "docker-cli",
    command: "docker",
    args: ["ps", "--format", "json"],
});
const response = await requestPrivilegedAction(request);
Capability string: "system.fs.scope.<scope-id>" (template literal type)Grants permission for host-mediated filesystem mutations within a named scope. Used alongside system.fs.mutate privileged actions. The scope constrains which paths and operations the host will allow.Example: "system.fs.scope.config-writer" grants write access within a scope named config-writer.
import {
    createFilesystemMutateActionRequest,
    requestPrivilegedAction,
} from "@anikitenko/fdo-sdk";

// Requires system.fs.scope.config-writer capability
const request = createFilesystemMutateActionRequest({
    scope: "config-writer",
    operations: [
        { type: "writeFile", path: "/etc/myapp/config.json", content: "{}" },
    ],
});
const response = await requestPrivilegedAction(request);
Capability string: "system.process.scope.<scope-id>" (template literal type)Narrows which executables, arguments, working directories, and environment variables the host allows within the named process execution scope. Always used together with system.process.exec.Example: "system.process.scope.kubectl" restricts execution to kubectl commands only.
import {
    createProcessScopeCapability,
    createProcessCapabilityBundle,
} from "@anikitenko/fdo-sdk";

const scopeCapability = createProcessScopeCapability("kubectl");
// → "system.process.scope.kubectl"

const bundle = createProcessCapabilityBundle("kubectl");
// → ["system.process.exec", "system.process.scope.kubectl"]

How the host grants capabilities

The host includes capabilities in the PLUGIN_INIT message payload. When the SDK processes PLUGIN_INIT, it calls PluginRegistry.configureCapabilities(...) with the granted set before calling init(). The PLUGIN_INIT payload shape:
type PluginInitRequest = {
    apiVersion?: string;
    capabilities?: PluginCapability[];
};
Where PluginCapability is:
export type FilesystemScopeCapability = `system.fs.scope.${string}`;
export type ProcessScopeCapability = `system.process.scope.${string}`;
export type PluginCapability =
    | "storage.json"
    | "sudo.prompt"
    | "system.hosts.write"
    | "system.process.exec"
    | FilesystemScopeCapability
    | ProcessScopeCapability;
You do not call configureCapabilities directly in plugin code — the SDK handles this as part of the PLUGIN_INIT flow. Your plugin receives only the capabilities the host explicitly grants.

Declaring capabilities in your plugin

Implement the optional declareCapabilities() method on your plugin class to declare which capabilities your plugin needs. The SDK uses this declaration during init() preflight to log any capabilities you declared but were not granted.
import {
    FDO_SDK,
    FDOInterface,
    PluginMetadata,
    PluginCapability,
    PluginRegistry,
} from "@anikitenko/fdo-sdk";

export default class DockerPlugin extends FDO_SDK implements FDOInterface {
    private readonly _metadata: PluginMetadata = {
        name: "Docker Manager",
        version: "1.0.0",
        author: "Your Name",
        description: "Manages Docker containers",
        icon: "box",
    };

    get metadata(): PluginMetadata {
        return this._metadata;
    }

    declareCapabilities(): PluginCapability[] {
        return [
            "system.process.exec",
            "system.process.scope.docker-cli",
        ];
    }

    init(): void {
        this.info("Docker plugin initialized");
    }

    render(): string {
        return `<div>Docker Manager</div>`;
    }
}

new DockerPlugin();
declareCapabilities() is defined in FDOInterface:
export interface FDOInterface {
    declareCapabilities?(): PluginCapability[];
    init(): void;
    render(): string;
    renderOnLoad?(): string;
}
Use declareCapabilities() in all operator-style plugins. It enables the host to run preflight capability checks before any rare action paths are triggered — surfacing missing permissions early rather than at the moment a user tries to perform an action.

What happens when a capability is missing

If your plugin calls a privileged SDK feature without the required capability, the SDK throws an error immediately. The operation does not proceed. For example, calling PluginRegistry.useStore("json") without the storage.json capability:
Error: Capability 'storage.json' is required to use the JSON persistent store.
The error message follows a consistent format that encodes the missing capability name, which you can parse programmatically.

Helper functions

describeCapability(capability)

Returns a human-readable descriptor for any capability string, including label, description, and category. Use this to build diagnostic UIs or display permission explanations to users.
import { describeCapability } from "@anikitenko/fdo-sdk";

const descriptor = describeCapability("system.process.exec");
// {
//   capability: "system.process.exec",
//   label: "Process Execution",
//   description: "...",
//   category: "process"
// }

parseMissingCapabilityError(error)

Parses a capability error thrown by the SDK and returns a structured MissingCapabilityDiagnostic with the capability name, category, label, description, and remediation guidance. Use this in error handlers to surface actionable information rather than raw error strings.
import { parseMissingCapabilityError } from "@anikitenko/fdo-sdk";

PluginRegistry.registerHandler("runDockerPs", async () => {
    try {
        // ... privileged action that might fail
        return await requestPrivilegedAction(/* ... */);
    } catch (error) {
        const diagnostic = parseMissingCapabilityError(error);
        if (diagnostic) {
            return {
                error: `Missing capability: ${diagnostic.label}`,
                remediation: diagnostic.remediation,
                category: diagnostic.category,
            };
        }
        throw error;
    }
});
The returned MissingCapabilityDiagnostic type:
export type MissingCapabilityDiagnostic = {
    capability: PluginCapability | string;
    action: string;
    category: CapabilityCategory;
    label: string;
    description: string;
    remediation: string;
};

Capability bundle helpers

The SDK provides helpers to build capability arrays for common patterns:
import {
    createProcessCapabilityBundle,
    createFilesystemCapabilityBundle,
    createWorkflowCapabilityBundle,
    createProcessScopeCapability,
    createFilesystemScopeCapability,
} from "@anikitenko/fdo-sdk";

// ["system.process.exec", "system.process.scope.kubectl"]
const processBundle = createProcessCapabilityBundle("kubectl");

// ["system.fs.scope.config-writer", "system.hosts.write"]
const fsBundle = createFilesystemCapabilityBundle("config-writer");

// Scope capability strings directly
const processScope = createProcessScopeCapability("helm");
// → "system.process.scope.helm"

const fsScope = createFilesystemScopeCapability("deploy-root");
// → "system.fs.scope.deploy-root"
Use these in declareCapabilities() to avoid hand-crafting capability strings:
declareCapabilities(): PluginCapability[] {
    return [
        ...createProcessCapabilityBundle("kubectl"),
        ...createProcessCapabilityBundle("helm"),
        "storage.json",
    ];
}

Capability diagnostics

The SDK tracks capability usage and denial counts for every plugin instance. You can query this data at runtime using the built-in diagnostics handler:
{
    message: "UI_MESSAGE",
    content: {
        handler: "__sdk.getDiagnostics",
        content: { notificationsLimit: 20 }
    }
}
The response includes:
capabilities: {
    declaration: {
        declared: PluginCapability[];      // from declareCapabilities()
        missing: PluginCapability[];       // declared but not granted
        undeclaredGranted: PluginCapability[]; // granted but not declared
    };
    permissions: {
        granted: PluginCapability[];
        usageCount: Record<string, number>;
        deniedCount: Record<string, number>;
    };
}
This lets hosts, AI tools, and operators distinguish missing broad capabilities from missing narrow scope capabilities without ad hoc string parsing.