Sooua
登录
返回文章列表
Claude Code··13 分钟阅读·6558 次阅读

安全加固与沙箱配置

graph LR

目标:配置安全策略,使用沙箱隔离,防止 AI 操作带来的风险
预计时间:35 分钟
对应官方文档:SecuritySandboxingSandbox Environments


安全威胁模型

AI 代理的风险

风险示例防护措施
数据泄露AI 上传代码到外部网络隔离、DLP
权限提升AI 修改系统文件沙箱、权限控制
供应链攻击AI 安装恶意包包管理审查
意外破坏AI 删除生产数据备份、只读模式
提示注入恶意输入诱导 AI输入验证

安全威胁模型图


沙箱选项对比

方案隔离级别复杂度适用场景
内置 Bash 沙箱进程级日常开发
Dev Container容器级团队统一环境
Docker容器级自定义环境
VM / 云沙箱系统级高安全要求

内置沙箱配置

默认限制

# Claude Code 内置沙箱默认限制:
- 不能访问 $HOME 以外的目录(项目目录除外)
- 不能修改系统文件
- 网络访问受限
- 环境变量隔离

自定义沙箱规则

# .claude/sandbox.yaml
sandbox:
  # 文件系统
  filesystem:
    allowed_paths:
      - ./src
      - ./tests
      - /tmp/claude-work
    denied_paths:
      - ./secrets/
      - ~/.ssh/
      - /etc/
    
  # 网络
  network:
    mode: restricted
    allowed_hosts:
      - pypi.org
      - npmjs.org
      - github.com
    denied_hosts:
      - "*"
    
  # 命令
  commands:
    allowed:
      - python
      - pytest
      - black
      - git
    denied:
      - rm -rf /
      - curl *
      - wget *
    require_confirmation:
      - pip install *
      - npm install *
      - docker *

Dev Container 配置

创建配置

// .devcontainer/devcontainer.json
{
  "name": "Secure Claude Environment",
  "image": "mcr.microsoft.com/devcontainers/python:3.11",
  
  "features": {
    "ghcr.io/devcontainers/features/docker-in-docker:2": {}
  },
  
  "customizations": {
    "vscode": {
      "extensions": ["anthropic.claude-code"]
    }
  },
  
  "postCreateCommand": "pip install -r requirements.txt",
  
  "remoteUser": "vscode",
  
  // 安全加固
  "runArgs": [
    "--cap-drop=ALL",
    "--security-opt=no-new-privileges"
  ],
  
  // 挂载限制
  "mounts": [
    "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached"
  ]
}

使用

# VS Code 自动检测并启动 Dev Container
# 或手动启动
devcontainer up --workspace-folder .
 
# 在容器内运行 Claude Code
claude

安全策略最佳实践

1. 最小权限原则

# 创建专用用户(不要 root)
useradd -m claude-worker
su - claude-worker
 
# 限制目录权限
chmod 700 /home/claude-worker/projects
chmod 500 /home/claude-worker/.claude

2. 网络隔离

# 使用网络命名空间
sudo unshare --net --pid --fork --mount-proc /bin/bash
 
# 或使用 Firejail
firejail --net=none --private=. claude

3. 命令审计

# 记录所有执行的命令
export PROMPT_COMMAND='history -a'
export HISTFILE=/var/log/claude-commands.log
 
# 实时监控
tail -f /var/log/claude-commands.log | grep -E "(rm|curl|wget|pip|npm)"

4. 备份策略

# 自动备份(pre-session)
#!/bin/bash
# .claude/hooks/session-start.sh
BACKUP_DIR="/backups/claude/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR"
git bundle create "$BACKUP_DIR/repo.bundle" --all

安全插件

安装安全审查插件

claude plugin install security-guidance

配置安全规则

# .claude/security-rules.yaml
rules:
  - id: no-hardcoded-secrets
    pattern: '(password|secret|key|token)\s*=\s*["\'][^"\']+["\']'
    severity: error
    message: "禁止硬编码敏感信息"
    
  - id: no-sql-injection
    pattern: 'execute\s*\(\s*["\'].*%s'
    severity: error
    message: "可能存在 SQL 注入风险"
    
  - id: check-dependency-vulnerabilities
    command: "safety check"
    severity: warning
    
  - id: scan-for-secrets
    command: "git-secrets --scan"
    severity: error

应急响应

发现异常时

# 1. 立即停止所有 Claude 进程
killall claude
 
# 2. 检查修改了哪些文件
git status
git diff
 
# 3. 回滚到安全状态
git reset --hard HEAD
 
# 4. 审查日志
cat ~/.claude/logs/latest.log | grep -i "error\|warning\|denied"
 
# 5. 报告安全团队
# 保存相关日志和 diff

生产级安全加固方案

Docker Compose 完整配置

# docker-compose.security.yml
version: '3.8'
 
services:
  claude-secure:
    image: anthropic/claude-code:latest
    container_name: claude-sandbox
    
    # 安全选项
    security_opt:
      - no-new-privileges:true
      - seccomp:./seccomp-claude.json
    
    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - SETGID
      - SETUID
    
    # 只读根文件系统
    read_only: true
    
    # 临时文件系统
    tmpfs:
      - /tmp:noexec,nosuid,size=100m
      - /home/claude/.cache:size=50m
    
    # 网络隔离
    networks:
      - claude-isolated
    
    # 资源限制
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '0.5'
          memory: 512M
    
    # 健康检查
    healthcheck:
      test: ["CMD", "claude", "--version"]
      interval: 30s
      timeout: 10s
      retries: 3
    
    volumes:
      # 只读挂载项目代码
      - ./project:/workspace:ro
      # 可写挂载(限制范围)
      - ./workspace:/workspace/output:rw
      # 配置文件
      - ./security/claude-sandbox.yaml:/etc/claude/config.yaml:ro
 
networks:
  claude-isolated:
    driver: bridge
    internal: true  # 无外网访问

Seccomp 配置文件

// seccomp-claude.json
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86"],
  "syscalls": [
    {
      "names": [
        "accept", "bind", "clone", "close", "connect",
        "execve", "exit", "exit_group", "fcntl", "fork",
        "fstat", "getpid", "getrandom", "ioctl", "mmap",
        "munmap", "open", "openat", "poll", "read",
        "recvfrom", "sendto", "socket", "wait4", "write"
      ],
      "action": "SCMP_ACT_ALLOW"
    },
    {
      "names": ["chroot", "mount", "umount", "pivot_root"],
      "action": "SCMP_ACT_KILL"
    }
  ]
}

多层防御纵深架构

生产环境不应依赖单一沙箱。建议把「网络 → 进程 → 文件 → 命令 → 审计」组成五层防御:

关键原则:每一层都假设上一层会被绕过。L1 只放 API、L2 拒绝 mount/ptrace/setns、L3 把 ~/.ssh~/.aws 排除在视野之外、L4 阻断 curl/wget、L5 把所有 ToolUse 事件实时送到 SIEM。


提示注入防御链路

大多数「AI 代理被劫持」的案例都源自外部内容(README、issue、网页、MCP server 返回值)夹带了对模型的指令。下面的检测链路把它当作「输入污染」处理:

配套的 UserPromptSubmit Hook 实现:

# .claude/hooks/prompt_injection_guard.py
"""对用户输入和外部内容做提示注入检测。
 
Claude Code 在 UserPromptSubmit 与 PreToolUse 都会调用本脚本,
通过 stdin 传入 JSON:{ "text": "...", "source": "user|tool" }
返回非 0 表示阻断。
"""
import json
import re
import sys
from pathlib import Path
 
SUSPICIOUS_PATTERNS = [
    # 经典注入指令
    r"(?i)ignore\s+(all\s+)?previous\s+instructions",
    r"(?i)disregard\s+the\s+system\s+prompt",
    r"(?i)you\s+are\s+now\s+(dan|developer\s+mode)",
    # 试图触发危险工具
    r"(?i)run\s+`?\s*(rm\s+-rf|curl\s+http|wget\s+http)",
    r"(?i)exfiltrat\w*\s+(secrets?|tokens?|keys?)",
    # 试图越权读取
    r"(?i)cat\s+/etc/(passwd|shadow)",
    r"(?i)~\/\.ssh\/id_(rsa|ed25519)",
    # 隐写:零宽 / Unicode tag
    r"[\u200b-\u200f\ufeff\ue0000-\ue007f]",
]
 
ALLOW_FILE = Path(".claude/security/prompt_allowlist.txt")
 
 
def load_allowlist() -> list[re.Pattern]:
    if not ALLOW_FILE.exists():
        return []
    return [re.compile(line.strip()) for line in ALLOW_FILE.read_text().splitlines() if line.strip()]
 
 
def scan(text: str) -> list[str]:
    hits = []
    for pat in SUSPICIOUS_PATTERNS:
        for m in re.finditer(pat, text):
            hits.append(f"{pat} -> {m.group(0)[:80]}")
    return hits
 
 
def main() -> int:
    payload = json.loads(sys.stdin.read() or "{}")
    text = payload.get("text", "")
    source = payload.get("source", "user")
 
    allow = load_allowlist()
    if any(p.search(text) for p in allow):
        return 0
 
    hits = scan(text)
    if not hits:
        return 0
 
    decision = {
        "action": "block" if source == "user" else "flag",
        "reason": "prompt-injection suspicion",
        "hits": hits[:5],
    }
    sys.stderr.write(json.dumps(decision, ensure_ascii=False))
    # 用户输入直接拒绝;外部内容只标记,让模型继续但加 system reminder
    return 2 if source == "user" else 0
 
 
if __name__ == "__main__":
    raise SystemExit(main())

注册到 .claude/settings.json

{
  "hooks": {
    "UserPromptSubmit": [
      { "command": "python3 .claude/hooks/prompt_injection_guard.py" }
    ],
    "PreToolUse": [
      { "matcher": "WebFetch|WebSearch|mcp__.*",
        "command": "python3 .claude/hooks/prompt_injection_guard.py" }
    ]
  }
}

命令白名单 + 二次确认守门 Hook

沙箱再严,最容易出事的还是 Bash 工具。下面这个 Hook 同时实现:白名单、危险命令拦截、不可逆操作要求二次确认。

# .claude/hooks/bash_guard.py
import json
import os
import re
import shlex
import sys
from pathlib import Path
 
ALLOW = {
    "git", "python", "python3", "pip", "pytest", "node", "npm", "pnpm",
    "ls", "cat", "grep", "rg", "find", "head", "tail", "wc", "jq",
    "black", "ruff", "mypy", "go", "cargo", "make",
}
 
DESTRUCTIVE = [
    re.compile(r"\brm\s+-rf?\s+/"),
    re.compile(r"\bdd\s+if=.+of=/dev/(sd|nvme|hd)"),
    re.compile(r"\bmkfs\."),
    re.compile(r"(?i)\b(curl|wget)\s+[^|]*\|\s*(sh|bash|zsh|python)\b"),
    re.compile(r"\bgit\s+push\s+.*--force"),
    re.compile(r"\bdocker\s+system\s+prune\s+.*--volumes"),
]
 
NETWORK_HOSTS_ALLOW = {"api.anthropic.com", "github.com", "pypi.org", "registry.npmjs.org"}
CONFIRM_FILE = Path(os.environ.get("CLAUDE_CONFIRM_FILE", ".claude/.last_confirmed_command"))
 
 
def classify(cmd: str) -> tuple[str, str]:
    tokens = shlex.split(cmd, posix=True)
    if not tokens:
        return "allow", "empty"
    head = os.path.basename(tokens[0])
    if any(p.search(cmd) for p in DESTRUCTIVE):
        return "deny", f"destructive-pattern: {cmd[:80]}"
    if head not in ALLOW:
        return "confirm", f"command not in allowlist: {head}"
    if head in {"curl", "wget"}:
        host_match = re.search(r"https?://([^/\s]+)", cmd)
        host = host_match.group(1) if host_match else ""
        if host not in NETWORK_HOSTS_ALLOW:
            return "deny", f"network host not allowlisted: {host}"
    if "rm" in head and ("-r" in tokens or "-rf" in tokens or "--recursive" in tokens):
        return "confirm", "recursive deletion"
    return "allow", "ok"
 
 
def main() -> int:
    payload = json.loads(sys.stdin.read() or "{}")
    cmd = payload.get("command") or payload.get("tool_input", {}).get("command", "")
    decision, reason = classify(cmd)
 
    if decision == "allow":
        return 0
 
    if decision == "deny":
        sys.stderr.write(json.dumps({"action": "deny", "reason": reason}, ensure_ascii=False))
        return 2
 
    # confirm: 校验当前命令是否与上一次「人工确认」一致
    last = CONFIRM_FILE.read_text().strip() if CONFIRM_FILE.exists() else ""
    if last == cmd:
        CONFIRM_FILE.unlink(missing_ok=True)
        return 0
 
    CONFIRM_FILE.parent.mkdir(parents=True, exist_ok=True)
    CONFIRM_FILE.write_text(cmd)
    sys.stderr.write(json.dumps({
        "action": "confirm",
        "reason": reason,
        "hint": "再次发送相同命令以确认执行;或修改命令重新提交。",
    }, ensure_ascii=False))
    return 2
 
 
if __name__ == "__main__":
    raise SystemExit(main())

注册:

{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash", "command": "python3 .claude/hooks/bash_guard.py" }
    ]
  }
}

企业级实战场景

场景一:金融行业「PII 数据零外发」工作站

业务背景:某券商风控团队希望让分析师用 Claude Code 写策略代码,但合规要求客户身份信息(PII)和持仓数据不得离开内网。

威胁建模

完整配置

# /etc/claude/policy.yaml —— 通过 settings.json 引用
permissions:
  network:
    egress_proxy: "http://egress.corp.local:3128"
    enforce_proxy: true
    allow_hosts:
      - api.anthropic.com
  filesystem:
    deny_globs:
      - "**/customer_*.csv"
      - "**/positions_*.parquet"
      - "**/.env*"
      - "**/secrets/**"
  redaction:
    # PreToolUse 时自动脱敏发送给模型的内容
    patterns:
      - name: id_card
        regex: '\b\d{17}[\dXx]\b'
        replacement: "[REDACTED_ID]"
      - name: bank_card
        regex: '\b\d{16,19}\b'
        replacement: "[REDACTED_CARD]"
      - name: phone_cn
        regex: '\b1[3-9]\d{9}\b'
        replacement: "[REDACTED_PHONE]"
hooks:
  UserPromptSubmit:
    - command: "python3 /opt/claude/hooks/dlp_inline.py"
  PreToolUse:
    - matcher: "Read|Write|Edit|Bash"
      command: "python3 /opt/claude/hooks/dlp_inline.py"
audit:
  sink: "syslog://siem.corp.local:6514"
  include_tool_io: true
  redact_secrets: true
# /opt/claude/hooks/dlp_inline.py
import json
import re
import sys
 
PII = [
    re.compile(r"\b\d{17}[\dXx]\b"),     # 身份证
    re.compile(r"\b1[3-9]\d{9}\b"),      # 手机号
    re.compile(r"\b\d{16,19}\b"),        # 银行卡
    re.compile(r"(?i)customer_[a-z0-9]+"),
]
 
def main() -> int:
    data = json.loads(sys.stdin.read() or "{}")
    blob = json.dumps(data, ensure_ascii=False)
    for p in PII:
        if p.search(blob):
            sys.stderr.write(json.dumps({
                "action": "deny",
                "reason": "DLP: PII detected in tool I/O",
                "pattern": p.pattern,
            }, ensure_ascii=False))
            return 2
    return 0
 
if __name__ == "__main__":
    raise SystemExit(main())

部署清单:

  • 出口仅放行 api.anthropic.com,所有请求经 Squid + ICAP DLP;
  • 工位本地 claude 二进制由 SCCM 推送,settings.json 指向 /etc/claude/policy.yaml,普通用户无写权限;
  • 运行账号在 /etc/sudoers.d/claude 中显式禁用 sudo;
  • 每日 02:00 巡检脚本对比 hash,发现 settings 被改自动告警并恢复。

场景二:开源仓库「不可信 PR 自动审查」沙箱

业务背景:某开源项目维护者希望让 Claude Code 自动给外部 PR 写 review 评论,但 PR 里的 package.json、构建脚本、issue 模板都可能藏提示注入。

核心策略:所有外部内容一律视为 untrusted,跑在专用网络命名空间里,禁止接触主仓库 secrets。

# scripts/review-pr.sh —— 由 GitHub Actions 触发
#!/usr/bin/env bash
set -euo pipefail
 
PR_NUMBER="$1"
WORKDIR="$(mktemp -d /tmp/pr-review-XXXX)"
trap 'rm -rf "$WORKDIR"' EXIT
 
git clone --depth=1 --branch "pr-${PR_NUMBER}" \
  https://github.com/example/project.git "$WORKDIR/src"
 
# 用 unshare 创建独立网络命名空间 + 只读挂载
exec unshare --net --pid --fork --mount-proc \
  --user --map-root-user \
  bwrap \
    --ro-bind /usr /usr \
    --ro-bind /lib /lib --ro-bind /lib64 /lib64 \
    --ro-bind /bin /bin --ro-bind /etc /etc \
    --tmpfs /tmp --proc /proc --dev /dev \
    --bind "$WORKDIR/src" /work \
    --chdir /work \
    --setenv ANTHROPIC_API_KEY "$REVIEWER_KEY" \
    --setenv CLAUDE_CONFIG_DIR /work/.claude-runtime \
    --unshare-net \
    -- \
    claude --print --dangerously-skip-permissions=false \
           --allowed-tools "Read,Grep,Glob" \
           --max-turns 10 \
           "$(cat /opt/review-prompt.md)"
// .claude-runtime/settings.json(仅启动时生成、ro 挂载)
{
  "permissions": {
    "tools": {
      "allow": ["Read", "Grep", "Glob"],
      "deny": ["Bash", "Write", "Edit", "WebFetch", "WebSearch"]
    }
  },
  "hooks": {
    "UserPromptSubmit": [
      { "command": "python3 /opt/claude/hooks/prompt_injection_guard.py" }
    ],
    "PreToolUse": [
      { "matcher": "Read",
        "command": "python3 /opt/claude/hooks/path_guard.py" }
    ]
  }
}
# /opt/claude/hooks/path_guard.py —— 阻止读取仓库以外的路径
import json, os, sys
ROOT = os.path.abspath("/work")
 
def main() -> int:
    data = json.loads(sys.stdin.read() or "{}")
    path = data.get("tool_input", {}).get("file_path", "")
    real = os.path.realpath(path) if path else ""
    if not real.startswith(ROOT):
        sys.stderr.write(json.dumps({
            "action": "deny",
            "reason": f"path escape: {real}",
        }, ensure_ascii=False))
        return 2
    return 0
 
if __name__ == "__main__":
    raise SystemExit(main())

落地要点:

  • API Key 通过 GitHub Encrypted Secrets 注入,且只授予 claude.ai/code review 配额,不要复用主项目 key;
  • --unshare-net 配合 --allowed-toolsWebFetch/WebSearch/Bash 全锁死,模型即使被注入也无法外联;
  • review 结果通过 stdout 收集,再由外层 GitHub Actions 用 gh pr comment 写回,AI 进程本身没有写仓库的权限。

下一步

07. CI/CD 集成:GitHub Actions / GitLab

分享

评论

登录 后参与讨论。

加载中…

相关文章