mirror of https://github.com/openclaw/openclaw.git
Security: require Feishu webhook encrypt key (#44087)
* Feishu: require webhook encrypt key in schema * Feishu: cover encrypt key webhook validation * Feishu: enforce encrypt key at startup * Feishu: add webhook forgery regression test * Feishu: collect encrypt key during onboarding * Docs: require Feishu webhook encrypt key * Changelog: note Feishu webhook hardening * Docs: clarify Feishu encrypt key screenshot * Feishu: treat webhook encrypt key as secret input * Feishu: resolve encrypt key only in webhook mode
This commit is contained in:
parent
99170e2408
commit
7844bc89a1
|
|
@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
|
|||
- Security/browser.request: block persistent browser profile create/delete routes from write-scoped `browser.request` so callers can no longer persist admin-only browser profile changes through the browser control surface. (`GHSA-vmhq-cqm9-6p7q`)(#43800) Thanks @tdjackey and @vincentkoc.
|
||||
- Security/agent: reject public spawned-run lineage fields and keep workspace inheritance on the internal spawned-session path so external `agent` callers can no longer override the gateway workspace boundary. (`GHSA-2rqg-gjgv-84jm`)(#43801) Thanks @tdjackey and @vincentkoc.
|
||||
- Security/exec allowlist: preserve POSIX case sensitivity and keep `?` within a single path segment so exact-looking allowlist patterns no longer overmatch executables across case or directory boundaries. (`GHSA-f8r2-vg7x-gh8m`)(#43798) Thanks @zpbrent and @vincentkoc.
|
||||
- Security/Feishu webhook: require `encryptKey` alongside `verificationToken` in webhook mode so unsigned forged events are rejected instead of being processed with token-only configuration. (`GHSA-g353-mgv3-8pcj`)(#44087) Thanks @lintsinghua and @vincentkoc.
|
||||
- Security/exec detection: normalize compatibility Unicode and strip invisible formatting code points before obfuscation checks so zero-width and fullwidth command tricks no longer suppress heuristic detection. (`GHSA-9r3v-37xh-2cf6`)(#44091) Thanks @wooluo and @vincentkoc.
|
||||
- Security/WebSocket preauth: shorten unauthenticated handshake retention and reject oversized pre-auth frames before application-layer parsing to reduce pre-pairing exposure on unsupported public deployments. (`GHSA-jv4g-m82p-2j93`)(#44089) (`GHSA-xwx2-ppv2-wx98`)(#44089) Thanks @ez-lbz and @vincentkoc.
|
||||
- Security/Feishu reactions: preserve looked-up group chat typing and fail closed on ambiguous reaction context so group authorization and mention gating cannot be bypassed through synthetic `p2p` reactions. (`GHSA-m69h-jm2f-2pv8`)(#44088) Thanks @zpbrent and @vincentkoc.
|
||||
|
|
|
|||
|
|
@ -193,16 +193,18 @@ Edit `~/.openclaw/openclaw.json`:
|
|||
}
|
||||
```
|
||||
|
||||
If you use `connectionMode: "webhook"`, set `verificationToken`. The Feishu webhook server binds to `127.0.0.1` by default; set `webhookHost` only if you intentionally need a different bind address.
|
||||
If you use `connectionMode: "webhook"`, set both `verificationToken` and `encryptKey`. The Feishu webhook server binds to `127.0.0.1` by default; set `webhookHost` only if you intentionally need a different bind address.
|
||||
|
||||
#### Verification Token (webhook mode)
|
||||
#### Verification Token and Encrypt Key (webhook mode)
|
||||
|
||||
When using webhook mode, set `channels.feishu.verificationToken` in your config. To get the value:
|
||||
When using webhook mode, set both `channels.feishu.verificationToken` and `channels.feishu.encryptKey` in your config. To get the values:
|
||||
|
||||
1. In Feishu Open Platform, open your app
|
||||
2. Go to **Development** → **Events & Callbacks** (开发配置 → 事件与回调)
|
||||
3. Open the **Encryption** tab (加密策略)
|
||||
4. Copy **Verification Token**
|
||||
4. Copy **Verification Token** and **Encrypt Key**
|
||||
|
||||
The screenshot below shows where to find the **Verification Token**. The **Encrypt Key** is listed in the same **Encryption** section.
|
||||
|
||||

|
||||
|
||||
|
|
@ -600,6 +602,7 @@ Key options:
|
|||
| `channels.feishu.connectionMode` | Event transport mode | `websocket` |
|
||||
| `channels.feishu.defaultAccount` | Default account ID for outbound routing | `default` |
|
||||
| `channels.feishu.verificationToken` | Required for webhook mode | - |
|
||||
| `channels.feishu.encryptKey` | Required for webhook mode | - |
|
||||
| `channels.feishu.webhookPath` | Webhook route path | `/feishu/events` |
|
||||
| `channels.feishu.webhookHost` | Webhook bind host | `127.0.0.1` |
|
||||
| `channels.feishu.webhookPort` | Webhook bind port | `3000` |
|
||||
|
|
|
|||
|
|
@ -241,6 +241,25 @@ describe("resolveFeishuCredentials", () => {
|
|||
domain: "feishu",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not resolve encryptKey SecretRefs outside webhook mode", () => {
|
||||
const creds = resolveFeishuCredentials(
|
||||
asConfig({
|
||||
connectionMode: "websocket",
|
||||
appId: "cli_123",
|
||||
appSecret: "secret_456",
|
||||
encryptKey: { source: "file", provider: "default", id: "path/to/secret" } as never,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(creds).toEqual({
|
||||
appId: "cli_123",
|
||||
appSecret: "secret_456", // pragma: allowlist secret
|
||||
encryptKey: undefined,
|
||||
verificationToken: undefined,
|
||||
domain: "feishu",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveFeishuAccount", () => {
|
||||
|
|
|
|||
|
|
@ -169,10 +169,14 @@ export function resolveFeishuCredentials(
|
|||
if (!appId || !appSecret) {
|
||||
return null;
|
||||
}
|
||||
const connectionMode = cfg?.connectionMode ?? "websocket";
|
||||
return {
|
||||
appId,
|
||||
appSecret,
|
||||
encryptKey: normalizeString(cfg?.encryptKey),
|
||||
encryptKey:
|
||||
connectionMode === "webhook"
|
||||
? resolveSecretLike(cfg?.encryptKey, "channels.feishu.encryptKey")
|
||||
: normalizeString(cfg?.encryptKey),
|
||||
verificationToken: resolveSecretLike(
|
||||
cfg?.verificationToken,
|
||||
"channels.feishu.verificationToken",
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
|||
defaultAccount: { type: "string" },
|
||||
appId: { type: "string" },
|
||||
appSecret: secretInputJsonSchema,
|
||||
encryptKey: { type: "string" },
|
||||
encryptKey: secretInputJsonSchema,
|
||||
verificationToken: secretInputJsonSchema,
|
||||
domain: {
|
||||
oneOf: [
|
||||
|
|
@ -170,7 +170,7 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
|||
name: { type: "string" },
|
||||
appId: { type: "string" },
|
||||
appSecret: secretInputJsonSchema,
|
||||
encryptKey: { type: "string" },
|
||||
encryptKey: secretInputJsonSchema,
|
||||
verificationToken: secretInputJsonSchema,
|
||||
domain: { type: "string", enum: ["feishu", "lark"] },
|
||||
connectionMode: { type: "string", enum: ["websocket", "webhook"] },
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ describe("FeishuConfigSchema webhook validation", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("accepts top-level webhook mode with verificationToken", () => {
|
||||
it("rejects top-level webhook mode without encryptKey", () => {
|
||||
const result = FeishuConfigSchema.safeParse({
|
||||
connectionMode: "webhook",
|
||||
verificationToken: "token_top",
|
||||
|
|
@ -55,6 +55,21 @@ describe("FeishuConfigSchema webhook validation", () => {
|
|||
appSecret: "secret_top", // pragma: allowlist secret
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues.some((issue) => issue.path.join(".") === "encryptKey")).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts top-level webhook mode with verificationToken and encryptKey", () => {
|
||||
const result = FeishuConfigSchema.safeParse({
|
||||
connectionMode: "webhook",
|
||||
verificationToken: "token_top",
|
||||
encryptKey: "encrypt_top",
|
||||
appId: "cli_top",
|
||||
appSecret: "secret_top", // pragma: allowlist secret
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
|
|
@ -79,9 +94,30 @@ describe("FeishuConfigSchema webhook validation", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("accepts account webhook mode inheriting top-level verificationToken", () => {
|
||||
it("rejects account webhook mode without encryptKey", () => {
|
||||
const result = FeishuConfigSchema.safeParse({
|
||||
accounts: {
|
||||
main: {
|
||||
connectionMode: "webhook",
|
||||
verificationToken: "token_main",
|
||||
appId: "cli_main",
|
||||
appSecret: "secret_main", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(
|
||||
result.error.issues.some((issue) => issue.path.join(".") === "accounts.main.encryptKey"),
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts account webhook mode inheriting top-level verificationToken and encryptKey", () => {
|
||||
const result = FeishuConfigSchema.safeParse({
|
||||
verificationToken: "token_top",
|
||||
encryptKey: "encrypt_top",
|
||||
accounts: {
|
||||
main: {
|
||||
connectionMode: "webhook",
|
||||
|
|
@ -102,6 +138,31 @@ describe("FeishuConfigSchema webhook validation", () => {
|
|||
provider: "default",
|
||||
id: "FEISHU_VERIFICATION_TOKEN",
|
||||
},
|
||||
encryptKey: "encrypt_top",
|
||||
appId: "cli_top",
|
||||
appSecret: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "FEISHU_APP_SECRET",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts SecretRef encryptKey in webhook mode", () => {
|
||||
const result = FeishuConfigSchema.safeParse({
|
||||
connectionMode: "webhook",
|
||||
verificationToken: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "FEISHU_VERIFICATION_TOKEN",
|
||||
},
|
||||
encryptKey: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "FEISHU_ENCRYPT_KEY",
|
||||
},
|
||||
appId: "cli_top",
|
||||
appSecret: {
|
||||
source: "env",
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ export const FeishuAccountConfigSchema = z
|
|||
name: z.string().optional(), // Display name for this account
|
||||
appId: z.string().optional(),
|
||||
appSecret: buildSecretInputSchema().optional(),
|
||||
encryptKey: z.string().optional(),
|
||||
encryptKey: buildSecretInputSchema().optional(),
|
||||
verificationToken: buildSecretInputSchema().optional(),
|
||||
domain: FeishuDomainSchema.optional(),
|
||||
connectionMode: FeishuConnectionModeSchema.optional(),
|
||||
|
|
@ -204,7 +204,7 @@ export const FeishuConfigSchema = z
|
|||
// Top-level credentials (backward compatible for single-account mode)
|
||||
appId: z.string().optional(),
|
||||
appSecret: buildSecretInputSchema().optional(),
|
||||
encryptKey: z.string().optional(),
|
||||
encryptKey: buildSecretInputSchema().optional(),
|
||||
verificationToken: buildSecretInputSchema().optional(),
|
||||
domain: FeishuDomainSchema.optional().default("feishu"),
|
||||
connectionMode: FeishuConnectionModeSchema.optional().default("websocket"),
|
||||
|
|
@ -240,13 +240,23 @@ export const FeishuConfigSchema = z
|
|||
|
||||
const defaultConnectionMode = value.connectionMode ?? "websocket";
|
||||
const defaultVerificationTokenConfigured = hasConfiguredSecretInput(value.verificationToken);
|
||||
if (defaultConnectionMode === "webhook" && !defaultVerificationTokenConfigured) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["verificationToken"],
|
||||
message:
|
||||
'channels.feishu.connectionMode="webhook" requires channels.feishu.verificationToken',
|
||||
});
|
||||
const defaultEncryptKeyConfigured = hasConfiguredSecretInput(value.encryptKey);
|
||||
if (defaultConnectionMode === "webhook") {
|
||||
if (!defaultVerificationTokenConfigured) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["verificationToken"],
|
||||
message:
|
||||
'channels.feishu.connectionMode="webhook" requires channels.feishu.verificationToken',
|
||||
});
|
||||
}
|
||||
if (!defaultEncryptKeyConfigured) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["encryptKey"],
|
||||
message: 'channels.feishu.connectionMode="webhook" requires channels.feishu.encryptKey',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const [accountId, account] of Object.entries(value.accounts ?? {})) {
|
||||
|
|
@ -259,6 +269,8 @@ export const FeishuConfigSchema = z
|
|||
}
|
||||
const accountVerificationTokenConfigured =
|
||||
hasConfiguredSecretInput(account.verificationToken) || defaultVerificationTokenConfigured;
|
||||
const accountEncryptKeyConfigured =
|
||||
hasConfiguredSecretInput(account.encryptKey) || defaultEncryptKeyConfigured;
|
||||
if (!accountVerificationTokenConfigured) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
|
|
@ -268,6 +280,15 @@ export const FeishuConfigSchema = z
|
|||
"a verificationToken (account-level or top-level)",
|
||||
});
|
||||
}
|
||||
if (!accountEncryptKeyConfigured) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["accounts", accountId, "encryptKey"],
|
||||
message:
|
||||
`channels.feishu.accounts.${accountId}.connectionMode="webhook" requires ` +
|
||||
"an encryptKey (account-level or top-level)",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (value.dmPolicy === "open") {
|
||||
|
|
|
|||
|
|
@ -534,6 +534,9 @@ export async function monitorSingleAccount(params: MonitorSingleAccountParams):
|
|||
if (connectionMode === "webhook" && !account.verificationToken?.trim()) {
|
||||
throw new Error(`Feishu account "${accountId}" webhook mode requires verificationToken`);
|
||||
}
|
||||
if (connectionMode === "webhook" && !account.encryptKey?.trim()) {
|
||||
throw new Error(`Feishu account "${accountId}" webhook mode requires encryptKey`);
|
||||
}
|
||||
|
||||
const warmupCount = await warmupDedupFromDisk(accountId, log);
|
||||
if (warmupCount > 0) {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ function buildConfig(params: {
|
|||
path: string;
|
||||
port: number;
|
||||
verificationToken?: string;
|
||||
encryptKey?: string;
|
||||
}): ClawdbotConfig {
|
||||
return {
|
||||
channels: {
|
||||
|
|
@ -78,6 +79,7 @@ function buildConfig(params: {
|
|||
webhookHost: "127.0.0.1",
|
||||
webhookPort: params.port,
|
||||
webhookPath: params.path,
|
||||
encryptKey: params.encryptKey,
|
||||
verificationToken: params.verificationToken,
|
||||
},
|
||||
},
|
||||
|
|
@ -91,6 +93,7 @@ async function withRunningWebhookMonitor(
|
|||
accountId: string;
|
||||
path: string;
|
||||
verificationToken: string;
|
||||
encryptKey: string;
|
||||
},
|
||||
run: (url: string) => Promise<void>,
|
||||
) {
|
||||
|
|
@ -99,6 +102,7 @@ async function withRunningWebhookMonitor(
|
|||
accountId: params.accountId,
|
||||
path: params.path,
|
||||
port,
|
||||
encryptKey: params.encryptKey,
|
||||
verificationToken: params.verificationToken,
|
||||
});
|
||||
|
||||
|
|
@ -141,6 +145,19 @@ describe("Feishu webhook security hardening", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("rejects webhook mode without encryptKey", async () => {
|
||||
probeFeishuMock.mockResolvedValue({ ok: true, botOpenId: "bot_open_id" });
|
||||
|
||||
const cfg = buildConfig({
|
||||
accountId: "missing-encrypt-key",
|
||||
path: "/hook-missing-encrypt",
|
||||
port: await getFreePort(),
|
||||
verificationToken: "verify_token",
|
||||
});
|
||||
|
||||
await expect(monitorFeishuProvider({ config: cfg })).rejects.toThrow(/requires encryptKey/i);
|
||||
});
|
||||
|
||||
it("returns 415 for POST requests without json content type", async () => {
|
||||
probeFeishuMock.mockResolvedValue({ ok: true, botOpenId: "bot_open_id" });
|
||||
await withRunningWebhookMonitor(
|
||||
|
|
@ -148,6 +165,7 @@ describe("Feishu webhook security hardening", () => {
|
|||
accountId: "content-type",
|
||||
path: "/hook-content-type",
|
||||
verificationToken: "verify_token",
|
||||
encryptKey: "encrypt_key",
|
||||
},
|
||||
async (url) => {
|
||||
const response = await fetch(url, {
|
||||
|
|
@ -169,6 +187,7 @@ describe("Feishu webhook security hardening", () => {
|
|||
accountId: "rate-limit",
|
||||
path: "/hook-rate-limit",
|
||||
verificationToken: "verify_token",
|
||||
encryptKey: "encrypt_key",
|
||||
},
|
||||
async (url) => {
|
||||
let saw429 = false;
|
||||
|
|
|
|||
|
|
@ -370,6 +370,37 @@ export const feishuOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||
},
|
||||
};
|
||||
}
|
||||
const currentEncryptKey = (next.channels?.feishu as FeishuConfig | undefined)?.encryptKey;
|
||||
const encryptKeyPromptState = buildSingleChannelSecretPromptState({
|
||||
accountConfigured: hasConfiguredSecretInput(currentEncryptKey),
|
||||
hasConfigToken: hasConfiguredSecretInput(currentEncryptKey),
|
||||
allowEnv: false,
|
||||
});
|
||||
const encryptKeyResult = await promptSingleChannelSecretInput({
|
||||
cfg: next,
|
||||
prompter,
|
||||
providerHint: "feishu-webhook",
|
||||
credentialLabel: "encrypt key",
|
||||
accountConfigured: encryptKeyPromptState.accountConfigured,
|
||||
canUseEnv: encryptKeyPromptState.canUseEnv,
|
||||
hasConfigToken: encryptKeyPromptState.hasConfigToken,
|
||||
envPrompt: "",
|
||||
keepPrompt: "Feishu encrypt key already configured. Keep it?",
|
||||
inputPrompt: "Enter Feishu encrypt key",
|
||||
preferredEnvVar: "FEISHU_ENCRYPT_KEY",
|
||||
});
|
||||
if (encryptKeyResult.action === "set") {
|
||||
next = {
|
||||
...next,
|
||||
channels: {
|
||||
...next.channels,
|
||||
feishu: {
|
||||
...next.channels?.feishu,
|
||||
encryptKey: encryptKeyResult.value,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
const currentWebhookPath = (next.channels?.feishu as FeishuConfig | undefined)?.webhookPath;
|
||||
const webhookPath = String(
|
||||
await prompter.text({
|
||||
|
|
|
|||
|
|
@ -801,6 +801,31 @@ function collectFeishuAssignments(params: {
|
|||
: baseConnectionMode;
|
||||
return accountMode === "webhook";
|
||||
});
|
||||
const topLevelEncryptKeyActive = !surface.channelEnabled
|
||||
? false
|
||||
: !surface.hasExplicitAccounts
|
||||
? baseConnectionMode === "webhook"
|
||||
: surface.accounts.some(({ account, enabled }) => {
|
||||
if (!enabled || hasOwnProperty(account, "encryptKey")) {
|
||||
return false;
|
||||
}
|
||||
const accountMode = hasOwnProperty(account, "connectionMode")
|
||||
? normalizeSecretStringValue(account.connectionMode)
|
||||
: baseConnectionMode;
|
||||
return accountMode === "webhook";
|
||||
});
|
||||
collectSecretInputAssignment({
|
||||
value: feishu.encryptKey,
|
||||
path: "channels.feishu.encryptKey",
|
||||
expected: "string",
|
||||
defaults: params.defaults,
|
||||
context: params.context,
|
||||
active: topLevelEncryptKeyActive,
|
||||
inactiveReason: "no enabled Feishu webhook-mode surface inherits this top-level encryptKey.",
|
||||
apply: (value) => {
|
||||
feishu.encryptKey = value;
|
||||
},
|
||||
});
|
||||
collectSecretInputAssignment({
|
||||
value: feishu.verificationToken,
|
||||
path: "channels.feishu.verificationToken",
|
||||
|
|
@ -818,6 +843,23 @@ function collectFeishuAssignments(params: {
|
|||
return;
|
||||
}
|
||||
for (const { accountId, account, enabled } of surface.accounts) {
|
||||
if (hasOwnProperty(account, "encryptKey")) {
|
||||
const accountMode = hasOwnProperty(account, "connectionMode")
|
||||
? normalizeSecretStringValue(account.connectionMode)
|
||||
: baseConnectionMode;
|
||||
collectSecretInputAssignment({
|
||||
value: account.encryptKey,
|
||||
path: `channels.feishu.accounts.${accountId}.encryptKey`,
|
||||
expected: "string",
|
||||
defaults: params.defaults,
|
||||
context: params.context,
|
||||
active: enabled && accountMode === "webhook",
|
||||
inactiveReason: "Feishu account is disabled or not running in webhook mode.",
|
||||
apply: (value) => {
|
||||
account.encryptKey = value;
|
||||
},
|
||||
});
|
||||
}
|
||||
if (!hasOwnProperty(account, "verificationToken")) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,9 @@ function buildConfigForOpenClawTarget(entry: SecretRegistryEntry, envId: string)
|
|||
if (entry.id === "channels.feishu.verificationToken") {
|
||||
setPathCreateStrict(config, ["channels", "feishu", "connectionMode"], "webhook");
|
||||
}
|
||||
if (entry.id === "channels.feishu.encryptKey") {
|
||||
setPathCreateStrict(config, ["channels", "feishu", "connectionMode"], "webhook");
|
||||
}
|
||||
if (entry.id === "channels.feishu.accounts.*.verificationToken") {
|
||||
setPathCreateStrict(
|
||||
config,
|
||||
|
|
@ -78,6 +81,13 @@ function buildConfigForOpenClawTarget(entry: SecretRegistryEntry, envId: string)
|
|||
"webhook",
|
||||
);
|
||||
}
|
||||
if (entry.id === "channels.feishu.accounts.*.encryptKey") {
|
||||
setPathCreateStrict(
|
||||
config,
|
||||
["channels", "feishu", "accounts", "sample", "connectionMode"],
|
||||
"webhook",
|
||||
);
|
||||
}
|
||||
if (entry.id === "tools.web.search.gemini.apiKey") {
|
||||
setPathCreateStrict(config, ["tools", "web", "search", "provider"], "gemini");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,6 +173,17 @@ const SECRET_TARGET_REGISTRY: SecretTargetRegistryEntry[] = [
|
|||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
{
|
||||
id: "channels.feishu.accounts.*.encryptKey",
|
||||
targetType: "channels.feishu.accounts.*.encryptKey",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: "channels.feishu.accounts.*.encryptKey",
|
||||
secretShape: SECRET_INPUT_SHAPE,
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
{
|
||||
id: "channels.feishu.accounts.*.verificationToken",
|
||||
targetType: "channels.feishu.accounts.*.verificationToken",
|
||||
|
|
@ -195,6 +206,17 @@ const SECRET_TARGET_REGISTRY: SecretTargetRegistryEntry[] = [
|
|||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
{
|
||||
id: "channels.feishu.encryptKey",
|
||||
targetType: "channels.feishu.encryptKey",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: "channels.feishu.encryptKey",
|
||||
secretShape: SECRET_INPUT_SHAPE,
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
{
|
||||
id: "channels.feishu.verificationToken",
|
||||
targetType: "channels.feishu.verificationToken",
|
||||
|
|
|
|||
Loading…
Reference in New Issue