@lobehub/lobehub 2.0.0-next.93 → 2.0.0-next.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,335 @@
1
+ import type { SecurityBlacklistConfig } from '@lobechat/types';
2
+
3
+ /**
4
+ * Default Security Blacklist
5
+ * These rules will ALWAYS block execution and require human intervention,
6
+ * regardless of user settings (even in auto-run mode)
7
+ *
8
+ * This is the last line of defense against dangerous operations
9
+ */
10
+ export const DEFAULT_SECURITY_BLACKLIST: SecurityBlacklistConfig = [
11
+ // ==================== File System Dangers ====================
12
+ {
13
+ description: 'Recursive deletion of home directory is extremely dangerous',
14
+ match: {
15
+ command: {
16
+ pattern: 'rm.*-r.*(~|\\$HOME|/Users/[^/]+|/home/[^/]+)/?\\s*$',
17
+ type: 'regex',
18
+ },
19
+ },
20
+ },
21
+ {
22
+ description: 'Recursive deletion of root directory will destroy the system',
23
+ match: {
24
+ command: {
25
+ pattern: 'rm.*-r.*/\\s*$',
26
+ type: 'regex',
27
+ },
28
+ },
29
+ },
30
+ {
31
+ description: 'Force recursive deletion without specific target is too dangerous',
32
+ match: {
33
+ command: {
34
+ pattern: 'rm\\s+-rf\\s+[~./]\\s*$',
35
+ type: 'regex',
36
+ },
37
+ },
38
+ },
39
+
40
+ // ==================== System Configuration Dangers ====================
41
+ {
42
+ description: 'Modifying /etc/passwd could lock you out of the system',
43
+ match: {
44
+ command: {
45
+ pattern: '.*(/etc/passwd|/etc/shadow).*',
46
+ type: 'regex',
47
+ },
48
+ },
49
+ },
50
+ {
51
+ description: 'Modifying sudoers file without proper validation is dangerous',
52
+ match: {
53
+ command: {
54
+ pattern: '.*/etc/sudoers.*',
55
+ type: 'regex',
56
+ },
57
+ },
58
+ },
59
+
60
+ // ==================== Dangerous Commands ====================
61
+ {
62
+ description: 'Fork bomb can crash the system',
63
+ match: {
64
+ command: {
65
+ pattern: '.*:\\(\\).*\\{.*\\|.*&.*\\};.*:.*',
66
+ type: 'regex',
67
+ },
68
+ },
69
+ },
70
+ {
71
+ description: 'Writing random data to disk devices can destroy data',
72
+ match: {
73
+ command: {
74
+ pattern: 'dd.*of=/dev/(sd|hd|nvme).*',
75
+ type: 'regex',
76
+ },
77
+ },
78
+ },
79
+ {
80
+ description: 'Formatting system partitions will destroy data',
81
+ match: {
82
+ command: {
83
+ pattern: '(mkfs|fdisk|parted).*(/dev/(sd|hd|nvme)|/)',
84
+ type: 'regex',
85
+ },
86
+ },
87
+ },
88
+
89
+ // ==================== Network & Remote Access Dangers ====================
90
+ {
91
+ description: 'Disabling firewall exposes system to attacks',
92
+ match: {
93
+ command: {
94
+ pattern: '(ufw\\s+disable|iptables\\s+-F|systemctl\\s+stop\\s+firewalld)',
95
+ type: 'regex',
96
+ },
97
+ },
98
+ },
99
+ {
100
+ description: 'Changing SSH configuration could lock you out',
101
+ match: {
102
+ command: {
103
+ pattern: '.*(/etc/ssh/sshd_config).*',
104
+ type: 'regex',
105
+ },
106
+ },
107
+ },
108
+
109
+ // ==================== Package Manager Dangers ====================
110
+ {
111
+ description: 'Removing essential system packages can break the system',
112
+ match: {
113
+ command: {
114
+ pattern: '(apt|yum|dnf|pacman)\\s+(remove|purge|erase).*(systemd|kernel|glibc|bash|sudo)',
115
+ type: 'regex',
116
+ },
117
+ },
118
+ },
119
+
120
+ // ==================== Kernel & System Core Dangers ====================
121
+ {
122
+ description: 'Modifying kernel parameters without understanding can crash the system',
123
+ match: {
124
+ command: {
125
+ pattern: 'echo.*>/proc/sys/.*',
126
+ type: 'regex',
127
+ },
128
+ },
129
+ },
130
+ {
131
+ description: 'Direct memory access is extremely dangerous',
132
+ match: {
133
+ command: {
134
+ pattern: '.*(/dev/(mem|kmem|port)).*',
135
+ type: 'regex',
136
+ },
137
+ },
138
+ },
139
+
140
+ // ==================== Privilege Escalation Dangers ====================
141
+ {
142
+ description: 'Changing file ownership of system directories is dangerous',
143
+ match: {
144
+ command: {
145
+ pattern: 'chown.*-R.*(/(etc|bin|sbin|usr|var|sys|proc)|~).*',
146
+ type: 'regex',
147
+ },
148
+ },
149
+ },
150
+ {
151
+ description: 'Setting SUID on shells or interpreters is a security risk',
152
+ match: {
153
+ command: {
154
+ pattern: 'chmod.*(4755|u\\+s).*(sh|bash|python|perl|ruby|node)',
155
+ type: 'regex',
156
+ },
157
+ },
158
+ },
159
+
160
+ // ==================== Sensitive Information Leakage ====================
161
+ {
162
+ description: 'Reading .env files may leak sensitive credentials and API keys',
163
+ match: {
164
+ command: {
165
+ pattern: '(cat|less|more|head|tail|vim|nano|vi|emacs|code).*\\.env.*',
166
+ type: 'regex',
167
+ },
168
+ },
169
+ },
170
+ {
171
+ description: 'Reading .env files may leak sensitive credentials and API keys',
172
+ match: {
173
+ path: {
174
+ pattern: '.*\\.env.*',
175
+ type: 'regex',
176
+ },
177
+ },
178
+ },
179
+ {
180
+ description: 'Reading SSH private keys can compromise system security',
181
+ match: {
182
+ command: {
183
+ pattern:
184
+ '(cat|less|more|head|tail|vim|nano|vi|emacs|code).*(id_rsa|id_ed25519|id_ecdsa)(?!\\.pub).*',
185
+ type: 'regex',
186
+ },
187
+ },
188
+ },
189
+ {
190
+ description: 'Reading SSH private keys can compromise system security',
191
+ match: {
192
+ path: {
193
+ pattern: '.*/\\.ssh/(id_rsa|id_ed25519|id_ecdsa)$',
194
+ type: 'regex',
195
+ },
196
+ },
197
+ },
198
+ {
199
+ description: 'Accessing AWS credentials can leak cloud access keys',
200
+ match: {
201
+ command: {
202
+ pattern: '(cat|less|more|head|tail|vim|nano|vi|emacs|code).*/\\.aws/credentials.*',
203
+ type: 'regex',
204
+ },
205
+ },
206
+ },
207
+ {
208
+ description: 'Accessing AWS credentials can leak cloud access keys',
209
+ match: {
210
+ path: {
211
+ pattern: '.*/\\.aws/credentials.*',
212
+ type: 'regex',
213
+ },
214
+ },
215
+ },
216
+ {
217
+ description: 'Reading Docker config may expose registry credentials',
218
+ match: {
219
+ command: {
220
+ pattern: '(cat|less|more|head|tail|vim|nano|vi|emacs|code).*/\\.docker/config\\.json.*',
221
+ type: 'regex',
222
+ },
223
+ },
224
+ },
225
+ {
226
+ description: 'Reading Docker config may expose registry credentials',
227
+ match: {
228
+ path: {
229
+ pattern: '.*/\\.docker/config\\.json$',
230
+ type: 'regex',
231
+ },
232
+ },
233
+ },
234
+ {
235
+ description: 'Reading Kubernetes config may expose cluster credentials',
236
+ match: {
237
+ command: {
238
+ pattern: '(cat|less|more|head|tail|vim|nano|vi|emacs|code).*/\\.kube/config.*',
239
+ type: 'regex',
240
+ },
241
+ },
242
+ },
243
+ {
244
+ description: 'Reading Kubernetes config may expose cluster credentials',
245
+ match: {
246
+ path: {
247
+ pattern: '.*/\\.kube/config$',
248
+ type: 'regex',
249
+ },
250
+ },
251
+ },
252
+ {
253
+ description: 'Reading Git credentials file may leak access tokens',
254
+ match: {
255
+ command: {
256
+ pattern: '(cat|less|more|head|tail|vim|nano|vi|emacs|code).*/\\.git-credentials.*',
257
+ type: 'regex',
258
+ },
259
+ },
260
+ },
261
+ {
262
+ description: 'Reading Git credentials file may leak access tokens',
263
+ match: {
264
+ path: {
265
+ pattern: '.*/\\.git-credentials$',
266
+ type: 'regex',
267
+ },
268
+ },
269
+ },
270
+ {
271
+ description: 'Reading npm token file may expose package registry credentials',
272
+ match: {
273
+ command: {
274
+ pattern: '(cat|less|more|head|tail|vim|nano|vi|emacs|code).*/\\.npmrc.*',
275
+ type: 'regex',
276
+ },
277
+ },
278
+ },
279
+ {
280
+ description: 'Reading npm token file may expose package registry credentials',
281
+ match: {
282
+ path: {
283
+ pattern: '.*/\\.npmrc$',
284
+ type: 'regex',
285
+ },
286
+ },
287
+ },
288
+ {
289
+ description: 'Reading history files may expose sensitive commands and credentials',
290
+ match: {
291
+ command: {
292
+ pattern:
293
+ '(cat|less|more|head|tail|vim|nano|vi|emacs|code).*/\\.(bash_history|zsh_history|history).*',
294
+ type: 'regex',
295
+ },
296
+ },
297
+ },
298
+ {
299
+ description: 'Reading history files may expose sensitive commands and credentials',
300
+ match: {
301
+ path: {
302
+ pattern: '.*/\\.(bash_history|zsh_history|history)$',
303
+ type: 'regex',
304
+ },
305
+ },
306
+ },
307
+ {
308
+ description: 'Accessing browser credential storage may leak passwords',
309
+ match: {
310
+ command: {
311
+ pattern:
312
+ '(cat|less|more|head|tail|vim|nano|vi|emacs|code).*(Cookies|Login Data|Web Data).*',
313
+ type: 'regex',
314
+ },
315
+ },
316
+ },
317
+ {
318
+ description: 'Reading GCP credentials may leak cloud service account keys',
319
+ match: {
320
+ command: {
321
+ pattern: '(cat|less|more|head|tail|vim|nano|vi|emacs|code).*/\\.config/gcloud/.*\\.json.*',
322
+ type: 'regex',
323
+ },
324
+ },
325
+ },
326
+ {
327
+ description: 'Reading GCP credentials may leak cloud service account keys',
328
+ match: {
329
+ path: {
330
+ pattern: '.*/\\.config/gcloud/.*\\.json$',
331
+ type: 'regex',
332
+ },
333
+ },
334
+ },
335
+ ];
@@ -1,3 +1,4 @@
1
+ export * from './defaultSecurityBlacklist';
1
2
  export * from './InterventionChecker';
2
3
  export * from './runtime';
3
4
  export * from './UsageCounter';
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
- import { ChatToolPayload, UserInterventionConfig } from '@lobechat/types';
2
+ import { ChatToolPayload, SecurityBlacklistConfig, UserInterventionConfig } from '@lobechat/types';
3
3
 
4
4
  import type { Cost, CostLimit, Usage } from './usage';
5
5
 
@@ -23,6 +23,15 @@ export interface AgentState {
23
23
  * Controls how tools requiring approval are handled
24
24
  */
25
25
  userInterventionConfig?: UserInterventionConfig;
26
+
27
+ /**
28
+ * Security blacklist configuration
29
+ * These rules will ALWAYS block execution and require human intervention,
30
+ * regardless of user settings (even in auto-run mode).
31
+ * If not provided, DEFAULT_SECURITY_BLACKLIST will be used.
32
+ */
33
+ securityBlacklist?: SecurityBlacklistConfig;
34
+
26
35
  // --- Execution Tracking ---
27
36
  /**
28
37
  * Number of execution steps in this session.
@@ -6,13 +6,94 @@ const xaiChatModels: AIChatModelCard[] = [
6
6
  abilities: {
7
7
  functionCall: true,
8
8
  search: true,
9
+ structuredOutput: true,
9
10
  vision: true,
10
11
  },
11
12
  contextWindowTokens: 2_000_000,
12
- description:
13
- '我们很高兴发布 Grok 4 Fast,这是我们在成本效益推理模型方面的最新进展。',
14
- displayName: 'Grok 4 Fast (Non-Reasoning)',
13
+ description: '前沿多模态模型,专门针对高性能代理工具调用进行优化。',
14
+ displayName: 'Grok 4.1 Fast (Non-Reasoning)',
15
+ enabled: true,
16
+ id: 'grok-4-1-fast-non-reasoning',
17
+ pricing: {
18
+ units: [
19
+ { name: 'textInput_cacheRead', rate: 0.05, strategy: 'fixed', unit: 'millionTokens' },
20
+ {
21
+ name: 'textInput',
22
+ strategy: 'tiered',
23
+ tiers: [
24
+ { rate: 0.2, upTo: 0.128 },
25
+ { rate: 0.4, upTo: 'infinity' },
26
+ ],
27
+ unit: 'millionTokens',
28
+ },
29
+ {
30
+ name: 'textOutput',
31
+ strategy: 'tiered',
32
+ tiers: [
33
+ { rate: 0.5, upTo: 0.128 },
34
+ { rate: 1, upTo: 'infinity' },
35
+ ],
36
+ unit: 'millionTokens',
37
+ },
38
+ ],
39
+ },
40
+ releasedAt: '2025-11-20',
41
+ settings: {
42
+ searchImpl: 'params',
43
+ },
44
+ type: 'chat',
45
+ },
46
+ {
47
+ abilities: {
48
+ functionCall: true,
49
+ reasoning: true,
50
+ search: true,
51
+ structuredOutput: true,
52
+ vision: true,
53
+ },
54
+ contextWindowTokens: 2_000_000,
55
+ description: '前沿多模态模型,专门针对高性能代理工具调用进行优化。',
56
+ displayName: 'Grok 4.1 Fast',
15
57
  enabled: true,
58
+ id: 'grok-4-1-fast-reasoning',
59
+ pricing: {
60
+ units: [
61
+ { name: 'textInput_cacheRead', rate: 0.05, strategy: 'fixed', unit: 'millionTokens' },
62
+ {
63
+ name: 'textInput',
64
+ strategy: 'tiered',
65
+ tiers: [
66
+ { rate: 0.2, upTo: 0.128 },
67
+ { rate: 0.4, upTo: 'infinity' },
68
+ ],
69
+ unit: 'millionTokens',
70
+ },
71
+ {
72
+ name: 'textOutput',
73
+ strategy: 'tiered',
74
+ tiers: [
75
+ { rate: 0.5, upTo: 0.128 },
76
+ { rate: 1, upTo: 'infinity' },
77
+ ],
78
+ unit: 'millionTokens',
79
+ },
80
+ ],
81
+ },
82
+ releasedAt: '2025-11-20',
83
+ settings: {
84
+ searchImpl: 'params',
85
+ },
86
+ type: 'chat',
87
+ },
88
+ {
89
+ abilities: {
90
+ functionCall: true,
91
+ search: true,
92
+ vision: true,
93
+ },
94
+ contextWindowTokens: 2_000_000,
95
+ description: '我们很高兴发布 Grok 4 Fast,这是我们在成本效益推理模型方面的最新进展。',
96
+ displayName: 'Grok 4 Fast (Non-Reasoning)',
16
97
  id: 'grok-4-fast-non-reasoning',
17
98
  pricing: {
18
99
  units: [
@@ -51,10 +132,8 @@ const xaiChatModels: AIChatModelCard[] = [
51
132
  vision: true,
52
133
  },
53
134
  contextWindowTokens: 2_000_000,
54
- description:
55
- '我们很高兴发布 Grok 4 Fast,这是我们在成本效益推理模型方面的最新进展。',
135
+ description: '我们很高兴发布 Grok 4 Fast,这是我们在成本效益推理模型方面的最新进展。',
56
136
  displayName: 'Grok 4 Fast',
57
- enabled: true,
58
137
  id: 'grok-4-fast-reasoning',
59
138
  pricing: {
60
139
  units: [
@@ -146,6 +146,36 @@ export const UserInterventionConfigSchema = z.object({
146
146
  approvalMode: z.enum(['auto-run', 'allow-list', 'manual']),
147
147
  });
148
148
 
149
+ /**
150
+ * Security Blacklist Rule
151
+ * Used to forcefully block dangerous operations regardless of user settings
152
+ */
153
+ export interface SecurityBlacklistRule {
154
+ /**
155
+ * Description of why this rule exists (for error messages)
156
+ */
157
+ description: string;
158
+
159
+ /**
160
+ * Parameter filter - matches against tool call arguments
161
+ * Same format as HumanInterventionRule.match
162
+ */
163
+ match: Record<string, ArgumentMatcher>;
164
+ }
165
+
166
+ export const SecurityBlacklistRuleSchema = z.object({
167
+ description: z.string(),
168
+ match: z.record(z.string(), ArgumentMatcherSchema),
169
+ });
170
+
171
+ /**
172
+ * Security Blacklist Configuration
173
+ * A list of rules that will always block execution and require intervention
174
+ */
175
+ export type SecurityBlacklistConfig = SecurityBlacklistRule[];
176
+
177
+ export const SecurityBlacklistConfigSchema = z.array(SecurityBlacklistRuleSchema);
178
+
149
179
  /**
150
180
  * Parameters for shouldIntervene method
151
181
  */
@@ -161,6 +191,13 @@ export interface ShouldInterveneParams {
161
191
  */
162
192
  confirmedHistory?: string[];
163
193
 
194
+ /**
195
+ * Security blacklist rules that will be checked first
196
+ * These rules override all other settings including auto-run mode
197
+ * @default []
198
+ */
199
+ securityBlacklist?: SecurityBlacklistConfig;
200
+
164
201
  /**
165
202
  * Tool call arguments to check against rules
166
203
  * @default {}
@@ -177,6 +214,7 @@ export interface ShouldInterveneParams {
177
214
  export const ShouldInterveneParamsSchema = z.object({
178
215
  config: HumanInterventionConfigSchema.optional(),
179
216
  confirmedHistory: z.array(z.string()).optional(),
217
+ securityBlacklist: SecurityBlacklistConfigSchema.optional(),
180
218
  toolArgs: z.record(z.string(), z.any()).optional(),
181
219
  toolKey: z.string().optional(),
182
220
  });
@@ -249,4 +249,29 @@ describe('createRemarkSelfClosingTagPlugin', () => {
249
249
 
250
250
  expect(tree).toMatchSnapshot();
251
251
  });
252
+
253
+ it('should handle tags wrapped in backticks (code)', () => {
254
+ const markdown = `Use this file: \`<${tagName} name="config.json" path="/app/config.json" />\` in your code.`;
255
+ const tree = processMarkdown(markdown, tagName);
256
+
257
+ expect(tree.children).toHaveLength(1);
258
+ expect(tree.children[0].type).toBe('paragraph');
259
+
260
+ const paragraphChildren = tree.children[0].children;
261
+ expect(paragraphChildren).toHaveLength(3);
262
+
263
+ expect(paragraphChildren[0].type).toBe('text');
264
+ expect(paragraphChildren[0].value).toBe('Use this file: ');
265
+
266
+ // The tag should be parsed even inside backticks
267
+ const tagNode = paragraphChildren[1];
268
+ expect(tagNode.type).toBe(tagName);
269
+ expect(tagNode.data?.hProperties).toEqual({
270
+ name: 'config.json',
271
+ path: '/app/config.json',
272
+ });
273
+
274
+ expect(paragraphChildren[2].type).toBe('text');
275
+ expect(paragraphChildren[2].value).toBe(' in your code.');
276
+ });
252
277
  });
@@ -130,5 +130,33 @@ export const createRemarkSelfClosingTagPlugin =
130
130
  return [SKIP, index + newChildren.length]; // Skip new nodes
131
131
  }
132
132
  });
133
+
134
+ // 3. Visit inlineCode nodes (backtick-wrapped tags like `<localFile ... />`)
135
+ // @ts-ignore
136
+ visit(tree, 'inlineCode', (node: any, index: number, parent) => {
137
+ log('>>> Visiting inlineCode node: "%s"', node.value);
138
+
139
+ if (!parent || typeof index !== 'number' || !node.value?.includes(`<${tagName}`)) {
140
+ return;
141
+ }
142
+
143
+ const match = node.value.match(exactTagRegex);
144
+ if (match) {
145
+ const [, attributesString] = match;
146
+ const properties = attributesString ? parseAttributes(attributesString.trim()) : {};
147
+
148
+ const newNode = {
149
+ data: {
150
+ hName: tagName,
151
+ hProperties: properties,
152
+ },
153
+ type: tagName,
154
+ };
155
+
156
+ log('Replacing inlineCode node at index %d with %s node: %o', index, tagName, newNode);
157
+ parent.children.splice(index, 1, newNode);
158
+ return [SKIP, index + 1];
159
+ }
160
+ });
133
161
  };
134
162
  };
@@ -2,11 +2,10 @@ import { ActionIcon, Icon } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
3
  import type { ItemType } from 'antd/es/menu/interface';
4
4
  import { LucideArrowRight, LucideBolt } from 'lucide-react';
5
- import Link from 'next/link';
6
- import { useRouter } from 'next/navigation';
7
5
  import { type ReactNode, memo, useMemo } from 'react';
8
6
  import { useTranslation } from 'react-i18next';
9
7
  import { Flexbox } from 'react-layout-kit';
8
+ import { useNavigate } from 'react-router-dom';
10
9
 
11
10
  import { ModelItemRender, ProviderItemRender } from '@/components/ModelSelect';
12
11
  import ActionDropdown from '@/features/ChatInput/ActionBar/components/ActionDropdown';
@@ -53,7 +52,7 @@ const ModelSwitchPanel = memo<IProps>(({ children, onOpenChange, open }) => {
53
52
  agentSelectors.currentAgentModelProvider(s),
54
53
  s.updateAgentConfig,
55
54
  ]);
56
- const router = useRouter();
55
+ const navigate = useNavigate();
57
56
  const enabledList = useEnabledChatModels();
58
57
 
59
58
  const items = useMemo<ItemType[]>(() => {
@@ -78,7 +77,7 @@ const ModelSwitchPanel = memo<IProps>(({ children, onOpenChange, open }) => {
78
77
  </Flexbox>
79
78
  ),
80
79
  onClick: () => {
81
- router.push(`/settings?active=provider&provider=${provider.id}`);
80
+ navigate(`/settings?active=provider&provider=${provider.id}`);
82
81
  },
83
82
  },
84
83
  ];
@@ -97,7 +96,7 @@ const ModelSwitchPanel = memo<IProps>(({ children, onOpenChange, open }) => {
97
96
  </Flexbox>
98
97
  ),
99
98
  onClick: () => {
100
- router.push('/settings?active=provider');
99
+ navigate('/settings?active=provider');
101
100
  },
102
101
  },
103
102
  ];
@@ -114,18 +113,21 @@ const ModelSwitchPanel = memo<IProps>(({ children, onOpenChange, open }) => {
114
113
  provider={provider.id}
115
114
  source={provider.source}
116
115
  />
117
- <Link href={`/settings?active=provider&provider=${provider.id}`}>
118
- <ActionIcon
119
- icon={LucideBolt}
120
- size={'small'}
121
- title={t('ModelSwitchPanel.goToSettings')}
122
- />
123
- </Link>
116
+ <ActionIcon
117
+ icon={LucideBolt}
118
+ onClick={(e) => {
119
+ e.preventDefault();
120
+ e.stopPropagation();
121
+ navigate(`/settings?active=provider&provider=${provider.id}`);
122
+ }}
123
+ size={'small'}
124
+ title={t('ModelSwitchPanel.goToSettings')}
125
+ />
124
126
  </Flexbox>
125
127
  ),
126
128
  type: 'group',
127
129
  }));
128
- }, [enabledList]);
130
+ }, [enabledList, navigate, t, theme.colorTextTertiary]);
129
131
 
130
132
  const icon = <div className={styles.tag}>{children}</div>;
131
133