From 45f1d9cb0f597905c2b0666b6bc3e0f0675f747f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 20 Apr 2026 22:14:45 +0100 Subject: [PATCH] refactor: share feishu security audit contract --- extensions/feishu/security-contract-api.ts | 82 +------------------ .../feishu/src/security-audit-shared.ts | 69 ++++++++++++++++ extensions/feishu/src/security-audit.ts | 61 +------------- 3 files changed, 71 insertions(+), 141 deletions(-) create mode 100644 extensions/feishu/src/security-audit-shared.ts diff --git a/extensions/feishu/security-contract-api.ts b/extensions/feishu/security-contract-api.ts index 9f1d56b25fa..4b008192bcc 100644 --- a/extensions/feishu/security-contract-api.ts +++ b/extensions/feishu/security-contract-api.ts @@ -1,81 +1 @@ -import type { OpenClawConfig } from "./runtime-api.js"; - -function asRecord(value: unknown): Record | undefined { - return value && typeof value === "object" && !Array.isArray(value) - ? (value as Record) - : undefined; -} - -function hasNonEmptyString(value: unknown): boolean { - return typeof value === "string" && value.trim().length > 0; -} - -function hasConfiguredSecretInput(value: unknown): boolean { - if (hasNonEmptyString(value)) { - return true; - } - const record = asRecord(value); - return ( - Boolean(record) && - hasNonEmptyString(record?.source) && - hasNonEmptyString(record?.provider) && - hasNonEmptyString(record?.id) - ); -} - -function isFeishuDocToolEnabled(cfg: OpenClawConfig): boolean { - const channels = asRecord(cfg.channels); - const feishu = asRecord(channels?.feishu); - if (!feishu || feishu.enabled === false) { - return false; - } - - const baseTools = asRecord(feishu.tools); - const baseDocEnabled = baseTools?.doc !== false; - const baseAppId = hasNonEmptyString(feishu.appId); - const baseAppSecret = hasConfiguredSecretInput(feishu.appSecret); - const baseConfigured = baseAppId && baseAppSecret; - - const accounts = asRecord(feishu.accounts); - if (!accounts || Object.keys(accounts).length === 0) { - return baseDocEnabled && baseConfigured; - } - - for (const accountValue of Object.values(accounts)) { - const account = asRecord(accountValue) ?? {}; - if (account.enabled === false) { - continue; - } - const accountTools = asRecord(account.tools); - const effectiveTools = accountTools ?? baseTools; - const docEnabled = effectiveTools?.doc !== false; - if (!docEnabled) { - continue; - } - const accountConfigured = - (hasNonEmptyString(account.appId) || baseAppId) && - (hasConfiguredSecretInput(account.appSecret) || baseAppSecret); - if (accountConfigured) { - return true; - } - } - - return false; -} - -export function collectFeishuSecurityAuditFindings(params: { cfg: OpenClawConfig }) { - if (!isFeishuDocToolEnabled(params.cfg)) { - return []; - } - return [ - { - checkId: "channels.feishu.doc_owner_open_id", - severity: "warn" as const, - title: "Feishu doc create can grant requester permissions", - detail: - 'channels.feishu tools include "doc"; feishu_doc action "create" can grant document access to the trusted requesting Feishu user.', - remediation: - "Disable channels.feishu.tools.doc when not needed, and restrict tool access for untrusted prompts.", - }, - ]; -} +export { collectFeishuSecurityAuditFindings } from "./src/security-audit-shared.js"; diff --git a/extensions/feishu/src/security-audit-shared.ts b/extensions/feishu/src/security-audit-shared.ts new file mode 100644 index 00000000000..af7d7de4ae1 --- /dev/null +++ b/extensions/feishu/src/security-audit-shared.ts @@ -0,0 +1,69 @@ +import { hasConfiguredSecretInput } from "openclaw/plugin-sdk/secret-input"; +import type { OpenClawConfig } from "../runtime-api.js"; + +function asRecord(value: unknown): Record | undefined { + return value && typeof value === "object" && !Array.isArray(value) + ? (value as Record) + : undefined; +} + +function hasNonEmptyString(value: unknown): boolean { + return typeof value === "string" && value.trim().length > 0; +} + +function isFeishuDocToolEnabled(cfg: OpenClawConfig): boolean { + const channels = asRecord(cfg.channels); + const feishu = asRecord(channels?.feishu); + if (!feishu || feishu.enabled === false) { + return false; + } + + const baseTools = asRecord(feishu.tools); + const baseDocEnabled = baseTools?.doc !== false; + const baseAppId = hasNonEmptyString(feishu.appId); + const baseAppSecret = hasConfiguredSecretInput(feishu.appSecret, cfg.secrets?.defaults); + const baseConfigured = baseAppId && baseAppSecret; + + const accounts = asRecord(feishu.accounts); + if (!accounts || Object.keys(accounts).length === 0) { + return baseDocEnabled && baseConfigured; + } + + for (const accountValue of Object.values(accounts)) { + const account = asRecord(accountValue) ?? {}; + if (account.enabled === false) { + continue; + } + const accountTools = asRecord(account.tools); + const effectiveTools = accountTools ?? baseTools; + const docEnabled = effectiveTools?.doc !== false; + if (!docEnabled) { + continue; + } + const accountConfigured = + (hasNonEmptyString(account.appId) || baseAppId) && + (hasConfiguredSecretInput(account.appSecret, cfg.secrets?.defaults) || baseAppSecret); + if (accountConfigured) { + return true; + } + } + + return false; +} + +export function collectFeishuSecurityAuditFindings(params: { cfg: OpenClawConfig }) { + if (!isFeishuDocToolEnabled(params.cfg)) { + return []; + } + return [ + { + checkId: "channels.feishu.doc_owner_open_id", + severity: "warn" as const, + title: "Feishu doc create can grant requester permissions", + detail: + 'channels.feishu tools include "doc"; feishu_doc action "create" can grant document access to the trusted requesting Feishu user.', + remediation: + "Disable channels.feishu.tools.doc when not needed, and restrict tool access for untrusted prompts.", + }, + ]; +} diff --git a/extensions/feishu/src/security-audit.ts b/extensions/feishu/src/security-audit.ts index 77a040212a2..8a5e46964e5 100644 --- a/extensions/feishu/src/security-audit.ts +++ b/extensions/feishu/src/security-audit.ts @@ -1,60 +1 @@ -import { hasConfiguredSecretInput } from "openclaw/plugin-sdk/secret-input"; -import type { OpenClawConfig } from "../runtime-api.js"; -import { asRecord, hasNonEmptyString } from "./comment-shared.js"; - -function isFeishuDocToolEnabled(cfg: OpenClawConfig): boolean { - const channels = asRecord(cfg.channels); - const feishu = asRecord(channels?.feishu); - if (!feishu || feishu.enabled === false) { - return false; - } - - const baseTools = asRecord(feishu.tools); - const baseDocEnabled = baseTools?.doc !== false; - const baseAppId = hasNonEmptyString(feishu.appId); - const baseAppSecret = hasConfiguredSecretInput(feishu.appSecret, cfg.secrets?.defaults); - const baseConfigured = baseAppId && baseAppSecret; - - const accounts = asRecord(feishu.accounts); - if (!accounts || Object.keys(accounts).length === 0) { - return baseDocEnabled && baseConfigured; - } - - for (const accountValue of Object.values(accounts)) { - const account = asRecord(accountValue) ?? {}; - if (account.enabled === false) { - continue; - } - const accountTools = asRecord(account.tools); - const effectiveTools = accountTools ?? baseTools; - const docEnabled = effectiveTools?.doc !== false; - if (!docEnabled) { - continue; - } - const accountConfigured = - (hasNonEmptyString(account.appId) || baseAppId) && - (hasConfiguredSecretInput(account.appSecret, cfg.secrets?.defaults) || baseAppSecret); - if (accountConfigured) { - return true; - } - } - - return false; -} - -export function collectFeishuSecurityAuditFindings(params: { cfg: OpenClawConfig }) { - if (!isFeishuDocToolEnabled(params.cfg)) { - return []; - } - return [ - { - checkId: "channels.feishu.doc_owner_open_id", - severity: "warn" as const, - title: "Feishu doc create can grant requester permissions", - detail: - 'channels.feishu tools include "doc"; feishu_doc action "create" can grant document access to the trusted requesting Feishu user.', - remediation: - "Disable channels.feishu.tools.doc when not needed, and restrict tool access for untrusted prompts.", - }, - ]; -} +export { collectFeishuSecurityAuditFindings } from "./security-audit-shared.js";