Files
john 4e21ca937a
Deploy Documentation / deploy (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
CI / H5 Plaza Tests and Build (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Add TKMind platform extensions, H5/MindSpace stack, and deployment tooling.
Fork goose with custom MCP widgets, platform extensions (aider, git, web, search),
MindSpace H5 backend/frontend, Plaza/Ops UIs, and deploy scripts for tkmind.cn.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 21:30:20 +08:00

197 lines
6.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** @typedef {'auto' | 'approve' | 'smart_approve' | 'chat'} GooseMode */
/** @typedef {'readwrite' | 'readonly'} WorkspaceAccess */
/** @typedef {'allow' | 'deny'} NetworkEgress */
export const POLICY_CATALOG = [
{
key: 'goose_mode',
label: 'TKMind 模式',
description:
'H5 终端用户不在聊天里点「允许/拒绝」;可用工具由「能力权限」决定。chat 不调用工具,auto 在授权范围内自动执行。',
type: 'select',
options: [
{ value: 'chat', label: '仅聊天 (chat)' },
{ value: 'auto', label: '自动执行 (auto)' },
],
defaultValue: 'chat',
category: 'goose',
risk: 'high',
},
{
key: 'workspace_access',
label: '工作区访问',
description: 'readonly 时禁止 shell 与文件写入,仅允许浏览与分析',
type: 'select',
options: [
{ value: 'readwrite', label: '读写' },
{ value: 'readonly', label: '只读' },
],
defaultValue: 'readwrite',
category: 'workspace',
risk: 'medium',
},
{
key: 'network_egress',
label: '网络出站',
description: 'deny 时禁用 shell、电脑控制、沙箱脚本等可能访问网络的扩展',
type: 'select',
options: [
{ value: 'deny', label: '禁止(推荐)' },
{ value: 'allow', label: '允许(需同时开启对应能力)' },
],
defaultValue: 'deny',
category: 'network',
risk: 'high',
},
{
key: 'api_lockdown',
label: 'API 代理锁定',
description: '开启后仅允许聊天相关 API,拦截改模式、加扩展、写配置等危险请求',
type: 'boolean',
defaultValue: true,
category: 'proxy',
risk: 'medium',
},
];
export const DEFAULT_USER_POLICIES = Object.fromEntries(
POLICY_CATALOG.map((item) => [item.key, item.defaultValue]),
);
const POLICY_KEYS = new Set(POLICY_CATALOG.map((item) => item.key));
const GOOSE_MODES = new Set(['auto', 'approve', 'smart_approve', 'chat']);
const WORKSPACE_ACCESS = new Set(['readwrite', 'readonly']);
const NETWORK_EGRESS = new Set(['allow', 'deny']);
export function policyKeys() {
return [...POLICY_KEYS];
}
export function isValidPolicyKey(key) {
return POLICY_KEYS.has(key);
}
export function normalizePolicyPatch(patch) {
const normalized = {};
for (const [key, raw] of Object.entries(patch ?? {})) {
if (!isValidPolicyKey(key)) continue;
const def = POLICY_CATALOG.find((item) => item.key === key);
if (!def) continue;
if (def.type === 'boolean') {
normalized[key] = raw === true || raw === 'true' || raw === 1 || raw === '1';
continue;
}
const value = String(raw ?? '').trim();
if (def.key === 'goose_mode' && GOOSE_MODES.has(value)) normalized[key] = value;
if (def.key === 'workspace_access' && WORKSPACE_ACCESS.has(value)) normalized[key] = value;
if (def.key === 'network_egress' && NETWORK_EGRESS.has(value)) normalized[key] = value;
}
return normalized;
}
export function resolvePolicies(rolePolicies, userOverrides) {
const resolved = { ...DEFAULT_USER_POLICIES };
for (const [key, value] of Object.entries(rolePolicies ?? {})) {
if (isValidPolicyKey(key)) resolved[key] = value;
}
for (const [key, value] of Object.entries(userOverrides ?? {})) {
if (isValidPolicyKey(key)) resolved[key] = value;
}
if (resolved.goose_mode === 'approve' || resolved.goose_mode === 'smart_approve') {
resolved.goose_mode = 'auto';
}
return resolved;
}
function hasExecutableTools(capabilities) {
return Boolean(
capabilities?.static_publish
|| capabilities?.shell
|| capabilities?.filesystem
|| capabilities?.code_browse
|| capabilities?.subagent
|| capabilities?.code_sandbox
|| capabilities?.computer
|| capabilities?.charts
|| capabilities?.aider
|| capabilities?.apps
|| capabilities?.todo
|| capabilities?.skills
|| capabilities?.chat_recall,
);
}
/**
* Goose mode for H5 agent sessions. Tool allow/deny is enforced via capability grants,
* not end-user confirmation dialogs.
*/
export function resolveAgentGooseMode(capabilities, policies) {
const requested = policies?.goose_mode ?? DEFAULT_USER_POLICIES.goose_mode;
if (!hasExecutableTools(capabilities)) {
return 'chat';
}
if (requested === 'chat' || requested === 'approve' || requested === 'smart_approve') {
return 'auto';
}
return 'auto';
}
/**
* Apply security policies on top of capability flags before building extension overrides.
*/
export function applyPoliciesToCapabilities(capabilities, policies) {
const effective = { ...capabilities };
if (policies.workspace_access === 'readonly') {
effective.shell = false;
effective.filesystem = false;
effective.static_publish = false;
effective.aider = false;
effective.code_sandbox = false;
effective.image_read = false;
}
if (policies.network_egress === 'deny') {
// static_publish 用户的 shell 仅能在本工作区内执行本地命令,不依赖外网出站
if (!effective.static_publish) {
effective.shell = false;
}
effective.computer = false;
effective.code_sandbox = false;
}
return effective;
}
const USER_API_ALLOWLIST = [
{ method: 'GET', pattern: /^\/status$/ },
{ method: 'POST', pattern: /^\/agent\/start$/ },
{ method: 'POST', pattern: /^\/agent\/resume$/ },
{ method: 'GET', pattern: /^\/sessions$/ },
{ method: 'GET', pattern: /^\/sessions\/[^/]+$/ },
{ method: 'GET', pattern: /^\/sessions\/[^/]+\/events$/ },
{ method: 'POST', pattern: /^\/sessions\/[^/]+\/reply$/ },
{ method: 'POST', pattern: /^\/sessions\/[^/]+\/cancel$/ },
{ method: 'POST', pattern: /^\/action-required\/tool-confirmation$/ },
{ method: 'POST', pattern: /^\/agent\/update_provider$/ },
{ method: 'POST', pattern: /^\/config\/read$/ },
{ method: 'POST', pattern: /^\/agent\/harness_bootstrap$/ },
{ method: 'POST', pattern: /^\/agent\/harness_remember$/ },
];
/**
* @returns {{ allowed: boolean, reason?: string }}
*/
export function evaluateProxyRequest(method, pathname, policies, { unrestricted = false } = {}) {
if (unrestricted) return { allowed: true };
if (!policies.api_lockdown) return { allowed: true };
const upper = method.toUpperCase();
const allowed = USER_API_ALLOWLIST.some(
(rule) => rule.method === upper && rule.pattern.test(pathname),
);
if (allowed) return { allowed: true };
return {
allowed: false,
reason: `策略已锁定 API${upper} ${pathname}`,
};
}