mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-12 18:12:56 +00:00
docs: document plugin security channel helpers
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { listChannelCatalogEntries } from "./channel-catalog-registry.js";
|
||||
import type { PluginPackageChannel } from "./manifest.js";
|
||||
|
||||
/** Lists channel metadata contributed by bundled package manifests. */
|
||||
export function listBundledPackageChannelMetadata(): readonly PluginPackageChannel[] {
|
||||
return listChannelCatalogEntries({ origin: "bundled" }).map((entry) => entry.channel);
|
||||
}
|
||||
|
||||
/** Finds bundled package channel metadata by id or alias. */
|
||||
export function findBundledPackageChannelMetadata(
|
||||
channelId: string,
|
||||
): PluginPackageChannel | undefined {
|
||||
|
||||
@@ -37,6 +37,7 @@ type BundledPluginPathPair = {
|
||||
built: string;
|
||||
};
|
||||
|
||||
/** Metadata collected from a bundled plugin package and manifest. */
|
||||
export type BundledPluginMetadata = {
|
||||
dirName: string;
|
||||
idHint: string;
|
||||
@@ -168,6 +169,7 @@ function collectBundledPluginMetadata(
|
||||
return entries;
|
||||
}
|
||||
|
||||
/** Lists bundled plugin metadata from source or built package layouts. */
|
||||
export function listBundledPluginMetadata(params?: {
|
||||
rootDir?: string;
|
||||
scanDir?: string;
|
||||
@@ -190,6 +192,7 @@ export function listBundledPluginMetadata(params?: {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/** Finds bundled plugin metadata by manifest id. */
|
||||
export function findBundledPluginMetadataById(
|
||||
pluginId: string,
|
||||
params?: {
|
||||
@@ -202,6 +205,7 @@ export function findBundledPluginMetadataById(
|
||||
return listBundledPluginMetadata(params).find((entry) => entry.manifest.id === pluginId);
|
||||
}
|
||||
|
||||
/** Resolves the source directory for a bundled plugin in the current workspace. */
|
||||
export function resolveBundledPluginWorkspaceSourcePath(params: {
|
||||
rootDir: string;
|
||||
scanDir?: string;
|
||||
@@ -296,6 +300,7 @@ function listBundledPluginEntrySearchPaths(
|
||||
return uniqueStrings(paths);
|
||||
}
|
||||
|
||||
/** Resolves a generated runtime path for a bundled plugin entry. */
|
||||
export function resolveBundledPluginGeneratedPath(
|
||||
rootDir: string,
|
||||
entry: BundledPluginPathPair | undefined,
|
||||
@@ -345,6 +350,7 @@ function resolveBundledPluginEntryCandidate(baseDir: string, entryPath: string):
|
||||
return candidate;
|
||||
}
|
||||
|
||||
/** Resolves the repo entry path for a bundled plugin, preferring source unless requested. */
|
||||
export function resolveBundledPluginRepoEntryPath(params: {
|
||||
rootDir: string;
|
||||
pluginId: string;
|
||||
|
||||
@@ -28,11 +28,13 @@ import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry-c
|
||||
|
||||
const IGNORED_CHANNEL_CONFIG_KEYS = new Set(["defaults", "modelByChannel"]);
|
||||
|
||||
/** Source classes that can make a channel appear configured for read-only scopes. */
|
||||
export type ConfiguredChannelPresenceSource =
|
||||
| "explicit-config"
|
||||
| Exclude<ChannelPresenceSignalSource, "config">
|
||||
| "manifest-env";
|
||||
|
||||
/** Reasons a configured channel signal is not effective. */
|
||||
export type ConfiguredChannelBlockedReason =
|
||||
| "plugins-disabled"
|
||||
| "blocked-by-denylist"
|
||||
@@ -44,6 +46,7 @@ export type ConfiguredChannelBlockedReason =
|
||||
| "no-channel-owner"
|
||||
| "not-activated";
|
||||
|
||||
/** Policy evaluation row for one configured channel signal. */
|
||||
export type ConfiguredChannelPresencePolicyEntry = {
|
||||
channelId: string;
|
||||
sources: ConfiguredChannelPresenceSource[];
|
||||
@@ -70,6 +73,7 @@ function hasNonEmptyEnvValue(env: NodeJS.ProcessEnv, key: string): boolean {
|
||||
return typeof value === "string" && value.trim().length > 0;
|
||||
}
|
||||
|
||||
/** True when config contains meaningful enabled channel settings. */
|
||||
export function hasExplicitChannelConfig(params: {
|
||||
config: OpenClawConfig;
|
||||
channelId: string;
|
||||
@@ -89,6 +93,7 @@ export function hasExplicitChannelConfig(params: {
|
||||
return enabled === true || hasMeaningfulChannelConfig(entry);
|
||||
}
|
||||
|
||||
/** Lists explicitly configured channel ids, excluding global channel config keys. */
|
||||
export function listExplicitConfiguredChannelIdsForConfig(config: OpenClawConfig): string[] {
|
||||
const channels = config.channels;
|
||||
if (!channels || typeof channels !== "object" || Array.isArray(channels)) {
|
||||
@@ -182,6 +187,7 @@ function isChannelPluginEligibleForScopedOwnership(params: {
|
||||
rootConfig: OpenClawConfig;
|
||||
channelId?: string;
|
||||
}): boolean {
|
||||
// Explicit config can activate bundled channel owners even under restrictive allowlists.
|
||||
const allowRestrictiveAllowlistBypass =
|
||||
params.channelId !== undefined &&
|
||||
isBundledManifestOwner(params.plugin) &&
|
||||
@@ -221,6 +227,7 @@ function evaluateEffectiveChannelPlugin(params: {
|
||||
config: OpenClawConfig;
|
||||
activationSource: ReturnType<typeof createPluginActivationSource>;
|
||||
}): { effective: boolean; pluginId: string; blockedReason?: ConfiguredChannelBlockedReason } {
|
||||
// Bundled channels with explicit config are effective before default enablement checks.
|
||||
const explicitBundledChannelConfig =
|
||||
isBundledManifestOwner(params.plugin) &&
|
||||
hasExplicitChannelConfig({
|
||||
@@ -319,6 +326,7 @@ function loadInstalledChannelManifestRecords(params: {
|
||||
}).plugins;
|
||||
}
|
||||
|
||||
/** Resolves effective configured-channel policy rows from config, auth state, env, and manifests. */
|
||||
export function resolveConfiguredChannelPresencePolicy(params: {
|
||||
config: OpenClawConfig;
|
||||
activationSourceConfig?: OpenClawConfig;
|
||||
@@ -406,6 +414,7 @@ export function resolveConfiguredChannelPresencePolicy(params: {
|
||||
return entries;
|
||||
}
|
||||
|
||||
/** Lists effective channel ids available to read-only scoped discovery. */
|
||||
export function listConfiguredChannelIdsForReadOnlyScope(
|
||||
params: Parameters<typeof resolveConfiguredChannelPresencePolicy>[0],
|
||||
): string[] {
|
||||
@@ -414,12 +423,14 @@ export function listConfiguredChannelIdsForReadOnlyScope(
|
||||
.map((entry) => entry.channelId);
|
||||
}
|
||||
|
||||
/** True when read-only scoped discovery has any effective configured channel. */
|
||||
export function hasConfiguredChannelsForReadOnlyScope(
|
||||
params: Parameters<typeof resolveConfiguredChannelPresencePolicy>[0],
|
||||
): boolean {
|
||||
return listConfiguredChannelIdsForReadOnlyScope(params).length > 0;
|
||||
}
|
||||
|
||||
/** Lists channel ids that should be announced as configured for operators. */
|
||||
export function listConfiguredAnnounceChannelIdsForConfig(params: {
|
||||
config: OpenClawConfig;
|
||||
activationSourceConfig?: OpenClawConfig;
|
||||
@@ -498,6 +509,7 @@ function resolveScopedChannelOwnerPluginIds(params: {
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
/** Resolves plugin ids discoverable for scoped channel activation. */
|
||||
export function resolveDiscoverableScopedChannelPluginIds(params: {
|
||||
config: OpenClawConfig;
|
||||
activationSourceConfig?: OpenClawConfig;
|
||||
@@ -509,6 +521,7 @@ export function resolveDiscoverableScopedChannelPluginIds(params: {
|
||||
return resolveScopedChannelOwnerPluginIds(params);
|
||||
}
|
||||
|
||||
/** Resolves plugin ids that own currently configured channels. */
|
||||
export function resolveConfiguredChannelPluginIds(params: {
|
||||
config: OpenClawConfig;
|
||||
activationSourceConfig?: OpenClawConfig;
|
||||
|
||||
@@ -50,6 +50,7 @@ function collectMissingChannelMetaFields(meta?: Partial<ChannelMeta> | null): st
|
||||
return missing;
|
||||
}
|
||||
|
||||
/** Validates and normalizes a channel plugin registration before runtime catalog insertion. */
|
||||
export function normalizeRegisteredChannelPlugin(params: {
|
||||
pluginId: string;
|
||||
source: string;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { AgentToolResult } from "../agents/runtime/index.js";
|
||||
|
||||
/** Tool-result event emitted to Codex app-server plugin extensions. */
|
||||
export type CodexAppServerToolResultEvent = {
|
||||
threadId: string;
|
||||
turnId: string;
|
||||
@@ -9,6 +10,7 @@ export type CodexAppServerToolResultEvent = {
|
||||
result: AgentToolResult<unknown>;
|
||||
};
|
||||
|
||||
/** Session context passed with Codex app-server extension events. */
|
||||
export type CodexAppServerExtensionContext = {
|
||||
agentId?: string;
|
||||
sessionId?: string;
|
||||
@@ -16,10 +18,12 @@ export type CodexAppServerExtensionContext = {
|
||||
runId?: string;
|
||||
};
|
||||
|
||||
/** Optional replacement result returned by a Codex app-server extension handler. */
|
||||
export type CodexAppServerToolResultHandlerResult = {
|
||||
result: AgentToolResult<unknown>;
|
||||
};
|
||||
|
||||
/** Runtime event surface exposed to Codex app-server extension factories. */
|
||||
export type CodexAppServerExtensionRuntime = {
|
||||
on: (
|
||||
event: "tool_result",
|
||||
@@ -33,6 +37,7 @@ export type CodexAppServerExtensionRuntime = {
|
||||
) => void;
|
||||
};
|
||||
|
||||
/** Factory signature for Codex app-server plugin extensions. */
|
||||
export type CodexAppServerExtensionFactory = (
|
||||
runtime: CodexAppServerExtensionRuntime,
|
||||
) => Promise<void> | void;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||
|
||||
/** Normalizes plugin HTTP paths to leading-slash form with optional fallback. */
|
||||
export function normalizePluginHttpPath(
|
||||
path?: string | null,
|
||||
fallback?: string | null,
|
||||
|
||||
@@ -11,6 +11,7 @@ type InstallScanLogger = {
|
||||
warn?: (message: string) => void;
|
||||
};
|
||||
|
||||
/** Result returned by plugin/skill install security policy checks. */
|
||||
export type InstallSecurityScanResult = {
|
||||
blocked?: {
|
||||
code?: "security_scan_blocked" | "security_scan_failed";
|
||||
@@ -18,8 +19,10 @@ export type InstallSecurityScanResult = {
|
||||
};
|
||||
};
|
||||
|
||||
/** Plugin install request kinds that share install policy without skill install semantics. */
|
||||
export type PluginInstallRequestKind = Exclude<InstallPolicyRequestKind, "skill-install">;
|
||||
|
||||
/** Skill install metadata shape passed into shared install policy evaluation. */
|
||||
export type SkillInstallSpecMetadata = {
|
||||
id?: string;
|
||||
kind: "brew" | "node" | "go" | "uv" | "download";
|
||||
@@ -36,16 +39,19 @@ export type SkillInstallSpecMetadata = {
|
||||
targetDir?: string;
|
||||
};
|
||||
|
||||
/** Package executable metadata used to scope dependency and entrypoint scans. */
|
||||
export type PackageExecutableScanMetadata = {
|
||||
runtimeExtensions?: readonly string[];
|
||||
runtimeSetupEntry?: string;
|
||||
setupEntry?: string;
|
||||
};
|
||||
|
||||
/** Lazily loads install scanning so normal plugin startup avoids policy/runtime imports. */
|
||||
async function loadInstallSecurityScanRuntime() {
|
||||
return await import("./install-security-scan.runtime.js");
|
||||
}
|
||||
|
||||
/** Scans an unpacked bundle source before plugin install/update. */
|
||||
export async function scanBundleInstallSource(
|
||||
params: InstallSafetyOverrides & {
|
||||
config?: OpenClawConfig;
|
||||
@@ -63,6 +69,7 @@ export async function scanBundleInstallSource(
|
||||
return await scanBundleInstallSourceRuntime(params);
|
||||
}
|
||||
|
||||
/** Scans a package source directory and executable metadata before install/update. */
|
||||
export async function scanPackageInstallSource(
|
||||
params: InstallSafetyOverrides & {
|
||||
config?: OpenClawConfig;
|
||||
@@ -84,6 +91,7 @@ export async function scanPackageInstallSource(
|
||||
return await scanPackageInstallSourceRuntime(params);
|
||||
}
|
||||
|
||||
/** Scans the installed package dependency tree after npm resolution. */
|
||||
export async function scanInstalledPackageDependencyTree(params: {
|
||||
additionalPackageDirs?: string[];
|
||||
allowManagedNpmRootPackagePeerSymlinks?: boolean;
|
||||
@@ -103,6 +111,7 @@ export async function scanInstalledPackageDependencyTree(params: {
|
||||
return await scanInstalledPackageDependencyTreeRuntime(params);
|
||||
}
|
||||
|
||||
/** Scans one file-based plugin install source. */
|
||||
export async function scanFileInstallSource(
|
||||
params: InstallSafetyOverrides & {
|
||||
config?: OpenClawConfig;
|
||||
@@ -118,6 +127,7 @@ export async function scanFileInstallSource(
|
||||
return await scanFileInstallSourceRuntime(params);
|
||||
}
|
||||
|
||||
/** Runs npm install policy checks before package install side effects. */
|
||||
export async function preflightPluginNpmInstallPolicy(params: {
|
||||
config?: OpenClawConfig;
|
||||
logger: InstallScanLogger;
|
||||
@@ -133,6 +143,7 @@ export async function preflightPluginNpmInstallPolicy(params: {
|
||||
return await preflightPluginNpmInstallPolicyRuntime(params);
|
||||
}
|
||||
|
||||
/** Runs git install policy checks before plugin install side effects. */
|
||||
export async function preflightPluginGitInstallPolicy(params: {
|
||||
config?: OpenClawConfig;
|
||||
logger: InstallScanLogger;
|
||||
@@ -146,6 +157,7 @@ export async function preflightPluginGitInstallPolicy(params: {
|
||||
return await preflightPluginGitInstallPolicyRuntime(params);
|
||||
}
|
||||
|
||||
/** Evaluates shared install policy for skill-managed dependency installs. */
|
||||
export async function evaluateSkillInstallPolicy(params: {
|
||||
config?: OpenClawConfig;
|
||||
installId: string;
|
||||
|
||||
@@ -13,11 +13,13 @@ import {
|
||||
} from "./interactive-state.js";
|
||||
import type { PluginInteractiveHandlerRegistration } from "./types.js";
|
||||
|
||||
/** Registration result for plugin interactive namespace handlers. */
|
||||
export type InteractiveRegistrationResult = {
|
||||
ok: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
/** Resolves a channel payload to a registered plugin interactive namespace handler. */
|
||||
export function resolvePluginInteractiveNamespaceMatch(
|
||||
channel: string,
|
||||
data: string,
|
||||
@@ -29,6 +31,7 @@ export function resolvePluginInteractiveNamespaceMatch(
|
||||
});
|
||||
}
|
||||
|
||||
/** Registers one plugin interactive namespace for a channel. */
|
||||
export function registerPluginInteractiveHandler(
|
||||
pluginId: string,
|
||||
registration: PluginInteractiveHandlerRegistration,
|
||||
@@ -59,14 +62,17 @@ export function registerPluginInteractiveHandler(
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
/** Clears all active plugin interactive handlers. */
|
||||
export function clearPluginInteractiveHandlers(): void {
|
||||
clearPluginInteractiveHandlersState();
|
||||
}
|
||||
|
||||
/** Clears stored plugin interactive handler registrations. */
|
||||
export function clearPluginInteractiveHandlerRegistrations(): void {
|
||||
clearPluginInteractiveHandlerRegistrationsState();
|
||||
}
|
||||
|
||||
/** Clears active interactive handlers owned by one plugin. */
|
||||
export function clearPluginInteractiveHandlersForPlugin(pluginId: string): void {
|
||||
const interactiveHandlers = getPluginInteractiveHandlersState();
|
||||
for (const [key, value] of interactiveHandlers.entries()) {
|
||||
@@ -76,10 +82,12 @@ export function clearPluginInteractiveHandlersForPlugin(pluginId: string): void
|
||||
}
|
||||
}
|
||||
|
||||
/** Lists active plugin interactive handlers. */
|
||||
export function listPluginInteractiveHandlers(): RegisteredInteractiveHandler[] {
|
||||
return Array.from(getPluginInteractiveHandlersState().values());
|
||||
}
|
||||
|
||||
/** Restores active plugin interactive handlers from a saved registry snapshot. */
|
||||
export function restorePluginInteractiveHandlers(
|
||||
registrations: readonly RegisteredInteractiveHandler[],
|
||||
): void {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** Plugin-local re-export of shared path safety helpers for plugin install/runtime code. */
|
||||
export {
|
||||
isNotFoundPathError,
|
||||
hasNodeErrorCode,
|
||||
|
||||
@@ -12,24 +12,28 @@ type RunProviderModelSelectedHook =
|
||||
type ResolvePluginProviders = typeof import("./providers.runtime.js").resolvePluginProviders;
|
||||
type ResolvePluginSetupProvider = typeof import("./setup-registry.js").resolvePluginSetupProvider;
|
||||
|
||||
/** Runtime wrapper for provider plugin wizard choice resolution. */
|
||||
export function resolveProviderPluginChoice(
|
||||
...args: Parameters<ResolveProviderPluginChoice>
|
||||
): ReturnType<ResolveProviderPluginChoice> {
|
||||
return resolveProviderPluginChoiceImpl(...args);
|
||||
}
|
||||
|
||||
/** Runtime wrapper for provider model-selected hook dispatch. */
|
||||
export function runProviderModelSelectedHook(
|
||||
...args: Parameters<RunProviderModelSelectedHook>
|
||||
): ReturnType<RunProviderModelSelectedHook> {
|
||||
return runProviderModelSelectedHookImpl(...args);
|
||||
}
|
||||
|
||||
/** Runtime wrapper for registered model provider discovery. */
|
||||
export function resolvePluginProviders(
|
||||
...args: Parameters<ResolvePluginProviders>
|
||||
): ReturnType<ResolvePluginProviders> {
|
||||
return resolvePluginProvidersImpl(...args);
|
||||
}
|
||||
|
||||
/** Runtime wrapper for plugin setup-provider discovery. */
|
||||
export function resolvePluginSetupProvider(
|
||||
...args: Parameters<ResolvePluginSetupProvider>
|
||||
): ReturnType<ResolvePluginSetupProvider> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { WizardPrompter } from "../wizard/prompts.js";
|
||||
import type { SecretInputMode } from "./provider-auth-types.js";
|
||||
|
||||
/** Prompt copy overrides for provider secret input mode selection. */
|
||||
export type SecretInputModePromptCopy = {
|
||||
modeMessage?: string;
|
||||
plaintextLabel?: string;
|
||||
@@ -9,6 +10,7 @@ export type SecretInputModePromptCopy = {
|
||||
refHint?: string;
|
||||
};
|
||||
|
||||
/** Resolves provider secret input mode from explicit option or wizard selection. */
|
||||
export async function resolveSecretInputModeForEnvSelection(params: {
|
||||
prompter: Pick<WizardPrompter, "select">;
|
||||
explicitMode?: SecretInputMode;
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
/** Provider secret input modes: inline plaintext or external secret reference. */
|
||||
export type SecretInputMode = "plaintext" | "ref"; // pragma: allowlist secret
|
||||
|
||||
@@ -12,6 +12,7 @@ const CORE_BUILT_IN_MODEL_APIS = new Set([
|
||||
"openai-responses",
|
||||
]);
|
||||
|
||||
/** Returns the plugin API id that owns a provider config when it is not core built-in. */
|
||||
export function resolveProviderConfigApiOwnerHint(params: {
|
||||
provider: string;
|
||||
config?: OpenClawConfig;
|
||||
|
||||
@@ -12,6 +12,7 @@ export const PUBLIC_SURFACE_SOURCE_EXTENSIONS = [
|
||||
".cjs",
|
||||
] as const;
|
||||
|
||||
/** Normalizes a bundled public artifact subpath and rejects traversal/absolute paths. */
|
||||
export function normalizeBundledPluginArtifactSubpath(artifactBasename: string): string {
|
||||
if (
|
||||
path.posix.isAbsolute(artifactBasename) ||
|
||||
@@ -39,6 +40,7 @@ export function normalizeBundledPluginArtifactSubpath(artifactBasename: string):
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/** Normalizes a bundled plugin directory name and rejects path-like values. */
|
||||
export function normalizeBundledPluginDirName(dirName: string): string {
|
||||
const normalized = dirName.trim();
|
||||
if (
|
||||
@@ -54,6 +56,7 @@ export function normalizeBundledPluginDirName(dirName: string): string {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/** Resolves a source-tree public surface artifact path for bundled plugin development. */
|
||||
export function resolveBundledPluginSourcePublicSurfacePath(params: {
|
||||
sourceRoot: string;
|
||||
dirName: string;
|
||||
@@ -153,6 +156,7 @@ function resolvePublicSurfaceFromBundledDir(params: {
|
||||
);
|
||||
}
|
||||
|
||||
/** Resolves a bundled plugin public surface artifact across source, dist, and package layouts. */
|
||||
export function resolveBundledPluginPublicSurfacePath(params: {
|
||||
rootDir: string;
|
||||
dirName: string;
|
||||
|
||||
Reference in New Issue
Block a user