在物理隔离网络环境里工作过的同学,应该都体会过那种“数据出不去、资料进不来”的别扭感:代码仓库在内部网络里,但你想把一段代码交给外部支持团队排查问题,或者整理项目上下文给分析工具处理,却总是被安全边界挡住。最近看到 Show HN 上的 SiloBrief 项目,它切入的场景非常精准:在 air-gapped networks 中,如何把代码上下文以受控、最小化、可审计的方式选择性导出。本文不打算逐行搬运 SiloBrief 源码,而是围绕这个核心思路,带着你从零手写一个可运行的最小版本,把“选择性导出代码上下文”这件事真正落地。
1. 为什么需要 SiloBrief:物理隔离网络的代码上下文导出需求
1.1 什么是物理隔离网络(Air-gapped Networks)
物理隔离网络,英文通常叫 Air-gapped Network,是指与外部互联网在物理层面断开的内部网络。计算机不能直接通过网线、Wi-Fi 或移动网络访问外网,数据只能通过经过批准的移动介质、专用接口或人工流程进出。这种网络常见于对安全性要求较高的企业内网、研发专网、生产控制网络等场景。
物理隔离的好处是显著缩小了攻击面:外部攻击者无法远程直接访问这些机器。但代价同样明显:开发者在隔离网络里写代码时,没法随手打开搜索引擎查资料,没法直接拉取最新的开源依赖,也没法把代码片段通过剪贴板一键发给外部同事。
1.2 隔离网络开发者的真实痛处
在隔离网络里做开发,最头疼的不是写代码本身,而是“上下文出不去”的问题。
举个例子:你在内网调试一个服务,发现某个模块的报错信息需要查询对应版本的源码才能确定问题根因。你手上只有内网代码,外部知识库、社区讨论、同事的私服全都访问不了。这时候你只能手工把相关文件的内容复制到文档里,再在审批流程走完后,通过合规通道把文档带出去。整个过程既慢又容易出错,而且你根本不敢把整个项目打包带出去——因为大部分文件跟眼前的问题无关,带出去反而增加泄密风险。
另一个常见场景是和外部支持团队协作。你负责维护一个内部系统,问题定位需要外部厂商或分析团队帮忙。对方通常不需要你的全部源码,而是需要“相关的代码片段 + 项目结构 + 依赖信息 + 配置示例”,也就是我们说的“代码上下文”。但如果这些内容靠人工整理,一份材料可能就要准备大半天。
1.3 SiloBrief 解决什么问题
SiloBrief 的核心思路非常直接:与其让开发者手工整理、全量打包,不如提供一个工具,通过配置规则,从项目目录中筛选出满足条件的文件,读取文件内容,生成一份结构化的代码上下文报告。
这个报告只包含你真正需要的文件,而不是整个项目。同时,它可以内置敏感信息脱敏、导出日志、文件哈希校验等功能,让导出过程有据可查。
换句话说,SiloBrief 解决的是三个问题:
- 选择性:只导出符合规则的文件,而不是全量拷贝。
- 受控性:通过配置限制文件类型、大小、数量,避免一次性导出过多数据。
- 可审计性:导出记录、文件哈希、时间、规则版本都可以留存,便于事后追溯。
1.4 一句话总结
SiloBrief 不是让数据“偷偷绕出去”的工具,而是在安全合规要求下,把“代码上下文导出”这个动作标准化、可控化。它让开发者能快速清晰地回答审计问题:导出了什么、为什么导出、谁有权限导出。
2. 代码上下文导出的核心概念
2.1 什么是代码上下文
“代码上下文”这个概念听起来有点抽象,其实可以拆成几层来看:
- 文件内容:最直接的层面,包含源码、配置文件、脚本。
- 项目结构:文件在项目中的相对路径,这决定了模块之间的依赖关系。
- 关键信息:比如依赖清单、构建配置、环境变量示例。
- 辅助数据:行数、文件大小、哈希值、修改时间等元信息,用于确认版本和完整性。
举个实际例子,假设你导出一个main.py,不能只给一段源码,还要说明它在项目中的路径、大概多少行、对应哪个版本、是否被脱敏过。这样接收方才能准确评估这段代码的上下文。
2.2 什么是选择性导出
选择性导出对应的是“全量导出”。全量导出就是把整个项目压缩打包,简单粗暴但风险很大。选择性导出则通过规则引擎,只从项目中挑选符合特定条件的文件。
选择性导出的设计重点在于规则表达能力:
- include:包含哪些路径、哪些文件类型。
- exclude:排除哪些目录,比如
tests、build、.git。 - 文件过滤:按扩展名、大小、数量限制。
- 内容处理:读取文本、脱敏、截断。
好的规则设计能让导出结果“少而精”,既不遗漏关键文件,也不夹带无关数据。
2.3 安全模型与合规边界
在讨论这类工具时,安全边界必须放在首位。SiloBrief 的定位应该是“在授权范围内的受控导出工具”,而不是绕过安全策略的旁门左道。
实际落地时,需要明确以下几点:
- 导出行为必须经过授权审批,由项目负责人或安全团队确认导出范围。
- 导出文件应当标记用途、时间、操作人,便于追溯。
- 敏感信息必须在导出前完成脱敏,比如密码、密钥、内部 IP。
- 导出文件使用完毕后,应按保密要求及时销毁或归档。
这一点很重要:工具本身是中性的,但使用方式决定了它的合规性。文章后续示例也会把这个原则贯穿进去。
3. 环境准备与项目结构
3.1 运行环境说明
我们动手实现一个最小版 SiloBrief。为了降低安装成本,代码完全基于 Python 标准库,不需要额外安装第三方依赖。
- 操作系统:Windows / macOS / Linux 均可,示例以通用命令行方式运行。
- Python 版本:3.8 及以上,推荐 3.10 或更高版本。
- 编辑器:VS Code、PyCharm 或任意文本编辑器。
- 项目路径:本文中
sample-project作为待导出项目,silobrief.py作为导出工具。
版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。
3.2 项目目录结构
我们创建一个演示工程,目录结构如下:
silo-demo/ ├── silobrief.py # 导出工具主程序 ├── config.json # 导出规则配置 ├── sample-project/ # 模拟待导出的隔离网络项目 │ ├── main.py │ ├── README.md │ ├── config/ │ │ └── app.properties │ └── tests/ │ └── test_main.py └── export/ # 导出文件输出目录(自动生成)3.3 是否需要额外依赖
不需要。我们用到的模块包括argparse、json、pathlib、fnmatch、hashlib、re、datetime、sys、typing,这些都是 Python 自带的标准库模块。
这样设计的好处是:在物理隔离网络中,开发者往往无法随意访问外网安装 PyPI 依赖。纯标准库方案可以最大程度保证工具可复制、可运行、可维护。
4. 最小实现:用 Python 写一个 SiloBrief
这一节是整个文章的核心。我们会一步步实现文件收集、规则匹配、内容脱敏、报告导出四个环节。
4.1 设计导出规则文件 config.json
规则文件是整个工具的“大脑”。它决定了收集哪些文件、跳过哪些文件、脱敏哪些内容。
{ "project_root": "sample-project", "output_file": "export/code-context.md", "include_patterns": [ "**/*.py", "**/*.md", "**/*.properties", "**/*.yaml", "**/*.yml" ], "exclude_patterns": [ "**/tests/**", "**/test/**", "**/build/**", "**/target/**", "**/.git/**", "**/node_modules/**", "**/__pycache__/**", "**/*.pyc" ], "exclude_extensions": [ ".png", ".jpg", ".jpeg", ".gif", ".ico", ".jar", ".class", ".zip", ".tar", ".gz" ], "max_file_size_kb": 512, "max_files": 200, "redact_patterns": [ { "pattern": "(?i)(password\\s*[=:]\\s*)\\S+", "replacement": "\\1***REDACTED***" }, { "pattern": "(?i)(token|secret|api[_-]?key)\\s*[=:]\\s*\\S+", "replacement": "\\1***REDACTED***" }, { "pattern": "\\b((?:25[0-5]|2[0-4]\\d|1?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1?\\d?\\d)\\b", "replacement": "***IP-REDACTED***" } ] }关键字段含义:
project_root:要扫描的项目根目录。include_patterns:只有匹配这些 glob 模式的文件才会被导出。exclude_patterns:即使匹配了 include,也会被排除的目录或文件。exclude_extensions:按扩展名排除二进制文件或压缩包。max_file_size_kb:超过该大小的文件不导出,防止把大日志、大二进制带出去。max_files:最多导出多少个文件,防止意外全量导出。redact_patterns:脱敏规则,支持正则替换。
注意 JSON 不支持注释,所以实际使用时不要把注释写进 JSON 文件。
4.2 实现文件收集与过滤逻辑
我们从最核心的文件收集函数开始。目标是遍历project_root,逐层应用过滤规则,最终返回符合条件的文件列表。
#!/usr/bin/env python3 """SiloBrief 最小实现:从物理隔离网络中选择性导出代码上下文。""" import argparse import fnmatch import hashlib import json import re import sys from datetime import datetime from pathlib import Path from typing import Dict, List, Optional def load_rules(rules_path: str) -> dict: """读取 JSON 规则文件。""" with open(rules_path, "r", encoding="utf-8") as f: return json.load(f) def match_path(pattern: str, rel_posix: str) -> bool: """ 判断相对路径是否匹配某个 glob 模式。 说明:fnmatch 的 * 会匹配包括 / 在内的任意字符, 所以 ** 不需要特殊展开。为了兼容 **/xxx 匹配根目录文件, 额外去掉 **/ 前缀再匹配一次。 """ if fnmatch.fnmatchcase(rel_posix, pattern): return True if pattern.startswith("**/"): return fnmatch.fnmatchcase(rel_posix, pattern[3:]) return False def is_excluded(rel_posix: str, rules: dict) -> bool: """命中任意 exclude 规则,则排除。""" for pattern in rules.get("exclude_patterns", []): if match_path(pattern, rel_posix): return True return False def is_included(rel_posix: str, rules: dict) -> bool: """命中任意 include 规则,则收集。""" include_patterns = rules.get("include_patterns", ["**/*"]) for pattern in include_patterns: if match_path(pattern, rel_posix): return True return False def is_hidden_dir(rel_path: Path) -> bool: """跳过 .git、.idea、__pycache__ 等隐藏目录。""" return any(part.startswith(".") for part in rel_path.parts) def collect_files(root: Path, rules: dict) -> List[Path]: """遍历项目目录,收集符合规则的文件。""" max_size_kb = int(rules.get("max_file_size_kb", 1024)) max_files = int(rules.get("max_files", 500)) exclude_exts = rules.get("exclude_extensions", []) files = [] for p in root.rglob("*"): if not p.is_file(): continue rel_path = p.relative_to(root) if is_hidden_dir(rel_path): continue if p.suffix.lower() in exclude_exts: continue rel_posix = rel_path.as_posix() if is_excluded(rel_posix, rules): continue if not is_included(rel_posix, rules): continue if p.stat().st_size > max_size_kb * 1024: continue files.append(p) if len(files) >= max_files: break return files这个流程可以概括为:先“缩小范围”,再“排除干扰”,最后“施加数量限制”。
这里有一个容易被忽略的细节:is_hidden_dir会把所有以.开头的目录都过滤掉,比如.git、.idea。这是有意为之,避免把版本控制目录和 IDE 配置导出到外部。如果项目里确实存在需要导出的隐藏配置文件,可以单独调整这个函数,或者把特定路径加到 include 白名单中。
4.3 实现内容脱敏与上下文生成
文件收集完成之后,下一步就是读取文件内容,生成结构化的上下文对象。这一步要考虑文本编码问题,以及在导出前完成敏感信息脱敏。
def read_text_file(path: Path) -> Optional[str]: """读取文本文件,优先 UTF-8,失败则尝试 GBK。""" for encoding in ("utf-8", "gbk", "latin-1"): try: return path.read_text(encoding=encoding) except UnicodeDecodeError: continue return None def redact_content(content: str, rules: dict) -> str: """根据脱敏规则替换敏感信息。""" for item in rules.get("redact_patterns", []): try: pattern = item["pattern"] replacement = item["replacement"] content = re.sub(pattern, replacement, content) except (re.error, KeyError): continue return content def file_sha256(path: Path) -> str: """计算文件 SHA256,用于完整性校验。""" return hashlib.sha256(path.read_bytes()).hexdigest() def build_context(root: Path, file: Path, rules: dict) -> Optional[dict]: """为单个文件生成上下文对象。""" content = read_text_file(file) if content is None: return None content = redact_content(content, rules) rel_posix = file.relative_to(root).as_posix() return { "path": rel_posix, "line_count": content.count("\n") + 1, "size_bytes": file.stat().st_size, "sha256": file_sha256(file), "content": content, }为什么要给每个文件算 SHA256?因为隔离网络的导出物可能需要人工审批。有了哈希值,验收方可以确认文件在传输过程中没有被篡改。同时,如果接收方手上本来就有该文件,也可以通过哈希快速对账。
4.4 实现 Markdown 导出与审计清单
得到上下文列表后,我们把它导出为一份可读的 Markdown 报告,同时生成一个审计清单。
def export_markdown(items: List[dict], output_path: Path, manifest: dict): """将上下文结果导出为 Markdown 文件。""" lines = [] lines.append("# SiloBrief 代码上下文导出报告\n") lines.append(f"- 导出时间:{manifest['export_time']}") lines.append(f"- 项目根目录:{manifest['project_root']}") lines.append(f"- 导出文件数:{manifest['total_files']}") lines.append(f"- 规则文件 SHA256:{manifest['rules_sha256']}\n") lines.append("---\n") for idx, item in enumerate(items, 1): lines.append(f"## {idx}. {item['path']}\n") lines.append(f"- 行数:{item['line_count']}") lines.append(f"- 大小:{item['size_bytes']} bytes") lines.append(f"- SHA256:{item['sha256']}\n") lang = Path(item["path"]).suffix.lstrip(".") or "text" lines.append(f"```{lang}") lines.append(item["content"]) lines.append("```\n") output_path.parent.mkdir(parents=True, exist_ok=True) output_path.write_text("\n".join(lines), encoding="utf-8") def generate_manifest(root: Path, items: List[dict], rules_path: str) -> dict: """生成审计清单信息。""" rules_file = Path(rules_path) rules_sha256 = hashlib.sha256(rules_file.read_bytes()).hexdigest() return { "tool": "SiloBrief-minimal", "export_time": datetime.now().isoformat(), "project_root": str(root), "total_files": len(items), "rules_path": str(rules_file), "rules_sha256": rules_sha256, "safety_note": "请确保本次导出已获得授权,使用后按保密要求处理导出文件。", }审计清单不一定要单独存储,可以直接放在导出报告头部。这样收到报告的人一眼就能看出导出的来源、时间和文件数量。
4.5 运行与验证
最后是主函数,把整个流程串起来。
def main(): parser = argparse.ArgumentParser( description="SiloBrief: 从物理隔离网络选择性导出代码上下文" ) parser.add_argument("--rules", default="config.json", help="导出规则文件路径(JSON)") parser.add_argument("--output", default="export/code-context.md", help="导出文件路径") args = parser.parse_args() if not Path(args.rules).exists(): print(f"[错误] 规则文件不存在:{args.rules}") sys.exit(1) rules = load_rules(args.rules) root = Path(rules.get("project_root", ".")) if not root.exists(): print(f"[错误] 项目根目录不存在:{root}") sys.exit(1) files = collect_files(root, rules) if not files: print("[提示] 没有匹配到任何文件,请检查 include/exclude 规则。") return items = [] for file in files: ctx = build_context(root, file, rules) if ctx: items.append(ctx) manifest = generate_manifest(root, items, args.rules) output = Path(args.output) export_markdown(items, output, manifest) print(f"[完成] 导出文件数:{len(items)}") print(f"[完成] 输出文件:{output.resolve()}") print(f"[审计] 导出时间:{manifest['export_time']}") if __name__ == "__main__": main()在 sample-project 目录中运行:
python silobrief.py --rules config.json --output export/code-context.md预期输出类似:
[完成] 导出文件数:3 [完成] 输出文件:/path/to/silo-demo/export/code-context.md [审计] 导出时间:2025-02-20T10:30:00.123456由于我们排除了tests目录,所以test_main.py不会被导出。导出的应该是main.py、README.md、config/app.properties三个文件。
打开生成的 Markdown,你会看到类似这样的结构:
# SiloBrief 代码上下文导出报告 - 导出时间:2025-02-20T10:30:00.123456 - 项目根目录:sample-project - 导出文件数:3 - 规则文件 SHA256:xxxxxxxxxxxxxxxxxxxx --- ## 1. main.py - 行数:18 - 大小:342 bytes - SHA256:yyyyyyyyyyyyyyyyyyyy ```python # main.py 示例内容 def hello(): print("hello silo")如果 `main.py` 里有类似 `password = "123456"` 这样的内容,导出内容会变成:password =REDACTED
这就完成了最基本的敏感信息脱敏。 ## 5. 导出规则进阶:如何设计精细的上下文收集策略 上面的最小实现已经能支撑简单场景。但在真实业务中,规则设计还可以更精细。 ### 5.1 用 include / exclude 控制收集范围 include 和 exclude 是一对互补规则。include 决定“默认要什么”,exclude 决定“从中剔除什么”。 一个比较实用的策略是:先把项目所有文本文件都视为候选,然后用 exclude 剔除明显不需要的目录,最后用 include 只保留和主题相关的文件类型。 例如: ```json { "include_patterns": ["**/*.java", "**/*.xml", "**/*.yml"], "exclude_patterns": ["**/target/**", "**/.git/**", "**/test/**"] }这样导出的范围被压缩得比较小,接收方拿到手后可以直接定位到业务代码和配置文件。
5.2 设置大小与数量上限
在隔离网络场景下,导出文件不能无限大。建议设置两个上限。
max_file_size_kb:单文件超过该大小就不导出。这可以避免把几十 MB 的日志文件或测试数据带出去。max_files:最多导出文件数。一旦达到上限,遍历停止,避免失控。
这两个参数配合使用,可以让导出结果保持“轻量”。
5.3 敏感信息脱敏规则
脱敏不要只依赖一两条正则,因为代码里的敏感信息格式千差万别。
至少要考虑以下几类:
- 密码:
password = xxxx、passwd: xxxx - 令牌:
token、secret、api_key、apikey - 连接串:数据库连接字符串中的账号密码部分
- IP 地址:内网 IP、公网 IP
- 邮箱和手机号:如果代码里存在测试用户数据
更稳妥的做法是在导出前再做一次全量扫描,确认脱敏没有遗漏。如果发现导出报告里仍有敏感信息,应该立即停止分发并重新导出。
5.4 审计链路:谁能导出、导出了什么、为什么导出
工具能生成 audit 信息,但完整审计链路还需要配合人工流程:
- 操作人提出导出申请,说明目的。
- 负责人审核导出范围和规则配置。
- 执行工具,生成导出报告和审计清单。
- 验收人检查报告内容,确认无敏感信息。
- 登记台账,记录导出时间、文件数、用途。
- 使用结束后,按保密要求归档或销毁导出文件。
这些步骤不需要全写进代码,但应当在制度上固定下来。
6. 常见问题与排查思路
在实际使用这个工具时,大家可能会遇到下面这些问题。我整理了一张排查清单,方便对照。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 导出报告为空 | include_patterns 写错,或者 exclude 规则误伤了所有文件 | 先删除 include_patterns,用**/*测试能否收集文件;再逐步增加 exclude 规则定位问题 |
| 中文内容乱码 | 文件编码不是 UTF-8 | 代码会尝试 GBK、Latin-1;如果仍乱码,可以手动修改read_text_file的编码顺序 |
| JSON 解析报错 | 配置文件里写了注释或尾逗号 | JSON 标准格式不支持注释和尾逗号,删除后重新运行 |
| 某些文件一直没被导出 | 文件在隐藏目录里,或大小超过max_file_size_kb | 检查is_hidden_dir逻辑;临时调大上限验证是否为大小问题 |
| 导出的代码里仍有敏感信息 | 脱敏正则没有覆盖对应格式 | 在redact_patterns中补充规则,并对导出报告做二次人工检查 |
| Windows 下路径匹配不到 | fnmatch对路径分隔符敏感,Windows 路径反斜杠不会被/匹配 | 代码中统一使用as_posix()将路径转换为/风格;若仍不行,考虑用Path.match()代替 |
如果排错时不确定是规则问题还是代码问题,可以在collect_files里临时打印每个被排除文件的路径和原因,这样能快速定位是哪一个环节拦截了文件。
7. 最佳实践与工程建议
7.1 最小导出原则
导出代码上下文时,原则永远是“能少则少”。不要因为操作简单就整包导出,也不要为了方便一次性把所有源码都带出去。严格来说,每次导出都应当只包含解决当前问题所必需的文件。
7.2 规则配置纳入版本管理
config.json不应该只是本机临时文件,而应当纳入版本管理。这样你可以追溯某次导出使用的是哪一版规则,也便于团队成员复用和评审。
7.3 脱敏规则要持续补充
敏感信息格式多种多样,一次性写全所有正则不太现实。建议每次导出后复盘:这次报告里有没有新的敏感信息格式?如果有,就把对应规则补充到redact_patterns中,形成自己的敏感信息规则库。
7.4 导出文件生命周期管理
导出后的 Markdown 文件也属于敏感数据。建议做到:
- 通过加密压缩包存储。
- 使用专用介质或经批准的通道分发。
- 设置有效期限,过期自动删除或归档。
- 所有操作记录纳入台账。
7.5 将 SiloBrief 接入 AI 分析流程
很多团队希望用 AI 助手分析代码上下文。如果业务流程允许,可以把 SiloBrief 生成的报告作为输入,交给内部部署或经过授权的分析服务再处理。这样做的好处是:原始代码不会直接暴露给无关人员,AI 拿到的已经是最小化、脱敏后的上下文。
接入时要注意:脱敏后的代码可能丢失部分语义,分析结果可能不准确。所以建议在报告关键位置添加“已脱敏”标记,提醒阅读方注意。
8. 总结与下一步学习方向
写到这里,最小版 SiloBrief 的完整实现已经跑通了。我们从需求背景开始,分析了物理隔离网络中代码上下文导出的难点,设计了一个 JSON 规则文件,给出了文件收集、过滤、脱敏、导出、审计的完整 Python 实现。
如果接下来想继续深入,可以从这几个方向入手:
- 给工具增加 TUI 或 Web 界面,方便非技术同事使用。
- 支持导出 JSON Lines 或加密 ZIP,便于程序化处理。
- 增加增量导出能力,只导出自上次以来变更的文件。
- 接入依赖分析工具,自动生成项目依赖清单。
- 做一个导出报告的离线阅读器,支持搜索和比对。
实际项目中,优先关注的点仍然是安全边界:导出前授权、导出时脱敏、导出后留痕。希望这篇文章能帮你把“选择性导出代码上下文”这个需求落地成一套清晰可控的流程。