mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:20:43 +00:00
refactor: derive web credential secret targets from manifests
This commit is contained in:
@@ -23,6 +23,7 @@ const CORE_SECRET_SURFACE_GUARDS = [
|
||||
/channels\.bluebubbles\./,
|
||||
/channels\.msteams\./,
|
||||
/channels\.nextcloud-talk\./,
|
||||
/plugins\.entries\.(?:brave|google|exa|xai|moonshot|perplexity|firecrawl|tavily|minimax)\.config\.web(?:Search|Fetch)\.apiKey/,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,10 +1,72 @@
|
||||
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
|
||||
import {
|
||||
loadPluginManifestRegistry,
|
||||
type PluginManifestRecord,
|
||||
} from "../plugins/manifest-registry.js";
|
||||
import { loadBundledChannelSecretContractApi } from "./channel-contract-api.js";
|
||||
import type { SecretTargetRegistryEntry } from "./target-registry-types.js";
|
||||
|
||||
const SECRET_INPUT_SHAPE = "secret_input"; // pragma: allowlist secret
|
||||
const SIBLING_REF_SHAPE = "sibling_ref"; // pragma: allowlist secret
|
||||
|
||||
const WEB_PROVIDER_SECRET_CONFIGS = [
|
||||
{ contract: "webSearchProviders", configPath: "webSearch.apiKey" },
|
||||
{ contract: "webFetchProviders", configPath: "webFetch.apiKey" },
|
||||
] as const;
|
||||
|
||||
type WebProviderSecretConfig = (typeof WEB_PROVIDER_SECRET_CONFIGS)[number];
|
||||
|
||||
function createPluginOpenClawConfigSecretTargetEntry(
|
||||
pluginId: string,
|
||||
configPath: string,
|
||||
): SecretTargetRegistryEntry {
|
||||
const pathPattern = ["plugins", "entries", pluginId, "config", ...configPath.split(".")].join(
|
||||
".",
|
||||
);
|
||||
return {
|
||||
id: pathPattern,
|
||||
targetType: pathPattern,
|
||||
configFile: "openclaw.json",
|
||||
pathPattern,
|
||||
secretShape: SECRET_INPUT_SHAPE,
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
};
|
||||
}
|
||||
|
||||
function hasSensitiveConfigHint(
|
||||
plugin: PluginManifestRecord,
|
||||
configPath: WebProviderSecretConfig["configPath"],
|
||||
): boolean {
|
||||
return plugin.configUiHints?.[configPath]?.sensitive === true;
|
||||
}
|
||||
|
||||
function hasWebProviderContract(
|
||||
plugin: PluginManifestRecord,
|
||||
contract: WebProviderSecretConfig["contract"],
|
||||
): boolean {
|
||||
return (plugin.contracts?.[contract]?.length ?? 0) > 0;
|
||||
}
|
||||
|
||||
function listBundledWebProviderSecretTargetRegistryEntries(): SecretTargetRegistryEntry[] {
|
||||
const entries: SecretTargetRegistryEntry[] = [];
|
||||
for (const record of loadPluginManifestRegistry({}).plugins) {
|
||||
if (record.origin !== "bundled") {
|
||||
continue;
|
||||
}
|
||||
for (const config of WEB_PROVIDER_SECRET_CONFIGS) {
|
||||
if (
|
||||
hasWebProviderContract(record, config.contract) &&
|
||||
hasSensitiveConfigHint(record, config.configPath)
|
||||
) {
|
||||
entries.push(createPluginOpenClawConfigSecretTargetEntry(record.id, config.configPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
return entries.toSorted((left, right) => left.id.localeCompare(right.id));
|
||||
}
|
||||
|
||||
function listChannelSecretTargetRegistryEntries(): SecretTargetRegistryEntry[] {
|
||||
const entries: SecretTargetRegistryEntry[] = [];
|
||||
|
||||
@@ -347,116 +409,6 @@ const CORE_SECRET_TARGET_REGISTRY: SecretTargetRegistryEntry[] = [
|
||||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
{
|
||||
id: "plugins.entries.brave.config.webSearch.apiKey",
|
||||
targetType: "plugins.entries.brave.config.webSearch.apiKey",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: "plugins.entries.brave.config.webSearch.apiKey",
|
||||
secretShape: SECRET_INPUT_SHAPE,
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
{
|
||||
id: "plugins.entries.google.config.webSearch.apiKey",
|
||||
targetType: "plugins.entries.google.config.webSearch.apiKey",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: "plugins.entries.google.config.webSearch.apiKey",
|
||||
secretShape: SECRET_INPUT_SHAPE,
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
{
|
||||
id: "plugins.entries.exa.config.webSearch.apiKey",
|
||||
targetType: "plugins.entries.exa.config.webSearch.apiKey",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: "plugins.entries.exa.config.webSearch.apiKey",
|
||||
secretShape: SECRET_INPUT_SHAPE,
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
{
|
||||
id: "plugins.entries.xai.config.webSearch.apiKey",
|
||||
targetType: "plugins.entries.xai.config.webSearch.apiKey",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: "plugins.entries.xai.config.webSearch.apiKey",
|
||||
secretShape: SECRET_INPUT_SHAPE,
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
{
|
||||
id: "plugins.entries.moonshot.config.webSearch.apiKey",
|
||||
targetType: "plugins.entries.moonshot.config.webSearch.apiKey",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: "plugins.entries.moonshot.config.webSearch.apiKey",
|
||||
secretShape: SECRET_INPUT_SHAPE,
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
{
|
||||
id: "plugins.entries.perplexity.config.webSearch.apiKey",
|
||||
targetType: "plugins.entries.perplexity.config.webSearch.apiKey",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: "plugins.entries.perplexity.config.webSearch.apiKey",
|
||||
secretShape: SECRET_INPUT_SHAPE,
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
{
|
||||
id: "plugins.entries.firecrawl.config.webSearch.apiKey",
|
||||
targetType: "plugins.entries.firecrawl.config.webSearch.apiKey",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: "plugins.entries.firecrawl.config.webSearch.apiKey",
|
||||
secretShape: SECRET_INPUT_SHAPE,
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
{
|
||||
id: "plugins.entries.firecrawl.config.webFetch.apiKey",
|
||||
targetType: "plugins.entries.firecrawl.config.webFetch.apiKey",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: "plugins.entries.firecrawl.config.webFetch.apiKey",
|
||||
secretShape: SECRET_INPUT_SHAPE,
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
{
|
||||
id: "plugins.entries.tavily.config.webSearch.apiKey",
|
||||
targetType: "plugins.entries.tavily.config.webSearch.apiKey",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: "plugins.entries.tavily.config.webSearch.apiKey",
|
||||
secretShape: SECRET_INPUT_SHAPE,
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
{
|
||||
id: "plugins.entries.minimax.config.webSearch.apiKey",
|
||||
targetType: "plugins.entries.minimax.config.webSearch.apiKey",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: "plugins.entries.minimax.config.webSearch.apiKey",
|
||||
secretShape: SECRET_INPUT_SHAPE,
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
];
|
||||
|
||||
let cachedSecretTargetRegistry: SecretTargetRegistryEntry[] | null = null;
|
||||
@@ -471,6 +423,7 @@ export function getSecretTargetRegistry(): SecretTargetRegistryEntry[] {
|
||||
}
|
||||
cachedSecretTargetRegistry = [
|
||||
...CORE_SECRET_TARGET_REGISTRY,
|
||||
...listBundledWebProviderSecretTargetRegistryEntries(),
|
||||
...listChannelSecretTargetRegistryEntries(),
|
||||
];
|
||||
return cachedSecretTargetRegistry;
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
TALK_TEST_PROVIDER_API_KEY_PATH,
|
||||
TALK_TEST_PROVIDER_ID,
|
||||
} from "../test-utils/talk-test-provider.js";
|
||||
import { getCoreSecretTargetRegistry } from "./target-registry-data.js";
|
||||
import {
|
||||
discoverConfigSecretTargetsByIds,
|
||||
resolveConfigSecretTargetByPath,
|
||||
@@ -43,7 +44,11 @@ describe("secret target registry", () => {
|
||||
expect(target).toBeNull();
|
||||
});
|
||||
|
||||
it("includes exa webSearch api key target path", () => {
|
||||
it("derives bundled web provider api key target paths from plugin manifests", () => {
|
||||
const coreTargetIds = new Set(getCoreSecretTargetRegistry().map((entry) => entry.id));
|
||||
expect(coreTargetIds.has("plugins.entries.exa.config.webSearch.apiKey")).toBe(false);
|
||||
expect(coreTargetIds.has("plugins.entries.firecrawl.config.webFetch.apiKey")).toBe(false);
|
||||
|
||||
const target = resolveConfigSecretTargetByPath([
|
||||
"plugins",
|
||||
"entries",
|
||||
@@ -55,5 +60,16 @@ describe("secret target registry", () => {
|
||||
|
||||
expect(target).not.toBeNull();
|
||||
expect(target?.entry?.id).toBe("plugins.entries.exa.config.webSearch.apiKey");
|
||||
|
||||
const fetchTarget = resolveConfigSecretTargetByPath([
|
||||
"plugins",
|
||||
"entries",
|
||||
"firecrawl",
|
||||
"config",
|
||||
"webFetch",
|
||||
"apiKey",
|
||||
]);
|
||||
expect(fetchTarget).not.toBeNull();
|
||||
expect(fetchTarget?.entry?.id).toBe("plugins.entries.firecrawl.config.webFetch.apiKey");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user