Agent 的能力来自上下文——把代码、文档、日志塞进上下文,模型才能理解和修改。但上下文也是一个数据出口:塞进去的东西都可能被模型提供商记录、缓存、甚至用于训练。本文梳理源代码、日志、客户数据、密钥、私有算法等数据边界,给出分级策略和过滤方案。

Agent 隐私与数据边界:什么代码不能进模型上下文

Agent 的能力来自上下文——把代码、文档、日志塞进上下文,模型才能理解和修改。但上下文也是一个数据出口:塞进去的东西都可能被模型提供商记录、缓存、甚至用于训练。本文梳理源代码、日志、客户数据、密钥、私有算法等数据边界,给出分级策略和过滤方案。

一、数据边界的本质问题

text
数据流向:
  本地代码库 → 上下文包生成器 → 模型提供商 API → 模型推理 → 返回结果

风险点:
  ① 上下文包可能包含不该发送的内容
  ② 传输过程可能被截获
  ③ 模型提供商可能记录/缓存/训练
  ④ 返回结果可能泄露给其他用户

不同组织的风险承受度不同:

组织类型 风险承受度 策略
个人开发者 默认全发送,不敏感
初创公司 过滤密钥和配置,代码可发送
中型企业 过滤客户数据、核心算法、生产日志
金融/医疗/政府 极低 只用本地模型或私有化部署

二、数据分级标准

2.1 五级分类

yaml
# data-classification.yaml
classification_levels:
  
  L1_public:
    name: "公开数据"
    description: "可以发送给任何模型"
    examples:
      - "开源项目代码"
      - "公开的 API 文档"
      - "技术博客内容"
    handling: "无需特殊处理"
  
  L2_internal:
    name: "内部数据"
    description: "可以发送给商业模型,但不应发送给免费模型"
    examples:
      - "业务代码(非核心逻辑)"
      - "内部工具代码"
      - "测试数据(脱敏后)"
    handling: "正常发送,记录审计日志"
  
  L3_confidential:
    name: "机密数据"
    description: "需要审批才能发送,或必须脱敏后发送"
    examples:
      - "核心业务逻辑"
      - "数据库 Schema"
      - "内部 API 设计"
      - "配置文件(含环境变量)"
    handling: "审批后发送 / 脱敏后发送"
  
  L4_restricted:
    name: "受限数据"
    description: "默认不发送,特殊场景需高级审批"
    examples:
      - "客户个人信息(PII)"
      - "支付相关代码"
      - "安全相关代码(加密、认证)"
      - "生产环境日志"
    handling: "高级审批 + 脱敏 + 审计"
  
  L5_prohibited:
    name: "禁止数据"
    description: "绝对不发送给任何外部模型"
    examples:
      - "API 密钥、Token、密码"
      - "私钥文件(SSH、TLS)"
      - "客户原始数据(未脱敏)"
      - "核心算法(竞争优势)"
      - "合规数据(HIPAA、GDPR 约束)"
    handling: "本地模型处理 / 完全不处理"

2.2 文件级扫描规则

python
# app/privacy/scanner.py
"""
数据边界扫描器。
扫描上下文包中的文件,标记敏感内容。
"""
import re
from pathlib import Path
from dataclasses import dataclass

@dataclass
class ScanResult:
    file_path: str
    classification: str     # L1-L5
    sensitive_items: list   # 发现的敏感项
    action: str             # allow / redact / block
    reason: str

class PrivacyScanner:
    def __init__(self, rules_path: str):
        self.rules = self._load_rules(rules_path)
    
    def scan_file(self, file_path: str) -> ScanResult:
        """扫描单个文件"""
        content = Path(file_path).read_text()
        
        sensitive_items = []
        max_level = "L1_public"
        
        # 1. 扫描密钥模式
        for pattern in self.rules["secret_patterns"]:
            matches = re.findall(pattern["regex"], content)
            if matches:
                sensitive_items.extend([{
                    "type": pattern["type"],
                    "count": len(matches),
                    "level": pattern["level"],
                } for _ in matches])
                max_level = self._max_level(max_level, pattern["level"])
        
        # 2. 扫描 PII 模式
        for pattern in self.rules["pii_patterns"]:
            matches = re.findall(pattern["regex"], content)
            if matches:
                sensitive_items.extend([{
                    "type": pattern["type"],
                    "count": len(matches),
                    "level": pattern["level"],
                } for _ in matches])
                max_level = self._max_level(max_level, pattern["level"])
        
        # 3. 检查文件路径规则
        for rule in self.rules["path_rules"]:
            if re.match(rule["pattern"], file_path):
                max_level = self._max_level(max_level, rule["level"])
        
        # 4. 决定处理方式
        action = self._decide_action(max_level)
        
        return ScanResult(
            file_path=file_path,
            classification=max_level,
            sensitive_items=sensitive_items,
            action=action,
            reason=f"Classification: {max_level}",
        )
    
    def _load_rules(self, rules_path: str) -> dict:
        import yaml
        with open(rules_path) as f:
            return yaml.safe_load(f)
    
    def _max_level(self, current: str, new: str) -> str:
        levels = ["L1_public", "L2_internal", "L3_confidential", 
                  "L4_restricted", "L5_prohibited"]
        return new if levels.index(new) > levels.index(current) else current
    
    def _decide_action(self, level: str) -> str:
        actions = {
            "L1_public": "allow",
            "L2_internal": "allow",
            "L3_confidential": "redact",
            "L4_restricted": "block",
            "L5_prohibited": "block",
        }
        return actions.get(level, "block")

# 扫描规则配置
SCANNER_RULES = """
secret_patterns:
  - type: "aws_access_key"
    regex: "AKIA[0-9A-Z]{16}"
    level: "L5_prohibited"
  
  - type: "github_token"
    regex: "ghp_[a-zA-Z0-9]{36}"
    level: "L5_prohibited"
  
  - type: "generic_api_key"
    regex: "(?i)(api[_-]?key|secret|password)\\\\s*[:=]\\\\s*['\\\\\"]?[a-zA-Z0-9]{16,}"
    level: "L4_restricted"
  
  - type: "private_key"
    regex: "-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----"
    level: "L5_prohibited"

pii_patterns:
  - type: "email"
    regex: "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}"
    level: "L3_confidential"
  
  - type: "phone"
    regex: "\\\\b\\\\d{3}[-.]?\\\\d{3}[-.]?\\\\d{4}\\\\b"
    level: "L3_confidential"
  
  - type: "ssn"
    regex: "\\\\b\\\\d{3}-\\\\d{2}-\\\\d{4}\\\\b"
    level: "L4_restricted"
  
  - type: "credit_card"
    regex: "\\\\b\\\\d{4}[- ]?\\\\d{4}[- ]?\\\\d{4}[- ]?\\\\d{4}\\\\b"
    level: "L4_restricted"

path_rules:
  - pattern: ".*\\\\.(env|pem|key|p12|pfx)$"
    level: "L5_prohibited"
  
  - pattern: ".*/config/(prod|production)/.*"
    level: "L4_restricted"
  
  - pattern: ".*/(test|tests|spec)/.*"
    level: "L2_internal"
  
  - pattern: ".*\\\\.md$"
    level: "L2_internal"
"""

三、脱敏策略

3.1 可逆脱敏 vs 不可逆脱敏

yaml
# redaction-strategies.yaml
strategies:
  
  # 不可逆脱敏(替换为占位符)
  irreversible:
    - type: "api_key"
      method: "replace"
      replacement: "***API_KEY***"
      use_case: "密钥、Token"
    
    - type: "email"
      method: "replace"
      replacement: "user@example.com"
      use_case: "邮件地址"
    
    - type: "phone"
      method: "replace"
      replacement: "123-456-7890"
      use_case: "电话号码"
    
    - type: "number"
      method: "mask_middle"
      keep_prefix: 3
      keep_suffix: 4
      use_case: "账号、订单号"
      example: "ORD-123***-4567"
  
  # 可逆脱敏(加密,需要时可解密)
  reversible:
    - type: "customer_id"
      method: "encrypt"
      algorithm: "AES-256"
      key_source: "KMS"
      use_case: "需要回溯客户数据时"
    
    - type: "ip_address"
      method: "hash"
      algorithm: "SHA-256"
      salt: "per-session"
      use_case: "日志分析时需要关联同一 IP"

3.2 上下文包过滤器

python
# app/privacy/context_filter.py
"""
上下文包过滤器。
在发送给模型之前,过滤和脱敏上下文包中的内容。
"""

class ContextFilter:
    def __init__(self, scanner: PrivacyScanner, redactor: Redactor):
        self.scanner = scanner
        self.redactor = redactor
    
    def filter_context_pack(self, files: list, policy: str) -> dict:
        """
        过滤上下文包。
        policy: "strict" / "moderate" / "relaxed"
        """
        filtered = []
        blocked = []
        redacted = []
        
        for file_path in files:
            scan_result = self.scanner.scan_file(file_path)
            
            if policy == "strict":
                # 严格模式:L3+ 都阻止
                if scan_result.classification in ["L3_confidential", "L4_restricted", "L5_prohibited"]:
                    blocked.append({
                        "file": file_path,
                        "reason": scan_result.classification,
                    })
                    continue
            
            elif policy == "moderate":
                # 中等模式:L4+ 阻止,L3 脱敏
                if scan_result.classification in ["L4_restricted", "L5_prohibited"]:
                    blocked.append({
                        "file": file_path,
                        "reason": scan_result.classification,
                    })
                    continue
                elif scan_result.classification == "L3_confidential":
                    content = self.redactor.redact_file(file_path)
                    filtered.append({
                        "path": file_path,
                        "content": content,
                        "redacted": True,
                    })
                    redacted.append(file_path)
                    continue
            
            # L1-L2 或 relaxed 模式:直接通过
            filtered.append({
                "path": file_path,
                "content": Path(file_path).read_text(),
                "redacted": False,
            })
        
        return {
            "files": filtered,
            "blocked": blocked,
            "redacted": redacted,
            "policy": policy,
        }

四、本地模型作为隐私边界

4.1 混合路由策略

yaml
# privacy-routing.yaml
routing:
  # 根据数据分级路由到不同模型
  
  - condition: "data_classification in ['L1_public', 'L2_internal']"
    model: "cloud-sonnet"
    reason: "非敏感数据可以用云端模型"
  
  - condition: "data_classification == 'L3_confidential'"
    model: "cloud-sonnet"
    pre_processing: "redact"
    reason: "脱敏后发送到云端"
  
  - condition: "data_classification in ['L4_restricted', 'L5_prohibited']"
    model: "local-llama3-70b"
    reason: "敏感数据只用本地模型"
    fallback: "skip_task"  # 没有本地模型就跳过
  
  # 特殊场景
  - condition: "task.involves_customer_pii"
    model: "local-only"
    note: "涉及客户 PII 的任务永远不出云"
  
  - condition: "task.involves_production_data"
    model: "local-only"
    note: "生产数据永远不出云"

五、真实经验与踩坑

5.1 .gitignore 不等于隐私边界

场景:团队认为 .gitignore 中的文件(如 .env)不会进入上下文包。 问题:上下文包生成器不读 .gitignore,它按照"Agent 可能需要的文件"逻辑选择文件。.env 文件因为"配置相关"被包含进上下文。 解决方案:上下文包生成器必须有"排除列表"(denylist),包含 .env*.pem*.key 等。这个排除列表应该和 .gitignore 独立维护——.gitignore 是版本控制逻辑,排除列表是隐私逻辑。

5.2 注释中的敏感信息最容易被忽略

场景:代码本身没有敏感信息,但注释中写了 // 测试账号: admin / P@ssw0rd123问题:扫描器只检查代码逻辑,忽略了注释。 解决方案:扫描规则应该覆盖注释内容。同时团队规范中明确:"注释中不允许包含真实凭据,测试凭据使用占位符"。

5.3 模型提供商的缓存策略要搞清楚

场景:同一份代码多次发送给模型,第二次明显更快。以为是"模型变聪明了"。 问题:实际上是模型提供商缓存了之前的请求。缓存意味着数据在提供商端有留存。 解决方案:了解模型提供商的缓存策略——Anthropic 的 Prompt Cache 是临时的(5 分钟 TTL),不会用于训练。但如果数据敏感级别极高,仍然应该使用本地模型避免任何外部缓存。

六、参数说明表

参数 类型 默认值 说明
scan_enabled bool true 是否启用隐私扫描
default_policy string "moderate" 默认过滤策略
secret_patterns list 内置 密钥扫描正则
pii_patterns list 内置 PII 扫描正则
path_denylist list 见 2.2 文件路径排除列表
redaction_strategy string "irreversible" 脱敏策略
local_model_routing bool true 敏感数据是否路由到本地模型
audit_all_scans bool true 扫描结果是否记入审计日志
override_requires_approval bool true 覆盖隐私策略是否需要审批

七、落地检查清单

  • 数据分级标准已定义(L1-L5)
  • 密钥扫描规则覆盖了常见模式(AWS、GitHub、通用 API Key)
  • PII 扫描规则覆盖了邮件、电话、身份证号、信用卡号
  • 文件路径排除列表包含 .env*.pem*.key
  • 脱敏策略已定义(可逆 vs 不可逆)
  • 上下文包过滤器已集成到 Agent 工作流
  • 敏感数据有本地模型兜底方案
  • 扫描结果记入审计日志
  • 团队培训了隐私边界规范
  • 定期审查和更新扫描规则

八、系列导航

上一篇:Agent 审计日志规范:工具调用、Prompt、diff 与审批记录 下一篇:案例 005:一个 20 人团队的 Agent 落地 30 天复盘