@atlisp/mcp 1.8.20 → 1.8.23
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.
- package/dist/atlisp-mcp.js +657 -121
- package/dist/cad-worker.js +6 -3
- package/dist/config.js +6 -0
- package/dist/handlers/debug-handlers.js +126 -0
- package/dist/lisp-security.js +66 -12
- package/package.json +1 -1
package/dist/cad-worker.js
CHANGED
|
@@ -579,9 +579,12 @@ async function handleMessage(msg) {
|
|
|
579
579
|
if (!parenCheck.valid) {
|
|
580
580
|
return { success: false, error: `括号错误: ${parenCheck.error} (位置 ${parenCheck.pos})` };
|
|
581
581
|
}
|
|
582
|
-
const
|
|
583
|
-
if (
|
|
584
|
-
|
|
582
|
+
const securityLevel = process.env.SECURITY_LEVEL || 'strict';
|
|
583
|
+
if (securityLevel !== 'relaxed') {
|
|
584
|
+
const injectionCheck = validateLispCodeWorker(msg.code);
|
|
585
|
+
if (!injectionCheck.valid) {
|
|
586
|
+
return { success: false, error: `安全限制: ${injectionCheck.error}` };
|
|
587
|
+
}
|
|
585
588
|
}
|
|
586
589
|
if (msg.code.length > 255) {
|
|
587
590
|
const cacheEnabled = process.env.LONG_CODE_CACHE === '1';
|
package/dist/config.js
CHANGED
|
@@ -38,6 +38,7 @@ const envSchema = z.object({
|
|
|
38
38
|
LINT_CONFIG_PATH: z.string().optional().default(''),
|
|
39
39
|
LINT_LEVEL: z.string().optional().default(''),
|
|
40
40
|
NO_LINT: z.string().optional().default(''),
|
|
41
|
+
SECURITY_LEVEL: z.string().optional().default('strict'),
|
|
41
42
|
});
|
|
42
43
|
|
|
43
44
|
const configSchema = z.object({
|
|
@@ -81,6 +82,7 @@ const configSchema = z.object({
|
|
|
81
82
|
lintConfigPath: z.string().optional(),
|
|
82
83
|
lintLevel: z.string().optional(),
|
|
83
84
|
noLint: z.boolean().optional(),
|
|
85
|
+
securityLevel: z.enum(['strict', 'standard', 'relaxed']).optional(),
|
|
84
86
|
});
|
|
85
87
|
|
|
86
88
|
function parseArgs() {
|
|
@@ -122,6 +124,9 @@ function parseArgs() {
|
|
|
122
124
|
i++;
|
|
123
125
|
} else if (args[i] === '--no-lint' || args[i] === '--lint-off') {
|
|
124
126
|
result.noLint = true;
|
|
127
|
+
} else if (args[i] === '--security-level' && args[i + 1]) {
|
|
128
|
+
result.securityLevel = args[i + 1];
|
|
129
|
+
i++;
|
|
125
130
|
}
|
|
126
131
|
}
|
|
127
132
|
return result;
|
|
@@ -197,6 +202,7 @@ const config = {
|
|
|
197
202
|
lintConfigPath: fileConfig?.lintConfigPath || cliArgs.lintConfigPath || env.LINT_CONFIG_PATH || undefined,
|
|
198
203
|
lintLevel: fileConfig?.lintLevel || cliArgs.lintLevel || env.LINT_LEVEL || undefined,
|
|
199
204
|
noLint: fileConfig?.noLint ?? cliArgs.noLint ?? (env.NO_LINT === 'true'),
|
|
205
|
+
securityLevel: fileConfig?.securityLevel ?? cliArgs.securityLevel ?? (env.SECURITY_LEVEL || 'strict'),
|
|
200
206
|
};
|
|
201
207
|
|
|
202
208
|
if (config.lintConfigPath) process.env.LINT_CONFIG_PATH = config.lintConfigPath;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { withCadConnection, mcpSuccess, mcpError } from '../handler-utils.js';
|
|
2
|
+
import {
|
|
3
|
+
createSession, getSession, closeSession, listSessions,
|
|
4
|
+
loadHelpers, fastForward, executeStep, continueExecution, evaluateExpression,
|
|
5
|
+
} from '../debug-engine.js';
|
|
6
|
+
|
|
7
|
+
export const debugStart = withCadConnection(async (args) => {
|
|
8
|
+
try {
|
|
9
|
+
const { code, chunks, encoding } = args || {};
|
|
10
|
+
if (!code) return mcpError('Missing required argument: code');
|
|
11
|
+
if (!chunks || !Array.isArray(chunks)) return mcpError('Missing required argument: chunks (must be an array)');
|
|
12
|
+
|
|
13
|
+
const sessionId = await createSession(code, chunks, encoding);
|
|
14
|
+
const session = getSession(sessionId);
|
|
15
|
+
if (!session) return mcpError('Failed to create debug session');
|
|
16
|
+
|
|
17
|
+
await loadHelpers(session);
|
|
18
|
+
|
|
19
|
+
const ffResult = await fastForward(session);
|
|
20
|
+
|
|
21
|
+
if (session.state === 'error') {
|
|
22
|
+
await closeSession(sessionId);
|
|
23
|
+
return mcpError(session.error);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return mcpSuccess(JSON.stringify({
|
|
27
|
+
sessionId,
|
|
28
|
+
totalChunks: session.totalChunks,
|
|
29
|
+
...ffResult,
|
|
30
|
+
}));
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return mcpError(e.message);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const debugStep = withCadConnection(async (args) => {
|
|
37
|
+
try {
|
|
38
|
+
const { sessionId, action } = args || {};
|
|
39
|
+
if (!sessionId) return mcpError('Missing required argument: sessionId');
|
|
40
|
+
if (!action) return mcpError('Missing required argument: action');
|
|
41
|
+
|
|
42
|
+
const session = getSession(sessionId);
|
|
43
|
+
if (!session) return mcpError(`Debug session not found: ${sessionId}`);
|
|
44
|
+
|
|
45
|
+
if (session.state !== 'paused' && session.state !== 'ready') {
|
|
46
|
+
return mcpError(`Invalid state: ${session.state} (expected paused or ready)`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let result;
|
|
50
|
+
if (action === 'step') {
|
|
51
|
+
result = await executeStep(session);
|
|
52
|
+
} else if (action === 'continue') {
|
|
53
|
+
result = await continueExecution(session);
|
|
54
|
+
} else {
|
|
55
|
+
return mcpError(`Invalid action: ${action} (expected step or continue)`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (session.state === 'error') {
|
|
59
|
+
return mcpError(session.error);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return mcpSuccess(JSON.stringify(result));
|
|
63
|
+
} catch (e) {
|
|
64
|
+
return mcpError(e.message);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const debugEvaluate = withCadConnection(async (args) => {
|
|
69
|
+
try {
|
|
70
|
+
const { sessionId, expression } = args || {};
|
|
71
|
+
if (!sessionId) return mcpError('Missing required argument: sessionId');
|
|
72
|
+
if (!expression) return mcpError('Missing required argument: expression');
|
|
73
|
+
|
|
74
|
+
const session = getSession(sessionId);
|
|
75
|
+
if (!session) return mcpError(`Debug session not found: ${sessionId}`);
|
|
76
|
+
|
|
77
|
+
if (session.state !== 'paused') {
|
|
78
|
+
return mcpError(`Invalid state: ${session.state} (expected paused)`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const result = await evaluateExpression(session, expression);
|
|
82
|
+
if (result.error) return mcpError(result.error);
|
|
83
|
+
|
|
84
|
+
return mcpSuccess(JSON.stringify({ value: result.value }));
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return mcpError(e.message);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export async function debugGetState(args) {
|
|
91
|
+
try {
|
|
92
|
+
const { sessionId } = args || {};
|
|
93
|
+
if (!sessionId) return mcpError('Missing required argument: sessionId');
|
|
94
|
+
|
|
95
|
+
const session = getSession(sessionId);
|
|
96
|
+
if (!session) return mcpError(`Debug session not found: ${sessionId}`);
|
|
97
|
+
|
|
98
|
+
const checkpoint = session.lastCheckpoint;
|
|
99
|
+
return mcpSuccess(JSON.stringify({
|
|
100
|
+
state: session.state,
|
|
101
|
+
currentIndex: session.currentIndex,
|
|
102
|
+
totalChunks: session.totalChunks,
|
|
103
|
+
currentCheckpoint: checkpoint || undefined,
|
|
104
|
+
error: session.error,
|
|
105
|
+
}));
|
|
106
|
+
} catch (e) {
|
|
107
|
+
return mcpError(e.message);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function debugStop(args) {
|
|
112
|
+
try {
|
|
113
|
+
const { sessionId } = args || {};
|
|
114
|
+
if (!sessionId) return mcpError('Missing required argument: sessionId');
|
|
115
|
+
|
|
116
|
+
const session = getSession(sessionId);
|
|
117
|
+
if (!session) {
|
|
118
|
+
return mcpError(`Debug session not found: ${sessionId}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const ok = await closeSession(sessionId);
|
|
122
|
+
return mcpSuccess(JSON.stringify({ success: ok }));
|
|
123
|
+
} catch (e) {
|
|
124
|
+
return mcpError(e.message);
|
|
125
|
+
}
|
|
126
|
+
}
|
package/dist/lisp-security.js
CHANGED
|
@@ -1,36 +1,90 @@
|
|
|
1
1
|
import { log, error as logError } from './logger.js';
|
|
2
|
+
import config, { onConfigChange } from './config.js';
|
|
2
3
|
|
|
3
4
|
export const THREAT_BLOCK = 'block';
|
|
4
5
|
export const THREAT_WARN = 'warn';
|
|
5
6
|
export const THREAT_ALLOW = 'allow';
|
|
6
7
|
|
|
8
|
+
export const SECURITY_LEVELS = ['strict', 'standard', 'relaxed'];
|
|
9
|
+
|
|
10
|
+
const CVT = {
|
|
11
|
+
strict: THREAT_BLOCK,
|
|
12
|
+
standard: THREAT_WARN,
|
|
13
|
+
relaxed: THREAT_ALLOW,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function getLevel() {
|
|
17
|
+
const level = config?.securityLevel || 'strict';
|
|
18
|
+
return CVT[level] || THREAT_BLOCK;
|
|
19
|
+
}
|
|
20
|
+
|
|
7
21
|
export const SECURITY_RULES = [
|
|
8
|
-
{ re: /\bstartapp\s*[(\s]/i,
|
|
9
|
-
{ re: /\bvl-bt\b/i,
|
|
10
|
-
{ re: /\bvlax-import-type-library\b/i,
|
|
11
|
-
{ re: /\bdos_command_string\s*[(\s]/i,
|
|
22
|
+
{ re: /\bstartapp\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 startapp 函数' },
|
|
23
|
+
{ re: /\bvl-bt\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 vl-bt 函数' },
|
|
24
|
+
{ re: /\bvlax-import-type-library\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止导入类型库' },
|
|
25
|
+
{ re: /\bdos_command_string\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 dos_command_string' },
|
|
26
|
+
{ re: /\beval\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 eval 函数' },
|
|
27
|
+
{ re: /\bcommand\s*["\s]*[_(]?\s*["']?\.?\s*_?(?:shell|quit|exit|close|open|new|recover|audit|purge|wblock|export)/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用危险 CAD 命令' },
|
|
28
|
+
{ re: /\bload\s*[(\s"]/i, baseSeverity: THREAT_BLOCK, msg: 'load 函数可能加载外部代码' },
|
|
29
|
+
{ re: /\b(?:read-line|read-char|write-line|write-char)\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '文件 I/O 操作可能影响系统' },
|
|
30
|
+
{ re: /\bvl-file-copy\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止文件复制' },
|
|
31
|
+
{ re: /\bvl-file-delete\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止文件删除' },
|
|
32
|
+
{ re: /\bvl-file-rename\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止文件重命名' },
|
|
33
|
+
{ re: /\bvl-registry-write[\s\)]/i, baseSeverity: THREAT_BLOCK, msg: 'vl-registry-write 会修改注册表' },
|
|
34
|
+
{ re: /\bvl-exit-with-value\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 vl-exit-with-value' },
|
|
35
|
+
{ re: /\bvl-exit-with-error\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 vl-exit-with-error' },
|
|
36
|
+
{ re: /\bvlax-add-cmd\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止注册新命令' },
|
|
37
|
+
{ re: /\bvla-Delete\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止直接删除 vla 对象' },
|
|
38
|
+
{ re: /\bvla-Save\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止直接保存文档' },
|
|
39
|
+
{ re: /\bdirectory\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 directory 函数' },
|
|
40
|
+
{ re: /\bvla-deletefile\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止删除文件' },
|
|
41
|
+
{ re: /\bvla-copyfile\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止复制文件' },
|
|
42
|
+
{ re: /\bvla-movefile\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止移动文件' },
|
|
12
43
|
];
|
|
13
44
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
45
|
+
let _cachedLevel = null;
|
|
46
|
+
|
|
47
|
+
export function refreshConfig() {
|
|
48
|
+
_cachedLevel = null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
onConfigChange(() => { _cachedLevel = null; });
|
|
53
|
+
} catch (_) {}
|
|
54
|
+
|
|
55
|
+
function resolveSeverity(rule) {
|
|
56
|
+
if (_cachedLevel) return _cachedLevel;
|
|
57
|
+
_cachedLevel = getLevel();
|
|
58
|
+
return _cachedLevel;
|
|
17
59
|
}
|
|
18
60
|
|
|
19
61
|
export function validateLispCodeWorker(code) {
|
|
20
62
|
const clean = stripStringsAndComments(code);
|
|
21
|
-
|
|
63
|
+
const effectiveSeverity = resolveSeverity();
|
|
64
|
+
|
|
65
|
+
for (const rule of SECURITY_RULES) {
|
|
66
|
+
const re = rule.re;
|
|
22
67
|
if (re.test(clean)) {
|
|
23
|
-
if (
|
|
24
|
-
return { valid: false, error: msg };
|
|
68
|
+
if (effectiveSeverity === THREAT_BLOCK) {
|
|
69
|
+
return { valid: false, error: rule.msg };
|
|
25
70
|
}
|
|
26
|
-
if (
|
|
27
|
-
log('WARN: ' + msg);
|
|
71
|
+
if (effectiveSeverity === THREAT_WARN) {
|
|
72
|
+
log('WARN: ' + rule.msg);
|
|
28
73
|
}
|
|
29
74
|
}
|
|
30
75
|
}
|
|
31
76
|
return { valid: true };
|
|
32
77
|
}
|
|
33
78
|
|
|
79
|
+
export function getEffectiveSeverity() {
|
|
80
|
+
return resolveSeverity();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function stripStringsAndComments(code) {
|
|
84
|
+
const withoutStrings = code.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, '');
|
|
85
|
+
return withoutStrings.replace(/;.*$/gm, '');
|
|
86
|
+
}
|
|
87
|
+
|
|
34
88
|
export function checkParens(code) {
|
|
35
89
|
let depth = 0;
|
|
36
90
|
let inString = false;
|