2026/9/13 4:42:59

Beads 测试/生产隔离防火墙深度解析:isProductionPort 端口检测与数据库名双保险机制(AD-01 / be-c5p)

Beads 测试/生产隔离防火墙深度解析:isProductionPort 端口检测与数据库名双保险机制(AD-01 / be-c5p) Beads 测试/生产隔离防火墙深度解析isProductionPort 端口检测与数据库名双保险机制AD-01 / be-c5p【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beadsBeads 是一个为编码 Agent 提供记忆层的开源项目其存储层以 Dolt SQL 服务器为底座。测试代码与生产数据共用同一套 Dolt 服务器时最危险的事故莫过于测试数据库被创建或连接到了生产端口。本篇文章围绕仓库内发布门禁文档 release-gates/be-c5p-firewall-gate.md 所评审的 AD-01 功能完整拆解 Beads 如何通过isProductionPort端口检测 数据库名模式匹配这套双保险防火墙将测试流量与生产数据彻底隔离读完你将对三条生产端口探测规则、BEADS_TEST_SERVER1显式逃生口、以及三道防线在源码中的确切位置有完整认识并能据此在自己的环境中安全配置生产端口与运行测试套件。一、为什么要一道防火墙测试污染事故的背景Beads 的存储后端是 internal/storage/dolt/store.go 中实现的 DoltStore它连接一个 Doltsql-server按数据库database组织 IssueOps 业务数据。测试套件集成测试、属性测试、版本往返测试、bd doctor自检等也会在服务器上创建自己的数据库——它们以固定的前缀命名如testdb_、beads_test、doctest_。问题在于测试数据库名与生产数据库名都只是字符串。一旦配置漂移——比如测试进程拿到的端口解析成了生产端口或者生产进程误用了测试命名约定——测试数据就会写入生产库或者生产代码被影子数据库污染。门禁文档将其归纳为 Clown Shows #12-#18 这一系列事故类型。be-c5pAD-01要解决的问题正是不依赖人的自律在代码层面用两道互相独立的检查把测试命名与生产端口钉死。评审结论为PASS改动规模为单个 commit064a9fa915 个文件195 / −10干净地 cherry-pick 到origin/main之上。二、整体架构三道防线而非一道从 internal/storage/dolt/store.go 的源码结构看这套隔离体系由三道独立防线组成每道防线都有独立的判定逻辑与逃生口防线位置触发方式失败行为第一道生产端口硬守卫store.go:1564-1566BEADS_TEST_MODE1且isProductionPort(cfg)panicfail-loud第二道数据库名防火墙store.go:1568-1582New()内库名命中测试前缀且BEADS_TEST_SERVER ! 1返回error可断言第三道建库最后防线store.go:2444-2449建库路径内库名命中测试前缀且端口为生产端口返回errorREFUSED三道防线共享两个判定原语isTestDatabaseName模式匹配与isProductionPort端口探测。下面逐一拆解。三、端口探测productionPortReasons的三条规则isProductionPort(cfg)的完整实现位于 internal/storage/dolt/store.go#L130-L176它本身只是一个包装return len(productionPortReasons(cfg)) 0。真正承载逻辑的是productionPortReasons它按顺序检查三个信号命中即追加一条可读的原因标签最终用于拼装 panic/error 消息方便排障。规则 1等于默认 SQL 端口 3307 —— 无条件、不可豁免const DefaultSQLPort 3307 // store.go:56只要cfg.ServerPort DefaultSQLPort立即判定为生产端口。这条规则在任何情况下都不被抑制——包括BEADS_TEST_SERVER1。理由在源码注释中写得很直白3307 是 Dolt 众所周知的默认端口是单一置信度最高的生产信号一个专门跑测试的服务器即便通过BEADS_TEST_SERVER1退出了其他启发式规则也绝不允许绑定到这个众所周知的默认端口。对应的单元测试 store_unit_test.go#L316-L356TestApplyConfigDefaults_TestModeBlocksProdPort_EvenWithTestServerOptIn专门验证了这一点即使设置了BEADS_TEST_SERVER1只要BEADS_TEST_MODE1且端口解析回 3307测试模式端口仍被改写源码注释称其为 Rule 1 is unconditional。规则 2BEADS_PRODUCTION_PORT环境变量如果环境变量BEADS_PRODUCTION_PORT能被解析为大于 0 的整数且恰好等于cfg.ServerPort则判定为生产端口if env : os.Getenv(BEADS_PRODUCTION_PORT); env ! { if p, err : strconv.Atoi(env); err nil p 0 p cfg.ServerPort { reasons append(reasons, fmt.Sprintf(BEADS_PRODUCTION_PORT%d matches, p)) } }这为非 3307 的自定义生产部署提供了显式声明手段生产环境把实际端口写入该变量即可让通用化的isProductionPort覆盖到任何端口。规则 3BeadsDir/dolt-server.port磁盘端口文件如果cfg.BeadsDir非空且该目录下的端口文件doltserver.PortFileName读取函数doltserver.ReadPortFile内容与cfg.ServerPort一致则判定为生产端口。这条规则有一个非常值得学习的边界设计当BeadsDir为空时绝不回退到filepath.Dir(cfg.Path)去推断目录。原因注释解释得很清楚端口解析链applyConfigDefaults确实会做这个回退但用于生产判定时回退会把恰好包含一个游离dolt-server.port文件的目录例如/tmp/dolt-server.port来自某个泄漏的 dev server误判为生产。而测试夹具经常把cfg.Path放在/tmp下、并没有真正的BeadsDir——只有显式设置的BeadsDir才被当作生产判定的权威依据。规则 2/3 的统一豁免BEADS_TEST_SERVER1三条规则并非同等权重。规则 1 无条件规则 2、3 是启发式环境变量或磁盘文件都可能过期或误配因此当BEADS_TEST_SERVER1时被跳过if os.Getenv(BEADS_TEST_SERVER) 1 { return reasons }也就是说操作者显式选择专用测试服务器通道如每测试一个容器、独立外部测试端口时系统尊重这一选择但固定默认端口仍然不可触碰。同时注意严格的1比较——文档与源码都强调这是严格 opt-in不是truthy判断杜绝了BEADS_TEST_SERVERyes这类意外放行。四、数据库名防火墙testDatabasePrefixes与isTestDatabaseName前缀清单internal/storage/dolt/store.go#L79-L87 定义了七个测试前缀var testDatabasePrefixes []string{ testdb_, // applyConfigDefaults 为 BEADS_TEST_MODE1 且未显式指定 Database 时派生cfg.Path 的 FNV 哈希 beads_test, // 手写集成测试的命名约定 beads_pt, // 属性测试夹具 beads_vr, // 版本往返 / 迁移夹具 doctest_, // bd doctor 自检夹具 doctortest_, // 较旧的 bd doctor 夹具名向后兼容保留 benchdb_, // 基准测试临时库格式 benchdb_unixnano由 AD-01 (be-c5p) 新增 }其中benchdb_正是本门禁新增的一行基准测试cmd/bd/template_test.go的newTemplateBenchmarkStore每次跑出的临时库形如benchdb_unixnano此前不在防火墙清单内。模式匹配纯前缀、不依赖环境变量func isTestDatabaseName(name string) bool { for _, prefix : range testDatabasePrefixes { if strings.HasPrefix(name, prefix) { return true } } return false }源码注释特别强调这是pattern-based 防火墙不依赖环境变量——因为环境变量可能被误配或缺失而数据库名本身是既成事实。前缀匹配能避免SHOW DATABASES LIKE把_、%当通配符的问题建库路径中用精确匹配遍历SHOW DATABASES的原因之一。单元测试 dolt_test.go#L1756-L1786TestIsTestDatabaseName给出了关键边界beads_test、beads_test_123、testdb_foo、doctest_bar、beads_pt_xyz、beads_vr_abc均为true而真正的生产库前缀beads_ta、beads_tabula、beads_tr、beads_tools、beads_vulcan必须为false——这保证像beads_vulcan这样的真实项目库不会被误伤。五、第二道防线细节New()拒绝测试名数据库在 internal/storage/dolt/store.go#L1568-L1582New()打开任意服务器生产或测试前执行if isTestDatabaseName(cfg.Database) os.Getenv(BEADS_TEST_SERVER) ! 1 { addr : net.JoinHostPort(cfg.ServerHost, strconv.Itoa(cfg.ServerPort)) if cfg.ServerSocket ! { addr cfg.ServerSocket } return nil, fmt.Errorf( refusing to connect test database %q to server %s: set BEADS_TEST_SERVER1 on a dedicated test server, or use test helpers in internal/storage/dolt/testserver, cfg.Database, addr) }要点返回error而非panic门禁文档明确此为设计选择——让测试可以断言错误路径。错误信息自解释同时给出被拒的库名与目标地址host:port或 socket并给出两条出路在专用测试服务器上设BEADS_TEST_SERVER1或使用internal/storage/dolt/testserver的测试辅助。独立于端口判定isProductionPort的BEADS_TEST_SERVER1豁免不影响此处的独立 opt-out——注释明确二者是 AD-01 的两个独立防线互不牵连。六、第三道防线建库路径上的最终拦截在真正的CREATE DATABASE执行前store.go#L2439-L2449有最后一层拦截if isTestDatabaseName(cfg.Database) isProductionPort(cfg) { return nil, , serverConnFacts{}, fmt.Errorf( REFUSED: will not CREATE DATABASE %q on production port %d — this is a test database name on the production server (see DOLT-WAR-ROOM.md), cfg.Database, cfg.ServerPort) }它是最后防线last line of defense即使前面的New()防火墙被绕过例如通过CreateIfMissing建库路径直达只要测试命名 生产端口同时成立建库即被 REFUSED。这里再次强调 pattern-based 而非 env-var-based并把生产端口判定通用化为isProductionPort从而覆盖非 3307 的生产部署AD-01 的通用化目标。此拦截位于ValidateDatabaseName防注入校验之后、databaseExistsOnServer存在性检查之前与beads_ta/beads_vulcan等项目前缀互不冲突。七、配套硬守卫BEADS_TEST_MODE1下的端口 panic除了上面三条生产路径防线还有一条测试路径上的硬守卫store.go#L1564-L1566if os.Getenv(BEADS_TEST_MODE) 1 isProductionPort(cfg) { panic(buildTestModeProductionPortPanic(cfg)) }applyConfigDefaults在BEADS_TEST_MODE1时会把生产端口改写成 1fail-loud-but-continue这里的 panic 是纵深防御defense-in-depth捕获任何绕过或事后篡改改写结果的路径。panic 消息由buildTestModeProductionPortPanicstore.go#L1622-L1634构建遵循 AD-01 Wireframe 1 格式可扫描的头部 database/path/server 字段 命中的规则列表 命名每个逃生口的修复块。当 panic 消息中规则列表为空时理论不可达也有兜底文案。八、测试基础设施的适配13 个 TestMain 全部显式 opt-in新增防火墙之后所有会连接测试服务器的测试套件都必须显式设置BEADS_TEST_SERVER1否则一律被New()拒绝。门禁文档确认全部 13 个 testmain 文件已更新grep 验证。以 internal/storage/dolt/testmain_test.go#L76-L79 为例// AD-01 (be-c5p): the test/bench harness opens a process-local dolt // sql-server (testcontainer or external port). The new database-name // firewall in dolt.New refuses test-named DBs unless this opt-in is set. os.Setenv(BEADS_TEST_SERVER, 1)该 TestMain 还展示了配套的隔离细节设置BEADS_TEST_PDEATHSIG1保证服务器进程生命周期绑定、为孤儿服务器清扫创建 suite 独有的临时根目录、把熔断状态隔离到专属目录。cmd/bd/doctor/fix/testmain_cgo_test.go在清理阶段主动 unset 该变量cmd/bd/context_binding_integration_test.go则因为该测试套件会过滤BEADS_*环境变量需要把该变量显式放进子进程Cmd.Env。九、前缀三表汇聚防火墙、清理与公式必须收敛防火墙清单不是孤立的一张表。testDatabasePrefixes与清理侧的staleDatabasePrefixescmd/bd/dolt.go#L1407-L1415以及 formula 侧的 stale-prefix 列表必须保持收敛否则新增前缀会导致陈旧夹具逃过清理防火墙侧internal/storage/dolt/store.go:testDatabasePrefixesSQL 侧拦截清理侧cmd/bd/dolt.go:staleDatabasePrefixes由bd dolt clean-databasescmd/bd/dolt_clean_databases.go使用cmd/bd/doctor/server.go的bd doctor也有同名列表公式侧gc dolt cleanup的 stale-prefix 列表门禁文档提及由 be-avn 处理汇聚。三个列表的注释相互引用如dolt.go:1387-1407明确写出防火墙列表、清理列表、公式列表必须收敛并且benchdb_已同步加入两边 Go 列表。任何新测试前缀都应遵循同一纪律三处同时更新。十、测试验证矩阵门禁与源码共同确认的测试覆盖测试位置验证点TestApplyConfigDefaults_TestModeBlocksProdPortstore_unit_test.go#L281BEADS_TEST_MODE1且未设BEADS_TEST_SERVER时生产端口被改写TestApplyConfigDefaults_TestModeBlocksProdPort_EvenWithTestServerOptInstore_unit_test.go#L316-L356即使BEADS_TEST_SERVER1规则 13307依然生效TestIsTestDatabaseNamedolt_test.go#L1756-L1786七个测试前缀命中、真实项目前缀beads_vulcan等不命中TestValidateDatabaseNamedolt_test.go#L1730-L1754库名防注入反引号、分号、空格、超长、空名均拒绝门禁文档给出的发布证据中最高风险用例TestApplyConfigDefaults_TestModeBlocksProdPort在干净环境下于internal/storage/dolt包内通过ok 0.092s。另有评审遗留的中等级 follow-upbe-z62为规则 2/3、BEADS_TEST_SERVER1短路、New()返回 error 路径补 8 个建议单元测试与低等级描述性 nit均不阻塞发布。十一、发布门禁评审结论与环境说明门禁文档的 Verdict 为PASS六项判据全部满足评审 PASS 存在0 blocker / 0 high / 1 medium / 1 low四项验收条件全部达成见下文测试通过且零回归——make testgms_pure_go在release/be-c5p-firewall与origin/main上产生完全一致的 35 个失败集合无 HIGH 级未关闭问题最终分支git status干净未跟踪的.gc/、.gitkeep属于 gc 管理基础设施分支干净分叉单个 commit064a9fa9cherry-pick 到origin/main8694c535上零冲突。四项验收条件即前文所述机制的可执行版本isProductionPort同时尊重BEADS_PRODUCTION_PORT环境变量、BeadsDir/dolt-server.port端口文件、以及遗留DefaultSQLPortNew()拒绝测试命名库除非BEADS_TEST_SERVER1严格1opt-intestDatabasePrefixes新增benchdb_全部 13 个测试套件在TestMain中设置BEADS_TEST_SERVER1。关于那 35 个失败的说明环境耦合非本改动引入门禁文档特别澄清deployer 工作树 rig 环境下make test报告的 35 个失败在origin/main上同样存在属于工作树/HOME 耦合的配置类测试集中在internal/config、cmd/bd、internal/beads——它们会拾取 rig 会话配置与BEADS_DIR。internal/storage/dolt在环境未清洗时也会因 rig 自身 dolt 服务器注入的BEADS_DOLT_SERVER_PORT而使端口解析测试受影响。清洗BEADS_DOLT_SERVER_PORT、BEADS_DOLT_AUTO_START、BEADS_DIR、GC_DOLT_PORT、GC_BEADS_SCOPE_ROOT、BEADS_ACTOR即可清除该子集这些失败在 builder/reviewer rig 上不出现builder 报告 source 分支 build/vet/tests 干净reviewer 确认make testPASS。判据 #3 真正要求的是零新增失败而非绝对零失败。十二、落地后的操作指引配置生产端口生产部署中若 Dolt 端口不是默认 3307设置BEADS_PRODUCTION_PORT端口环境变量或在BeadsDir下维护dolt-server.port文件isProductionPort即可覆盖到该端口默认 3307 无需任何配置即被识别。运行测试套件任何打开进程内/容器化 Dolt 测试服务器的套件必须在TestMain或等效入口中设置BEADS_TEST_SERVER1且该变量会从父进程环境被过滤时需显式注入子进程Cmd.Env。生产服务器上绝不设置此变量。新增测试库前缀向testDatabasePrefixesinternal/storage/dolt/store.go#L79追加前缀时必须同步更新清理侧staleDatabasePrefixescmd/bd/dolt.go#L1407、cmd/bd/doctor/server.go#L168与公式侧列表保证防火墙与bd dolt clean-databases、gc dolt cleanup语义一致。十三、小结AD-01be-c5p为 Beads 的 Dolt 存储层装上了一套不依赖纪律的隔离机制productionPortReasons用三条规则固定默认端口 环境变量 端口文件识别生产端口isTestDatabaseName用七个前缀识别测试库名二者组合出New()拒绝连接、建库路径 REFUSED、测试模式端口 panic 三道防线配合 13 个测试套件的BEADS_TEST_SERVER1显式 opt-in 与前后缀三表收敛纪律从代码层面根除了测试库连上生产服务器这一类事故。对需要在自己项目中建立测试/生产数据隔离的开发者而言这套模式匹配 多信号探测 独立逃生口 纵深防御的组合拳是一个可以直接借鉴的参考范式。延伸阅读发布评审全文见 release-gates/be-c5p-firewall-gate.md核心实现见 internal/storage/dolt/store.go清理侧汇聚见 cmd/bd/dolt.go端口文件读取与共享服务器模式见 internal/doltserver。【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考