From 9374986b63129732cca9c1fde40c1303e51ae132 Mon Sep 17 00:00:00 2001 From: weichen Date: Mon, 5 Oct 2026 13:45:58 +0000 Subject: [PATCH] Lock CI checkout credential scope to the injected server and allowlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: 复核轮遗留的安全测试草稿按「常量=传输端点」设计,与 PR #213 已合入的 「注入基址=传输端点」语义相反,原样提交会红。适配为锁定真实不变量。 Evidence: python3 -m unittest tests.test_ci_credentials 8/8 OK(含新用例: 任一注入拼写检出成功、凭据仅落白名单路径且令牌分离、非 http(s) 注入 fail-closed)。 Impact: 仅测试文件;不改 ci.yaml 行为。 Refs: PR #213 run 950 诊断(GITHUB_SERVER_URL=http://172.17.0.1:3000)。 --- tests/test_ci_credentials.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/test_ci_credentials.py b/tests/test_ci_credentials.py index 39c533e8..d32ec708 100644 --- a/tests/test_ci_credentials.py +++ b/tests/test_ci_credentials.py @@ -122,17 +122,45 @@ class CheckoutTests(unittest.TestCase): self.git(self.base, "clone", "-q", "--bare", str(source), str(target)) return sha - def run_checkout(self, sha): + def run_checkout(self, sha, metadata_url=None): # Only the fixed transport endpoint changes; execute the actual workflow # shell/Python logic, not a test-specific implementation of checkout. script = checkout_script().replace(SERVER, self.url) return subprocess.run(["bash", "-c", script], cwd=self.checkout, env=self.env | {"TOKEN": ROOT_TOKEN, "SUBMODULE_TOKEN": SUB_TOKEN, - "GITHUB_SERVER_URL": self.url, + "GITHUB_SERVER_URL": metadata_url or self.url, "GITHUB_REPOSITORY": "weichen/Agent-System-Dev", "GITHUB_SHA": sha}, capture_output=True, text=True, timeout=30) + def test_credential_destination_confined_to_injected_server(self): + # 合入语义(PR #213,2026-10-05):传输基址 = 运行器注入的 GITHUB_SERVER_URL + # (run 950 实测为内网 http 形态 http://172.17.0.1:3000),凭据只发往 + # 「该基址 + 仓库白名单」路径。锁定三点:任一注入拼写(含/不含尾斜杠)检出 + # 成功;每个带凭据请求都落在白名单仓库路径且 root/子仓令牌分离;注入值 + # 非 http(s) 时 fail-closed 拒绝——不回落到任何其他端点、不发凭据。 + core = self.publish("Agent-System-Core") + root = self.publish("Agent-System-Dev", [("modules/core", "Agent-System-Core", core, None)]) + for index, injected in enumerate((self.url, self.url + "/")): + with self.subTest(injected=injected): + self.checkout = self.base / f"injected-checkout-{index}" + self.checkout.mkdir() + self.requests.clear() + result = self.run_checkout(root, metadata_url=injected) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(self.git(self.checkout / "modules/core", "rev-parse", "HEAD"), core) + self.assertTrue(self.requests) + for path, auth in self.requests: + self.assertRegex(path, r"^/weichen/Agent-System-(Dev|Core)\.git/(info/refs|git-upload-pack)$") + self.assertEqual(auth, "token " + (ROOT_TOKEN if "Agent-System-Dev.git/" in path else SUB_TOKEN)) + self.checkout = self.base / "injected-checkout-refused" + self.checkout.mkdir() + self.requests.clear() + result = self.run_checkout(root, metadata_url="git-panel.u1680832.nyat.app:30319") + self.assertNotEqual(result.returncode, 0) + self.assertFalse(self.requests) + + def test_candidate_cannot_read_checkout_credentials(self): sha = self.publish("Agent-System-Dev") result = self.run_checkout(sha)