@atlisp/mcp
Advanced tools
+241
| import { z } from 'zod'; | ||
| import fs from 'fs'; | ||
| import path from 'path'; | ||
| import os from 'os'; | ||
| const envSchema = z.object({ | ||
| PORT: z.string().optional().default('8110'), | ||
| HOST: z.string().optional().default('0.0.0.0'), | ||
| TRANSPORT: z.enum(['http', 'stdio', 'ws']).optional().default('http'), | ||
| MCP_API_KEY: z.string().optional().default(''), | ||
| ENABLE_SSE: z.string().optional().default('true'), | ||
| MESSAGE_TIMEOUT: z.string().optional().default('60000'), | ||
| HEARTBEAT_INTERVAL: z.string().optional().default('30000'), | ||
| BUSY_RETRIES: z.string().optional().default('10'), | ||
| BUSY_DELAY: z.string().optional().default('500'), | ||
| ENABLE_CORS: z.string().optional().default('true'), | ||
| CORS_ORIGIN: z.string().optional().default('*'), | ||
| RATE_LIMIT_WINDOW: z.string().optional().default('60000'), | ||
| RATE_LIMIT_MAX: z.string().optional().default('100'), | ||
| RESOURCE_CACHE_TTL: z.string().optional().default('5000'), | ||
| FUNCTION_LIB_CACHE_TTL: z.string().optional().default('300000'), | ||
| DEBUG: z.string().optional().default('false'), | ||
| DEBUG_FILE: z.string().optional().default(''), | ||
| FUNCTION_LIB_URL: z.string().optional().default('http://s3.atlisp.cn/json/functions.json'), | ||
| CONFIG_FILE: z.string().optional().default(''), | ||
| API_KEY_RATE_LIMIT: z.string().optional().default('1000'), | ||
| METRICS_ENABLED: z.string().optional().default('true'), | ||
| REQUEST_LOG_ENABLED: z.string().optional().default('true'), | ||
| SSL_KEY_PATH: z.string().optional().default(''), | ||
| SSL_CERT_PATH: z.string().optional().default(''), | ||
| LLM_API_KEY: z.string().optional().default(''), | ||
| LLM_API_URL: z.string().optional().default('https://api.openai.com/v1/chat/completions'), | ||
| LLM_MODEL: z.string().optional().default('gpt-4o-mini'), | ||
| LLM_MAX_TOKENS: z.string().optional().default('4096'), | ||
| LLM_TEMPERATURE: z.string().optional().default('0.7'), | ||
| }); | ||
| const configSchema = z.object({ | ||
| port: z.number().optional(), | ||
| host: z.string().optional(), | ||
| transport: z.enum(['http', 'stdio', 'ws']).optional(), | ||
| apiKey: z.string().optional(), | ||
| enableSse: z.boolean().optional(), | ||
| messageTimeout: z.number().optional(), | ||
| heartbeatInterval: z.number().optional(), | ||
| busyRetries: z.number().optional(), | ||
| busyDelay: z.number().optional(), | ||
| enableCors: z.boolean().optional(), | ||
| corsOrigin: z.string().optional(), | ||
| rateLimitWindow: z.number().optional(), | ||
| rateLimitMax: z.number().optional(), | ||
| resourceCacheTtl: z.number().optional(), | ||
| functionLibCacheTtl: z.number().optional(), | ||
| debug: z.boolean().optional(), | ||
| debugFile: z.string().optional(), | ||
| functionLibUrl: z.string().optional(), | ||
| configFile: z.string().optional(), | ||
| apiKeyRateLimit: z.number().optional(), | ||
| metricsEnabled: z.boolean().optional(), | ||
| apiKeys: z.array(z.object({ | ||
| key: z.string(), | ||
| limit: z.number(), | ||
| name: z.string().optional(), | ||
| role: z.string().optional() | ||
| })).optional(), | ||
| sslKey: z.string().optional(), | ||
| sslCert: z.string().optional(), | ||
| llmApiKey: z.string().optional(), | ||
| llmApiUrl: z.string().optional(), | ||
| llmModel: z.string().optional(), | ||
| llmMaxTokens: z.number().optional(), | ||
| llmTemperature: z.number().optional(), | ||
| maxPoolSize: z.number().optional(), | ||
| }); | ||
| function parseArgs() { | ||
| const args = process.argv.slice(2); | ||
| const result = {}; | ||
| for (let i = 0; i < args.length; i++) { | ||
| if (args[i] === '--transport' && args[i + 1]) { | ||
| result.transport = args[i + 1]; | ||
| i++; | ||
| } else if (args[i] === '--stdio') { | ||
| result.transport = 'stdio'; | ||
| } else if (args[i] === '--ws') { | ||
| result.transport = 'ws'; | ||
| } else if (args[i] === '--port' && args[i + 1]) { | ||
| result.port = args[i + 1]; | ||
| i++; | ||
| } else if (args[i] === '--host' && args[i + 1]) { | ||
| result.host = args[i + 1]; | ||
| i++; | ||
| } else if (args[i] === '--config' && args[i + 1]) { | ||
| result.configFile = args[i + 1]; | ||
| i++; | ||
| } else if (args[i] === '--ssl-key' && args[i + 1]) { | ||
| result.sslKey = args[i + 1]; | ||
| i++; | ||
| } else if (args[i] === '--ssl-cert' && args[i + 1]) { | ||
| result.sslCert = args[i + 1]; | ||
| i++; | ||
| } else if (args[i] === '--debug') { | ||
| result.debug = true; | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| export { parseArgs }; | ||
| function loadConfigFile(filePath) { | ||
| if (!filePath || !fs.existsSync(filePath)) return null; | ||
| try { | ||
| const raw = fs.readFileSync(filePath, 'utf-8'); | ||
| const parsed = JSON.parse(raw); | ||
| const result = configSchema.safeParse(parsed); | ||
| if (result.success) { | ||
| return result.data; | ||
| } | ||
| console.error('配置文件格式错误:', result.error); | ||
| } catch (e) { | ||
| console.error('加载配置文件失败:', e.message); | ||
| } | ||
| return null; | ||
| } | ||
| const cliArgs = parseArgs(); | ||
| const envResult = envSchema.safeParse(process.env); | ||
| if (!envResult.success) { | ||
| console.error('环境变量配置错误:', envResult.error.flatten().fieldErrors); | ||
| process.exit(1); | ||
| } | ||
| const env = envResult.data; | ||
| const configFilePath = env.CONFIG_FILE || cliArgs.configFile || | ||
| path.join(os.homedir(), '.atlisp', 'atlisp.config.json'); | ||
| const fileConfig = loadConfigFile(configFilePath); | ||
| const config = { | ||
| port: fileConfig?.port || parseInt(cliArgs.port || env.PORT, 10), | ||
| host: fileConfig?.host || cliArgs.host || env.HOST, | ||
| transport: fileConfig?.transport ?? cliArgs.transport ?? env.TRANSPORT, | ||
| apiKey: fileConfig?.apiKey || env.MCP_API_KEY, | ||
| enableSse: fileConfig?.enableSse ?? (env.ENABLE_SSE === 'true'), | ||
| messageTimeout: fileConfig?.messageTimeout || parseInt(env.MESSAGE_TIMEOUT, 10), | ||
| heartbeatInterval: fileConfig?.heartbeatInterval || parseInt(env.HEARTBEAT_INTERVAL, 10), | ||
| busyRetries: fileConfig?.busyRetries || parseInt(env.BUSY_RETRIES, 10), | ||
| busyDelay: fileConfig?.busyDelay || parseInt(env.BUSY_DELAY, 10), | ||
| enableCors: fileConfig?.enableCors ?? (env.ENABLE_CORS === 'true'), | ||
| corsOrigin: fileConfig?.corsOrigin || env.CORS_ORIGIN, | ||
| rateLimitWindow: fileConfig?.rateLimitWindow || parseInt(env.RATE_LIMIT_WINDOW, 10), | ||
| rateLimitMax: fileConfig?.rateLimitMax || parseInt(env.RATE_LIMIT_MAX, 10), | ||
| resourceCacheTtl: fileConfig?.resourceCacheTtl || parseInt(env.RESOURCE_CACHE_TTL, 10), | ||
| functionLibCacheTtl: fileConfig?.functionLibCacheTtl || parseInt(env.FUNCTION_LIB_CACHE_TTL, 10), | ||
| debug: fileConfig?.debug ?? (env.DEBUG === 'true'), | ||
| debugFile: fileConfig?.debugFile || env.DEBUG_FILE, | ||
| functionLibUrl: fileConfig?.functionLibUrl || env.FUNCTION_LIB_URL, | ||
| configFile: configFilePath, | ||
| apiKeyRateLimit: fileConfig?.apiKeyRateLimit || parseInt(env.API_KEY_RATE_LIMIT, 10), | ||
| apiKeys: fileConfig?.apiKeys || [], | ||
| metricsEnabled: fileConfig?.metricsEnabled ?? (env.METRICS_ENABLED === 'true'), | ||
| requestLogEnabled: fileConfig?.requestLogEnabled ?? (env.REQUEST_LOG_ENABLED !== 'false'), | ||
| sslKey: fileConfig?.sslKey || env.SSL_KEY_PATH || cliArgs.sslKey, | ||
| sslCert: fileConfig?.sslCert || env.SSL_CERT_PATH || cliArgs.sslCert, | ||
| llmApiKey: fileConfig?.llmApiKey || env.LLM_API_KEY, | ||
| llmApiUrl: fileConfig?.llmApiUrl || env.LLM_API_URL, | ||
| llmModel: fileConfig?.llmModel || env.LLM_MODEL, | ||
| llmMaxTokens: fileConfig?.llmMaxTokens || parseInt(env.LLM_MAX_TOKENS, 10), | ||
| llmTemperature: fileConfig?.llmTemperature || parseFloat(env.LLM_TEMPERATURE), | ||
| maxPoolSize: fileConfig?.maxPoolSize || 3, | ||
| }; | ||
| export function validateConfig() { | ||
| const errors = []; | ||
| if (config.transport === 'http' || config.transport === 'ws') { | ||
| if (config.port < 1 || config.port > 65535) { | ||
| errors.push(`端口号无效: ${config.port}`); | ||
| } | ||
| } | ||
| if (config.sslKey && !fs.existsSync(config.sslKey)) { | ||
| errors.push(`SSL 密钥文件不存在: ${config.sslKey}`); | ||
| } | ||
| if (config.sslCert && !fs.existsSync(config.sslCert)) { | ||
| errors.push(`SSL 证书文件不存在: ${config.sslCert}`); | ||
| } | ||
| if (config.messageTimeout < 1000) { | ||
| errors.push(`消息超时过短: ${config.messageTimeout}ms (最小 1000ms)`); | ||
| } | ||
| if (config.rateLimitMax < 1) { | ||
| errors.push(`速率限制无效: ${config.rateLimitMax}`); | ||
| } | ||
| if (errors.length > 0) { | ||
| console.error('配置验证错误:'); | ||
| errors.forEach(e => console.error(` - ${e}`)); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| let configWatchers = []; | ||
| if (configFilePath && fs.existsSync(configFilePath)) { | ||
| try { | ||
| fs.watch(configFilePath, (eventType) => { | ||
| if (eventType === 'change') { | ||
| const newFileConfig = loadConfigFile(configFilePath); | ||
| if (newFileConfig) { | ||
| Object.assign(config, newFileConfig); | ||
| configWatchers.forEach(fn => fn(config)); | ||
| } | ||
| } | ||
| }); | ||
| } catch (e) { console.error(`Config file watch error: ${e.message}`); } | ||
| } | ||
| export function getConfig(key) { | ||
| return config[key]; | ||
| } | ||
| export function getAllConfig() { | ||
| return { ...config }; | ||
| } | ||
| export function reloadConfig() { | ||
| const newFileConfig = loadConfigFile(config.configFile); | ||
| if (newFileConfig) { | ||
| Object.assign(config, newFileConfig); | ||
| configWatchers.forEach(fn => fn(config)); | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| export function onConfigChange(callback) { | ||
| configWatchers.push(callback); | ||
| } | ||
| export default config; |
| import { log, error as logError } from './logger.js'; | ||
| export const THREAT_BLOCK = 'block'; | ||
| export const THREAT_WARN = 'warn'; | ||
| export const THREAT_ALLOW = 'allow'; | ||
| export const SECURITY_RULES = [ | ||
| { re: /\beval\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止使用 eval 函数' }, | ||
| { re: /\bstartapp\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止使用 startapp 函数' }, | ||
| { re: /\bvl-bt\b/i, severity: THREAT_BLOCK, msg: '禁止使用 vl-bt 函数' }, | ||
| { re: /\bvl-exit-with-value\b/i, severity: THREAT_BLOCK, msg: '禁止使用 vl-exit-with-value' }, | ||
| { re: /\bvl-exit-with-error\b/i, severity: THREAT_BLOCK, msg: '禁止使用 vl-exit-with-error' }, | ||
| { re: /\bvlax-import-type-library\b/i, severity: THREAT_BLOCK, msg: '禁止导入类型库' }, | ||
| { re: /\bvlax-add-cmd\b/i, severity: THREAT_BLOCK, msg: '禁止注册新命令' }, | ||
| { re: /\bvla-Delete\b/i, severity: THREAT_BLOCK, msg: '禁止直接删除 vla 对象' }, | ||
| { re: /\bvla-Save\b/i, severity: THREAT_BLOCK, msg: '禁止直接保存文档' }, | ||
| { re: /\bdirectory\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止使用 directory 函数' }, | ||
| { re: /\bvla-deletefile\b/i, severity: THREAT_BLOCK, msg: '禁止删除文件' }, | ||
| { re: /\bvla-copyfile\b/i, severity: THREAT_BLOCK, msg: '禁止复制文件' }, | ||
| { re: /\bvla-movefile\b/i, severity: THREAT_BLOCK, msg: '禁止移动文件' }, | ||
| { re: /\bvl-file-copy\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止文件复制' }, | ||
| { re: /\bvl-file-delete\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止文件删除' }, | ||
| { re: /\bvl-file-rename\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止文件重命名' }, | ||
| { re: /\bdos_command_string\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止使用 dos_command_string' }, | ||
| { re: /\bload\s*[(\s"]/i, severity: THREAT_WARN, msg: 'load 函数可能加载外部代码' }, | ||
| { re: /\bcommand\s*["\s]*[_(]?\s*["']?\.?\s*_?(?:quit|exit|close|open|new|recover|audit|purge|wblock|export)/i, severity: THREAT_BLOCK, msg: '禁止使用危险 CAD 命令' }, | ||
| { re: /\b(?:read-line|read-char|write-line|write-char)\s*[(\s]/i, severity: THREAT_WARN, msg: '文件 I/O 操作可能影响系统' }, | ||
| { re: /\bvla-add\b/i, severity: THREAT_ALLOW, msg: 'vla-add 创建新对象' }, | ||
| ]; | ||
| export function stripStringsAndComments(code) { | ||
| const withoutStrings = code.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, ''); | ||
| return withoutStrings.replace(/;.*$/gm, ''); | ||
| } | ||
| export function validateLispCodeWorker(code) { | ||
| const clean = stripStringsAndComments(code); | ||
| for (const { re, msg } of SECURITY_RULES) { | ||
| if (re.test(clean)) { | ||
| return { valid: false, error: msg }; | ||
| } | ||
| } | ||
| return { valid: true }; | ||
| } | ||
| export function checkParens(code) { | ||
| let depth = 0; | ||
| let inString = false; | ||
| for (let i = 0; i < code.length; i++) { | ||
| const ch = code[i]; | ||
| if (inString) { | ||
| if (ch === '\\' && i + 1 < code.length) { | ||
| i++; | ||
| continue; | ||
| } | ||
| if (ch === '"') { | ||
| inString = false; | ||
| } | ||
| continue; | ||
| } | ||
| if (ch === '"') { | ||
| inString = true; | ||
| continue; | ||
| } | ||
| if (ch === ';') { | ||
| while (i < code.length && code[i] !== '\n') i++; | ||
| continue; | ||
| } | ||
| if (ch === '(') depth++; | ||
| if (ch === ')') depth--; | ||
| if (depth < 0) return { valid: false, error: '多余右括号 )', pos: i }; | ||
| } | ||
| if (depth > 0) return { valid: false, error: `缺少 ${depth} 个右括号 )`, pos: code.length - 1 }; | ||
| if (depth < 0) return { valid: false, error: `多余 ${-depth} 个左括号 (` }; | ||
| return { valid: true }; | ||
| } |
+163
| import fs from 'fs'; | ||
| import path from 'path'; | ||
| import os from 'os'; | ||
| import config from './config.js'; | ||
| const DEBUG_FILE = config.debugFile || path.join(os.tmpdir(), 'mcp-server-debug.log'); | ||
| const MAX_LOG_SIZE = 10 * 1024 * 1024; | ||
| const MAX_LOG_FILES = 5; | ||
| let stream = null; | ||
| function rotateLog() { | ||
| try { | ||
| if (!fs.existsSync(DEBUG_FILE)) return; | ||
| const stat = fs.statSync(DEBUG_FILE); | ||
| if (stat.size < MAX_LOG_SIZE) return; | ||
| if (stream) { stream.end(); stream = null; } | ||
| for (let i = MAX_LOG_FILES - 1; i > 0; i--) { | ||
| const oldPath = `${DEBUG_FILE}.${i}`; | ||
| if (fs.existsSync(oldPath)) { | ||
| try { fs.renameSync(oldPath, `${DEBUG_FILE}.${i + 1}`); } catch (e) { console.error('log rotate rename error:', e.message); } | ||
| } | ||
| } | ||
| try { fs.renameSync(DEBUG_FILE, `${DEBUG_FILE}.1`); } catch (e) { console.error('log rotate rename error:', e.message); } | ||
| } catch (e) { console.error('log rotate error:', e.message); } | ||
| } | ||
| function getStream() { | ||
| if (!stream) { | ||
| rotateLog(); | ||
| stream = fs.createWriteStream(DEBUG_FILE, { flags: 'a' }); | ||
| } | ||
| return stream; | ||
| } | ||
| function formatMessage(level, message, meta = {}) { | ||
| const entry = { | ||
| timestamp: new Date().toISOString(), | ||
| level, | ||
| message, | ||
| ...meta, | ||
| pid: process.pid, | ||
| hostname: os.hostname(), | ||
| }; | ||
| return JSON.stringify(entry); | ||
| } | ||
| export function log(message, meta = {}) { | ||
| getStream().write(formatMessage('INFO', message, meta) + '\n'); | ||
| notify('info', message); | ||
| if (config.debug) { | ||
| console.log(`[INFO] ${message}`, meta); | ||
| } | ||
| } | ||
| export function error(message, meta = {}) { | ||
| getStream().write(formatMessage('ERROR', message, meta) + '\n'); | ||
| notify('error', message); | ||
| if (config.debug) { | ||
| console.error(`[ERROR] ${message}`, meta); | ||
| } | ||
| } | ||
| export function warn(message, meta = {}) { | ||
| getStream().write(formatMessage('WARN', message, meta) + '\n'); | ||
| notify('warning', message); | ||
| if (config.debug) { | ||
| console.warn(`[WARN] ${message}`, meta); | ||
| } | ||
| } | ||
| export function debug(message, meta = {}) { | ||
| if (config.debug) { | ||
| getStream().write(formatMessage('DEBUG', message, meta) + '\n'); | ||
| } | ||
| } | ||
| let _logNotify = null; | ||
| export function setLoggingNotify(fn) { | ||
| _logNotify = fn; | ||
| } | ||
| export function getLoggingNotify() { | ||
| return _logNotify; | ||
| } | ||
| function notify(level, message) { | ||
| if (_logNotify) { | ||
| try { _logNotify(level, message); } catch {} | ||
| } | ||
| } | ||
| export function closeLog() { | ||
| if (stream) { | ||
| stream.end(); | ||
| stream = null; | ||
| } | ||
| } | ||
| let requestStream = null; | ||
| const REQUEST_LOG_FILE = path.join(os.homedir(), '@lisp', 'logs', 'requests.log'); | ||
| function ensureRequestLogDir() { | ||
| const dir = path.dirname(REQUEST_LOG_FILE); | ||
| if (!fs.existsSync(dir)) { | ||
| try { fs.mkdirSync(dir, { recursive: true }); } catch (e) { console.error('request log mkdir error:', e.message); } | ||
| } | ||
| } | ||
| function rotateRequestLog() { | ||
| try { | ||
| if (!fs.existsSync(REQUEST_LOG_FILE)) return; | ||
| const stat = fs.statSync(REQUEST_LOG_FILE); | ||
| if (stat.size < 10 * 1024 * 1024) return; | ||
| if (requestStream) { requestStream.end(); requestStream = null; } | ||
| for (let i = 5; i > 0; i--) { | ||
| const oldPath = `${REQUEST_LOG_FILE}.${i}`; | ||
| if (fs.existsSync(oldPath)) { | ||
| try { fs.renameSync(oldPath, `${REQUEST_LOG_FILE}.${i + 1}`); } catch (e) { console.error('request log rotate error:', e.message); } | ||
| } | ||
| } | ||
| try { fs.renameSync(REQUEST_LOG_FILE, `${REQUEST_LOG_FILE}.1`); } catch (e) { console.error('request log rotate error:', e.message); } | ||
| } catch (e) { console.error('request log rotate error:', e.message); } | ||
| } | ||
| export function logRequest(req) { | ||
| if (!config.requestLogEnabled) return; | ||
| try { | ||
| ensureRequestLogDir(); | ||
| rotateRequestLog(); | ||
| if (!requestStream) { | ||
| requestStream = fs.createWriteStream(REQUEST_LOG_FILE, { flags: 'a' }); | ||
| } | ||
| const entry = { | ||
| timestamp: new Date().toISOString(), | ||
| method: req.method, | ||
| path: req.path, | ||
| query: req.query, | ||
| ip: req.ip || req.connection?.remoteAddress, | ||
| userAgent: req.get('user-agent'), | ||
| }; | ||
| requestStream.write(JSON.stringify(entry) + '\n'); | ||
| } catch (e) { console.error('request log error:', e.message); } | ||
| } | ||
| export function logResponse(req, res, duration) { | ||
| if (!config.requestLogEnabled) return; | ||
| try { | ||
| ensureRequestLogDir(); | ||
| if (!requestStream) { | ||
| requestStream = fs.createWriteStream(REQUEST_LOG_FILE, { flags: 'a' }); | ||
| } | ||
| const entry = { | ||
| timestamp: new Date().toISOString(), | ||
| method: req.method, | ||
| path: req.path, | ||
| statusCode: res.statusCode, | ||
| duration: `${duration}ms`, | ||
| }; | ||
| requestStream.write(JSON.stringify(entry) + '\n'); | ||
| } catch (e) { console.error('request log error:', e.message); } | ||
| } |
+6
-66
@@ -7,2 +7,3 @@ #!/usr/bin/env node | ||
| import crypto from 'crypto'; | ||
| import { validateLispCodeWorker, checkParens } from './lisp-security.js'; | ||
@@ -114,4 +115,4 @@ const BUSY_RETRIES = parseInt(process.env.BUSY_RETRIES || '10', 10); | ||
| } catch (Exception ex) { | ||
| System.Diagnostics.Debug.WriteLine("Create COM " + platforms[i] + " error: " + ex.Message); | ||
| } | ||
| System.Diagnostics.Debug.WriteLine("cadLaunch create " + progIds[i] + " error: " + ex.Message); | ||
| } | ||
| } | ||
@@ -475,65 +476,4 @@ | ||
| const DANGEROUS_PATTERNS = [ | ||
| { re: /\beval\s*[(\s]/i, msg: '禁止使用 eval 函数' }, | ||
| { re: /\bload\s*[(\s"]/i, msg: '禁止使用 load 函数' }, | ||
| { re: /\bstartapp\s*[(\s]/i, msg: '禁止使用 startapp 函数' }, | ||
| { re: /\bvl-bt\b/i, msg: '禁止使用 vl-bt 函数' }, | ||
| { re: /\bvl-exit-with-value\b/i, msg: '禁止使用 vl-exit-with-value' }, | ||
| { re: /\bvl-exit-with-error\b/i, msg: '禁止使用 vl-exit-with-error' }, | ||
| { re: /\bvlax-import-type-library\b/i, msg: '禁止导入类型库' }, | ||
| { re: /\bvlax-add-cmd\b/i, msg: '禁止注册新命令' }, | ||
| { re: /\bvla-Delete\b/i, msg: '禁止直接删除 vla 对象' }, | ||
| { re: /\bvla-Save\b/i, msg: '禁止直接保存文档' }, | ||
| { re: /\bcommand\s*["\s]*[_(]?\s*["']?\.?\s*_?(?:quit|exit|close|open|new|recover|audit|purge|wblock|export)/i, msg: '禁止使用危险 CAD 命令' }, | ||
| { re: /\bdirectory\s*[(\s]/i, msg: '禁止使用 directory 函数' }, | ||
| { re: /\b(?:read-line|read-char|write-line|write-char)\s*[(\s]/i, msg: '禁止直接文件 I/O' }, | ||
| { re: /\bvla-deletefile\b/i, msg: '禁止删除文件' }, | ||
| { re: /\bvla-copyfile\b/i, msg: '禁止复制文件' }, | ||
| { re: /\bvla-movefile\b/i, msg: '禁止移动文件' }, | ||
| { re: /\bdos_command_string\s*[(\s]/i, msg: '禁止使用 dos_command_string' }, | ||
| ]; | ||
| export { checkParens } from './lisp-security.js'; | ||
| function validateLispCode(code) { | ||
| const withoutStrings = code.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, ''); | ||
| const withoutComments = withoutStrings.replace(/;.*$/gm, ''); | ||
| for (const { re, msg } of DANGEROUS_PATTERNS) { | ||
| if (re.test(withoutComments)) { | ||
| return { valid: false, error: msg }; | ||
| } | ||
| } | ||
| return { valid: true }; | ||
| } | ||
| export function checkParens(code) { | ||
| let depth = 0; | ||
| let inString = false; | ||
| for (let i = 0; i < code.length; i++) { | ||
| const ch = code[i]; | ||
| if (inString) { | ||
| if (ch === '\\' && i + 1 < code.length) { | ||
| i++; | ||
| continue; | ||
| } | ||
| if (ch === '"') { | ||
| inString = false; | ||
| } | ||
| continue; | ||
| } | ||
| if (ch === '"') { | ||
| inString = true; | ||
| continue; | ||
| } | ||
| if (ch === ';') { | ||
| while (i < code.length && code[i] !== '\n') i++; | ||
| continue; | ||
| } | ||
| if (ch === '(') depth++; | ||
| if (ch === ')') depth--; | ||
| if (depth < 0) return { valid: false, error: '多余右括号 )', pos: i }; | ||
| } | ||
| if (depth > 0) return { valid: false, error: `缺少 ${depth} 个右括号 )`, pos: code.length - 1 }; | ||
| if (depth < 0) return { valid: false, error: `多余 ${-depth} 个左括号 (` }; | ||
| return { valid: true }; | ||
| } | ||
| async function connectToPlatform(targetPlatform) { | ||
@@ -615,3 +555,3 @@ return new Promise((resolve, reject) => { | ||
| } | ||
| const injectionCheck = validateLispCode(msg.code); | ||
| const injectionCheck = validateLispCodeWorker(msg.code); | ||
| if (!injectionCheck.valid) { | ||
@@ -669,3 +609,3 @@ return { success: false, error: `安全限制: ${injectionCheck.error}` }; | ||
| } | ||
| const injectionCheck = validateLispCode(msg.code); | ||
| const injectionCheck = validateLispCodeWorker(msg.code); | ||
| if (!injectionCheck.valid) { | ||
@@ -672,0 +612,0 @@ return { success: false, error: `安全限制: ${injectionCheck.error}` }; |
+2
-2
| { | ||
| "name": "@atlisp/mcp", | ||
| "version": "1.8.11", | ||
| "version": "1.8.12", | ||
| "description": "MCP Server for @lisp on CAD,support AutoCAD/GstarCAD/ZWCAD/BricsCAD or CAD platform compatible with AutoLISP", | ||
@@ -21,3 +21,3 @@ "type": "module", | ||
| "build:main": "esbuild src/atlisp-mcp.js --bundle --platform=node --target=node18 --format=esm --outdir=dist --external:edge-js --packages=external --banner:js=\"#!/usr/bin/env node\"", | ||
| "build:worker": "node -e \"require('fs').copyFileSync('src/cad-worker.js','dist/cad-worker.js')\"", | ||
| "build:worker": "node -e \"const fs=require('fs');for(const f of ['cad-worker.js','lisp-security.js','logger.js','config.js'])fs.copyFileSync('src/'+f,'dist/'+f)\"", | ||
| "prepublishOnly": "npm run build", | ||
@@ -24,0 +24,0 @@ "test": "vitest run", |
Sorry, the diff of this file is too big to display
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
745629
24.68%7
75%18661
21.81%