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:
Vincent Koc
2026-03-12 11:01:00 -04:00
committed by GitHub
parent 99170e2408
commit 7844bc89a1
13 changed files with 254 additions and 18 deletions

View File

@@ -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;
}

View File

@@ -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");
}

View File

@@ -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",