本文件包含针对所有安全修复的验证测试用例。
- TypeScript: Node.js, tsx, zod
- Python: Python 3.11+, pytest
- 平台: Windows 10/11, Linux, macOS
// 测试文件: ts-src/tests/mcp-security.test.ts
import { describe, it, expect } from 'vitest'
import { createMcpBackedTools } from '../src/mcp.js'
describe('MCP Command Injection Prevention', () => {
it('应该拒绝不在白名单中的相对命令', async () => {
await expect(
createMcpBackedTools({
cwd: process.cwd(),
mcpServers: {
malicious: {
command: 'malicious_command',
args: ['--exploit'],
},
},
})
).rejects.toThrow(/not in the allowed list/)
})
it('应该拒绝包含 shell 元字符的参数', async () => {
await expect(
createMcpBackedTools({
cwd: process.cwd(),
mcpServers: {
test: {
command: 'node',
args: ['-e', 'console.log("test"); rm -rf /'],
},
},
})
).rejects.toThrow(/dangerous shell character/)
})
it('应该允许白名单中的命令', async () => {
const result = await createMcpBackedTools({
cwd: process.cwd(),
mcpServers: {
valid: {
command: 'node',
args: ['--version'],
},
},
})
expect(result.servers).toHaveLength(1)
expect(result.servers[0].status).toBe('connected')
})
})# 1. 创建测试配置文件
cat > test-mcp-config.json << 'EOF'
{
"mcpServers": {
"test": {
"command": "node",
"args": ["--version"]
}
}
}
EOF
# 2. 测试合法命令
minicode --mcp-config test-mcp-config.json
# 3. 测试恶意命令(应该被拒绝)
cat > malicious-mcp-config.json << 'EOF'
{
"mcpServers": {
"malicious": {
"command": "evil_command",
"args": ["; rm -rf /"]
}
}
}
EOF
# 启动时应该报错
minicode --mcp-config malicious-mcp-config.json// 测试文件: ts-src/tests/workspace-security.test.ts
import { describe, it, expect } from 'vitest'
import path from 'path'
import { resolveToolPath } from '../src/workspace.js'
import type { ToolContext } from '../src/tool.js'
describe('Workspace Path Traversal Prevention', () => {
const mockContext: ToolContext = {
cwd: '/workspace',
permissions: undefined,
}
it('应该阻止 ../ 路径遍历', async () => {
await expect(
resolveToolPath(mockContext, '../etc/passwd', 'read')
).rejects.toThrow(/Path escapes workspace/)
})
it('应该阻止 ..\\ 路径遍历 (Windows)', async () => {
await expect(
resolveToolPath(mockContext, '..\\..\\windows\\system32', 'read')
).rejects.toThrow(/Path escapes workspace/)
})
it('应该允许工作区内的相对路径', async () => {
const result = await resolveToolPath(mockContext, 'src/index.ts', 'read')
expect(result).toContain('src')
expect(result).toContain('index.ts')
})
})# 1. 创建测试环境
mkdir -p /tmp/test-workspace
cd /tmp/test-workspace
echo "secret" > /tmp/secret-file
# 2. 尝试通过路径遍历读取外部文件
# 应该失败
minicode --cwd /tmp/test-workspace
# 输入: read_file ../../../../tmp/secret-file
# 3. 创建符号链接测试
cd /tmp/test-workspace
ln -s /tmp/secret-file symlink-to-secret
# 4. 尝试通过符号链读取(应该被 realpath 阻止)
minicode --cwd /tmp/test-workspace
# 输入: read_file symlink-to-secret// 测试文件: ts-src/tests/permissions-security.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import fs from 'fs/promises'
import path from 'path'
import os from 'os'
import { PermissionManager } from '../src/permissions.js'
describe('Permission Symlink Prevention', () => {
let tempDir: string
let workspaceDir: string
let outsideFile: string
let symlink: string
beforeEach(async () => {
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'permissions-test-'))
workspaceDir = path.join(tempDir, 'workspace')
outsideFile = path.join(tempDir, 'outside-secret.txt')
symlink = path.join(workspaceDir, 'symlink')
await fs.mkdir(workspaceDir)
await fs.writeFile(outsideFile, 'secret content')
await fs.symlink(outsideFile, symlink)
})
afterEach(async () => {
await fs.rm(tempDir, { recursive: true, force: true })
})
it('应该阻止通过符号链接访问工作区外文件', async () => {
const manager = new PermissionManager(workspaceDir)
await manager.whenReady()
await expect(
manager.ensurePathAccess(symlink, 'read')
).rejects.toThrow(/Access denied/)
})
it('应该允许访问工作区内的正常文件', async () => {
const manager = new PermissionManager(workspaceDir)
await manager.whenReady()
const insideFile = path.join(workspaceDir, 'inside.txt')
await fs.writeFile(insideFile, 'test')
// 不应该抛出异常
await manager.ensurePathAccess(insideFile, 'read')
})
})// 测试文件: ts-src/tests/mcp-json-error.test.ts
import { describe, it, expect } from 'vitest'
describe('MCP JSON Error Handling', () => {
it('应该优雅处理无效 JSON 而不崩溃', async () => {
// 创建模拟的无效输出
const invalidOutput = Buffer.from('this is not valid JSON')
// 测试客户端应该能处理无效 JSON
// (实际测试需要 mock spawn 进程)
expect(() => {
try {
JSON.parse(invalidOutput.toString())
} catch (error) {
// 应该被 catch 而不崩溃
expect(error).toBeDefined()
}
}).not.toThrow()
})
it('应该记录错误日志而不是崩溃', async () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
// 模拟 handleMessage 调用
const client = // ... 创建客户端
// 发送无效数据
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to parse JSON-RPC message')
)
consoleSpy.mockRestore()
})
})// 测试文件: ts-src/tests/mcp-resource-leak.test.ts
import { describe, it, expect, vi } from 'vitest'
import { spawn } from 'node:child_process'
describe('MCP Resource Cleanup', () => {
it('应该在 close() 后完全清理子进程', async () => {
const client = // ... 创建客户端
// 启动
await client.start()
// 关闭
await client.close()
// 验证进程已退出
expect(client['process']).toBeNull()
})
it('应该在 3 秒超时后强制终止进程', async () => {
// Mock 一个不响应 SIGTERM 的进程
const client = // ... 创建客户端
const startTime = Date.now()
await client.close()
const duration = Date.now() - startTime
// 应该在 3 秒左右完成
expect(duration).toBeLessThan(4000)
})
})// 测试文件: ts-src/tests/install-security.test.ts
import { describe, it, expect, vi } from 'vitest'
import fs from 'fs/promises'
import path from 'path'
describe('Install Script Security', () => {
it('应该在文件存在时提示用户确认', async () => {
// Mock 文件存在
vi.mocked(fs.access).mockResolvedValue(undefined)
// 运行安装脚本
// 应该提示用户确认
expect(console.log).toHaveBeenCalledWith(
expect.stringContaining('已存在')
)
})
it('应该拒绝包含 .. 的安装路径', async () => {
// 测试路径验证逻辑
const maliciousPath = '/tmp/../../../etc/malicious'
expect(() => {
if (maliciousPath.includes('..')) {
throw new Error('Invalid installation path')
}
}).toThrow(/Invalid installation path/)
})
})# 测试文件: py-src/tests/test_permissions_security.py
import pytest
from minicode.permissions import _classify_dangerous_command
def test_python_is_detected():
result = _classify_dangerous_command("python", ["-c", "print('hello')"])
assert result is not None
assert "python" in result.lower()
def test_python3_is_detected():
result = _classify_dangerous_command("python3", ["--version"])
assert result is not None
def test_pythonw_is_detected():
result = _classify_dangerous_command("pythonw", ["script.py"])
assert result is not None
def test_powershell_is_detected():
result = _classify_dangerous_command("powershell", ["-Command", "Get-Process"])
assert result is not None
def test_pwsh_is_detected():
result = _classify_dangerous_command("pwsh", ["--version"])
assert result is not None
def test_zsh_is_detected():
result = _classify_dangerous_command("zsh", ["-c", "echo hello"])
assert result is not None
def test_fish_is_detected():
result = _classify_dangerous_command("fish", ["-c", "echo hello"])
assert result is not None// 测试文件: ts-src/tests/permissions-security.test.ts
import { describe, it, expect } from 'vitest'
// 需要导出 classifyDangerousCommand 函数进行测试
import { classifyDangerousCommand } from '../src/permissions.js'
describe('Dangerous Command Detection', () => {
it('应该检测 python 命令', () => {
const result = classifyDangerousCommand('python', ['-c', "print('hello')"])
expect(result).not.toBeNull()
expect(result).toContain('python')
})
it('应该检测 python3 命令', () => {
const result = classifyDangerousCommand('python3', ['--version'])
expect(result).not.toBeNull()
})
it('应该检测 pythonw 命令', () => {
const result = classifyDangerousCommand('pythonw', ['script.py'])
expect(result).not.toBeNull()
})
it('应该检测 powershell 命令', () => {
const result = classifyDangerousCommand('powershell', ['-Command', 'Get-Process'])
expect(result).not.toBeNull()
})
it('应该检测 pwsh 命令', () => {
const result = classifyDangerousCommand('pwsh', ['--version'])
expect(result).not.toBeNull()
})
it('应该检测 zsh 命令', () => {
const result = classifyDangerousCommand('zsh', ['-c', 'echo hello'])
expect(result).not.toBeNull()
})
it('应该检测 fish 命令', () => {
const result = classifyDangerousCommand('fish', ['-c', 'echo hello'])
expect(result).not.toBeNull()
})
})// 测试文件: ts-src/tests/error-message-consistency.test.ts
import { describe, it, expect } from 'vitest'
import fs from 'fs/promises'
import path from 'path'
describe('Error Message Language Consistency', () => {
it('agent-loop.ts 不应该包含中文错误消息', async () => {
const content = await fs.readFile(
path.join(__dirname, '../src/agent-loop.ts'),
'utf-8'
)
// 检查不应出现的中文字符串
expect(content).not.toContain('诊断信息')
expect(content).not.toContain('模型在 thinking 阶段')
expect(content).not.toContain('工具执行后模型返回空响应')
expect(content).not.toContain('达到最大工具步数限制')
// 应该包含英文版本
expect(content).toContain('Diagnostics:')
expect(content).toContain('Model returned an empty response')
expect(content).toContain('Reached the maximum tool step limit')
})
})@echo off
REM 测试文件: ts-src/tests/test-minicode-cmd.bat
echo Testing minicode.cmd...
REM 1. 测试脚本存在性
if not exist "..\bin\minicode.cmd" (
echo FAIL: minicode.cmd not found
exit /b 1
)
echo PASS: minicode.cmd exists
REM 2. 测试帮助信息
call ..\bin\minicode.cmd --help
if %ERRORLEVEL% NEQ 0 (
echo FAIL: minicode.cmd failed
exit /b 1
)
echo PASS: minicode.cmd runs successfully# 测试文件: ts-src/tests/test-minicode-ps1.ps1
Write-Host "Testing minicode.ps1..." -ForegroundColor Green
# 1. 测试脚本存在性
if (-not (Test-Path "..\bin\minicode.ps1")) {
Write-Host "FAIL: minicode.ps1 not found" -ForegroundColor Red
exit 1
}
Write-Host "PASS: minicode.ps1 exists" -ForegroundColor Green
# 2. 测试执行
& "..\bin\minicode.ps1" --help
if ($LASTEXITCODE -ne 0) {
Write-Host "FAIL: minicode.ps1 failed" -ForegroundColor Red
exit 1
}
Write-Host "PASS: minicode.ps1 runs successfully" -ForegroundColor Green# 使用 ESLint 安全插件
cd ts-src
npm install --save-dev eslint @microsoft/eslint-plugin-security
# 创建 .eslintrc.json
cat > .eslintrc.json << 'EOF'
{
"plugins": ["@microsoft/security"],
"extends": ["plugin:@microsoft/security/recommended"]
}
EOF
# 运行安全检查
npx eslint src/**/*.ts# 检查 npm 依赖漏洞
cd ts-src
npm audit
# 检查 Python 依赖漏洞
cd py-src
pip install safety
safety checkcd ts-src
# 如果还没有测试框架,先安装
npm install --save-dev vitest @vitest/coverage-v8
# 运行测试
npx vitest run
# 或带覆盖率运行
npx vitest run --coveragecd py-src
# 运行新增的安全测试
python -m pytest tests/test_permissions_security.py -v
# 运行所有测试
python -m pytest tests/ -v# 安全修复测试结果
## 测试执行摘要
- **总测试数**: XX
- **通过**: XX
- **失败**: XX
- **跳过**: XX
- **覆盖率**: XX%
## 详细结果
### MCP 命令注入防护
- [ ] 白名单验证通过
- [ ] Shell 元字符拦截通过
- [ ] 合法命令放行通过
### 路径遍历防护
- [ ] ../ 拦截通过
- [ ] ..\ 拦截通过
- [ ] 符号链接拦截通过
- [ ] 正常路径放行通过
### 资源泄漏防护
- [ ] 子进程优雅关闭通过
- [ ] 超时强制终止通过
- [ ] 流销毁验证通过
### 错误处理
- [ ] JSON 解析异常捕获通过
- [ ] 错误日志记录通过
- [ ] 无进程崩溃通过
### 跨平台兼容性
- [ ] Windows CMD 启动通过
- [ ] Windows PowerShell 启动通过
- [ ] Linux Bash 启动通过
- [ ] macOS Zsh 启动通过
## 结论
所有安全修复测试 **通过/失败**,可以/不可以合并到主分支。# .github/workflows/security.yml
name: Security Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install TS dependencies
run: |
cd ts-src
npm install
npm install --save-dev vitest
- name: Install Python dependencies
run: |
cd py-src
pip install -e ".[dev]"
- name: Run TypeScript tests
run: |
cd ts-src
npx vitest run
- name: Run Python tests
run: |
cd py-src
python -m pytest tests/ -v
- name: Security audit
run: |
cd ts-src
npm audit
cd ../py-src
pip install safety
safety check测试完成日期: _____________
测试人员: _____________
审核人员: _____________