往 Copilot 粘贴前,用启发式规则扫描 API Key、私钥、密码赋值、身份证号等。拦得住常见事故,不是完整 DLP。
先看清成品
- safe → OK;risky → BLOCK(多条命中)。
- 3 项 pytest 通过。
合规提醒:个人 Copilot 与 Microsoft 365 Copilot 是不同产品;企业场景用公司账号并遵守组织数据政策。不要把密钥、客户名单、未公开财报等粘贴进个人版聊天。Graph 连接器写入的外部内容受 ACL 约束;错误开放等于数据泄露。本文不编造价格。
运行说明:无 Microsoft 365 / Graph 租户密钥。三篇均为本地离线助手:A 从会议记录抽待办;B 粘贴前 DLP 启发式扫描;C 校验 externalItem 形状。未调用官方聊天 API(消费级 Copilot 无通用「随便调」的公开 chat API)。2026-10-23(Asia/Shanghai)pytest 通过。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
paste_guard.py |
粘贴门禁 |
| 样例 txt | safe/risky |
test_102b.py |
测试 |
需要准备哪些环境?
| 项目 | 说明 |
|---|---|
| Python | 3.10+ |
关键事实
| 事实 | 来源 |
|---|---|
| 个人版与企业版数据保护边界不同;企业应遵守组织策略 | 定位说明 |
| 启发式无法覆盖所有密钥形态 | 重要 |
| 真正企业控制应使用 M365 DLP / 纯视图等管理功能 | 产品能力,以租户配置为准 |
文件结构
102-microsoft-copilot-B-src/
├── paste_guard.py
├── sample_safe.txt
├── sample_risky.txt
└── test_102b.py
第 1 步:门禁脚本
#!/usr/bin/env python3
"""Scan text before pasting into Microsoft Copilot / M365 Copilot (heuristic DLP)."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
RULES = [
("api_key", re.compile(r"(?i)(api[_-]?key|secret|token)\s*[:=]\s*['\"]?[A-Za-z0-9_\-]{16,}")),
("private_key", re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----")),
("aws_akid", re.compile(r"AKIA[0-9A-Z]{16}")),
("password_assign", re.compile(r"(?i)password\s*[:=]\s*\S+")),
("cn_id", re.compile(r"\b[1-9]\d{5}(?:19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dXx]\b")),
("email_batch", re.compile(r"([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+.*){5,}", re.S)),
]
def scan(text: str) -> list[dict]:
hits = []
for name, pat in RULES:
for m in pat.finditer(text):
hits.append({"rule": name, "span": m.span(), "snippet": text[m.start():m.start()+40]})
return hits
def main(argv=None) -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("file")
ap.add_argument("--json", action="store_true")
args = ap.parse_args(argv)
text = Path(args.file).read_text(encoding="utf-8")
hits = scan(text)
if args.json:
print(json.dumps({"ok": not hits, "hits": hits}, ensure_ascii=False, indent=2))
else:
if not hits:
print("OK: 未命中常见机密模式(启发式,非企业 DLP)")
else:
print(f"BLOCK: {len(hits)} hits")
for h in hits:
print(f"- [{h['rule']}] {h['snippet']!r}")
return 1 if hits else 0
if __name__ == "__main__":
raise SystemExit(main())
第 2 步:测试
from pathlib import Path
import subprocess, sys
from paste_guard import scan
HERE = Path(__file__).resolve().parent
def test_safe():
assert scan((HERE/"sample_safe.txt").read_text()) == []
def test_risky():
hits = scan((HERE/"sample_risky.txt").read_text())
assert {h["rule"] for h in hits} >= {"api_key", "private_key"}
def test_cli():
r = subprocess.run([sys.executable, str(HERE/"paste_guard.py"), str(HERE/"sample_safe.txt")], capture_output=True, text=True)
assert r.returncode == 0
第 3 步:运行
$ python paste_guard.py sample_safe.txt
OK: 未命中常见机密模式(启发式,非企业 DLP)
$ python paste_guard.py sample_risky.txt
BLOCK: 3 hits
- [api_key] 'api_key = "sk-proj-THISISFAKESECRETVALUE'
- [private_key] '-----BEGIN PRIVATE KEY-----\nMIIEvgIBADAN'
- [password_assign] 'password: hunter2\n-----BEGIN PRIVATE KEY'
$ pytest -q
... [100%]
3 passed in 0.03s
怎么检查结果?
- safe 退出码 0。
- risky 打印 BLOCK 与规则名。
调试示例
「这是假的 sk- 也拦」。 对:粘贴门禁按形态拦截,不验证真伪。演示密钥同样不该进聊天记录。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 误报 | 放宽规则 | 按团队白名单改 |
| 漏报 | 正常 | 再叠企业 DLP |
| 截图里的密钥 | 本脚本不 OCR | 禁止截图敏感信息 |
模板规则
- 个人版不贴客户机密。
- 命中即改写或删除后再问。
- 企业场景用公司账号。
项目资源
| 用途 | 地址 |
|---|---|
| Copilot 网页 | https://copilot.microsoft.com/ |
| M365 Copilot(介绍) | https://www.microsoft.com/en-us/microsoft-365/copilot |
| 扩展性概览(官方 Learn) | https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/overview |
先扫再贴,少一次密钥进模型的事故。