Files
Agent-System-Dev/tests/test_ci_credentials.py
T
weichen adb6cbcf48 Keep fixed checks and lifecycle fixtures isolated in CI (U5)
Why:
Go-to-Python tests leave ignored bytecode in fixed sources, lifecycle clones need a fixed main reference, and boundary gates require ripgrep.

Evidence:
SELF real byproduct and clone regressions failed before repair; host passed 11 component cases, 4 hook cases and 33 CI credential cases.

Impact:
Execute versioned Go checks in disposable clones while preserving editable overlays and strict dirty guards; install the boundary prerequisite and seed only isolated pinned lifecycle refs.

Refs:
R8-R10, R14-R15; dependency-workspace U5; Dev PR 537 run 3036
2026-10-14 02:29:36 +00:00

369 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Execute the workflow checkout against real disposable HTTP Git repositories."""
import http.server
import hashlib
import json
import os
from pathlib import Path
import re
import subprocess
import tempfile
import textwrap
import threading
import unittest
from urllib.parse import urlsplit
WORKFLOW = Path(__file__).resolve().parents[1] / ".gitea/workflows/ci.yaml"
SERVER = "https://git-home.u1680832.nyat.app:30319"
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
ROOT_TOKEN = "synthetic-root-token-not-a-real-secret"
SUB_TOKEN = "synthetic-component-token-not-a-real-secret"
COMPONENT_REPOSITORIES = (
"Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native", "Agent-Intent",
"Agent-Command", "Agent-Squad", "Agent-System-Backend", "Agent-System-UI",
"Agent-System-Frontend", "Agent-System-RDM",
)
COMPONENT_FIXTURES = Path(__file__).resolve().parent / "fixtures/component-ci"
def component_workflow(repository, *, snapshots=COMPONENT_FIXTURES, source_root=None):
"""Read committed test input; never use these snapshots for real acquisition.
Optional COMPONENT_CI_SOURCE_ROOT verifies the recorded immutable Git object
and current source HEAD. An archive requires only its committed fixture set.
"""
if repository not in COMPONENT_REPOSITORIES:
raise ValueError("Unknown component workflow fixture")
provenance = json.loads((snapshots / "provenance.json").read_text())
if provenance.get("schema") != 1 or set(provenance.get("components", {})) != set(COMPONENT_REPOSITORIES):
raise ValueError("Component workflow provenance must cover every reviewed repository")
record = provenance["components"][repository]
if (set(record) != {"repository", "source_sha", "workflow_path", "workflow_sha256"} or
record["repository"] != SSH + repository + ".git" or
record["workflow_path"] != ".gitea/workflows/ci.yml" or
not re.fullmatch(r"[0-9a-f]{40}", record["source_sha"]) or
not re.fullmatch(r"[0-9a-f]{64}", record["workflow_sha256"])):
raise ValueError("Invalid fixed component workflow provenance")
path = snapshots / repository / "ci.yml"
if path.is_symlink() or hashlib.sha256(path.read_bytes()).hexdigest() != record["workflow_sha256"]:
raise ValueError("Component workflow snapshot differs from its recorded source digest")
source_root = source_root or os.environ.get("COMPONENT_CI_SOURCE_ROOT")
if source_root:
source = Path(source_root) / repository
head = subprocess.check_output(["git", "-C", str(source), "rev-parse", "HEAD"], text=True).strip()
if head != record["source_sha"]:
raise ValueError("Component source HEAD differs from fixture provenance")
captured = subprocess.check_output(["git", "-C", str(source), "show",
record["source_sha"] + ":" + record["workflow_path"]])
if captured != path.read_bytes():
raise ValueError("Component workflow snapshot differs from its immutable Git source")
return path
def workflow_script(workflow, name):
"""Extract an inline workflow run block, resolving workflow-local YAML aliases."""
lines = workflow.read_text().splitlines()
start = next((i for i, line in enumerate(lines) if line.strip() == "- name: " + name), None)
if start is None:
raise AssertionError(f"Workflow has no executable phase: {name}")
run = next(i for i in range(start, len(lines)) if lines[i].strip().startswith("run: "))
value = lines[run].strip().removeprefix("run: ")
if value.startswith("*"):
run = next(i for i, line in enumerate(lines) if line.strip() == "run: &" + value[1:] + " |")
value = "|"
if value != "|" and not (value.startswith("&") and value.endswith(" |")):
return value
start = run + 1
end = next((i for i in range(start, len(lines))
if lines[i].strip() and not lines[i].startswith(" ")), len(lines))
return textwrap.dedent("\n".join(lines[start:end]))
def checkout_script():
return workflow_script(WORKFLOW, "Checkout")
def rdm_clone_script():
return workflow_script(WORKFLOW, "Clone Agent-System-RDM with nested pins (reviewed allowlist addition)")
class CheckoutTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.base = Path(self.temp.name)
self.published = self.base / "published"
self.published.mkdir()
self.home = self.base / "home"
self.home.mkdir()
self.checkout = self.base / "checkout"
self.checkout.mkdir()
self.env = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "GITHUB_")) and k not in {"TOKEN", "SUBMODULE_TOKEN"}}
self.env.update(HOME=str(self.home), GIT_CONFIG_NOSYSTEM="1", GIT_TERMINAL_PROMPT="0")
self.requests = []
self.redirect_repo = None
outer = self
class Handler(http.server.BaseHTTPRequestHandler):
def log_message(self, *args):
pass
def do_GET(self):
self.respond()
def do_POST(self):
self.respond()
def respond(self):
parsed = urlsplit(self.path)
outer.requests.append((parsed.path, self.headers.get("Authorization")))
if outer.redirect_repo and outer.redirect_repo in parsed.path:
self.send_response(302)
self.send_header("Location", outer.url + "/untrusted/receiver")
self.end_headers()
return
env = outer.env | {"GIT_PROJECT_ROOT": str(outer.published), "GIT_HTTP_EXPORT_ALL": "1",
"PATH_INFO": parsed.path, "QUERY_STRING": parsed.query,
"REQUEST_METHOD": self.command,
"CONTENT_TYPE": self.headers.get("Content-Type", ""),
"CONTENT_LENGTH": self.headers.get("Content-Length", "0")}
body = self.rfile.read(int(env["CONTENT_LENGTH"]))
result = subprocess.run(["git", "http-backend"], input=body, env=env, capture_output=True)
headers, body = result.stdout.split(b"\r\n\r\n", 1)
pairs = [line.decode().split(": ", 1) for line in headers.split(b"\r\n")]
status = next((int(value.split()[0]) for key, value in pairs if key == "Status"), 200)
self.send_response(status)
for key, value in pairs:
if key != "Status":
self.send_header(key, value)
self.end_headers()
self.wfile.write(body)
self.http = http.server.ThreadingHTTPServer(("127.0.0.1", 0), Handler)
self.url = f"http://127.0.0.1:{self.http.server_port}"
# Any regression that tries a production HTTPS URL must fail locally.
self.env.update(https_proxy=self.url, HTTPS_PROXY=self.url,
no_proxy="127.0.0.1,localhost", NO_PROXY="127.0.0.1,localhost")
thread = threading.Thread(target=self.http.serve_forever, daemon=True)
thread.start()
self.addCleanup(self.http.server_close)
self.addCleanup(self.http.shutdown)
def git(self, directory, *args):
return subprocess.run(["git", "-C", str(directory), *args], env=self.env,
check=True, capture_output=True, text=True).stdout.strip()
def publish(self, name, children=(), extra_gitmodules=""):
source = self.base / name
source.mkdir()
self.git(source, "init", "-q", "-b", "main")
(source / "README").write_text("Fixture repository\n")
# Candidate code tries the channels available after the checkout step.
(source / "probe.py").write_text(textwrap.dedent(f"""\
import os, pathlib, subprocess
sources = [repr(dict(os.environ)), subprocess.check_output(['git', 'config', '--list'], text=True)]
for path in [pathlib.Path.home() / '.gitconfig', *pathlib.Path('.git').rglob('config')]:
if path.is_file(): sources.append(path.read_text())
assert not any(secret in source for secret in ({ROOT_TOKEN!r}, {SUB_TOKEN!r}) for source in sources), 'credential readable by candidate'
print('candidate cannot read checkout credentials')
"""))
gitmodules = ""
for path, repo, sha, override in children:
gitmodules += f'[submodule "{path}"]\n\tpath = {path}\n\turl = {override or SSH + repo + ".git"}\n'
if gitmodules or extra_gitmodules:
(source / ".gitmodules").write_text(gitmodules + extra_gitmodules)
self.git(source, "add", ".")
for path, repo, sha, override in children:
self.git(source, "update-index", "--add", "--cacheinfo", f"160000,{sha},{path}")
self.git(source, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid",
"commit", "-q", "-m", "Fixture")
sha = self.git(source, "rev-parse", "HEAD")
target = self.published / "weichen" / (name + ".git")
target.parent.mkdir(exist_ok=True)
self.git(self.base, "clone", "-q", "--bare", str(source), str(target))
return 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": 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)
self.assertEqual(result.returncode, 0, result.stderr)
candidate = subprocess.run(["python3", "probe.py"], cwd=self.checkout,
env=self.env, capture_output=True, text=True)
self.assertEqual(candidate.returncode, 0, candidate.stderr)
self.assertNotIn(ROOT_TOKEN, result.stdout + result.stderr)
self.assertNotIn(SUB_TOKEN, result.stdout + result.stderr)
def test_recursive_gitlinks_materialize_exact_sha_without_persisting_credentials(self):
core = self.publish("Agent-System-Core")
backend = self.publish("Agent-System-Backend", [("deps/agent-system-core", "Agent-System-Core", core, None)])
root = self.publish("Agent-System-Dev", [("modules/backend", "Agent-System-Backend", backend, None)])
result = self.run_checkout(root)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(self.git(self.checkout / "modules/backend", "rev-parse", "HEAD"), backend)
self.assertEqual(self.git(self.checkout / "modules/backend/deps/agent-system-core", "rev-parse", "HEAD"), core)
self.assertNotIn("-", self.git(self.checkout, "submodule", "status", "--recursive")[:1])
candidate = subprocess.run(["python3", "probe.py"], cwd=self.checkout,
env=self.env, capture_output=True, text=True)
self.assertEqual(candidate.returncode, 0, candidate.stderr)
for path, auth in self.requests:
self.assertEqual(auth, "token " + (ROOT_TOKEN if "Agent-System-Dev.git/" in path else SUB_TOKEN))
for config in (self.checkout / ".git").rglob("config"):
self.assertNotIn(SUB_TOKEN, config.read_text())
def test_external_or_unlisted_urls_are_rejected_before_component_fetch(self):
core = self.publish("Agent-System-Core")
root = self.publish("Agent-System-Dev", [("modules/core", "Agent-System-Core", core,
self.url + "/untrusted/receiver")])
result = self.run_checkout(root)
self.assertNotEqual(result.returncode, 0)
self.assertFalse(any("untrusted" in path for path, auth in self.requests))
self.assertFalse((self.checkout / "modules/core/.git").exists())
def test_candidate_submodule_update_commands_are_rejected(self):
core = self.publish("Agent-System-Core")
root = self.publish("Agent-System-Dev", [("modules/core", "Agent-System-Core", core, None)],
"\tupdate = !touch /tmp/should-never-execute\n")
result = self.run_checkout(root)
self.assertNotEqual(result.returncode, 0)
self.assertFalse(any("Agent-System-Core.git/" in path for path, auth in self.requests))
def test_nested_external_url_is_rejected_before_authenticated_fetch(self):
core = self.publish("Agent-System-Core")
backend = self.publish("Agent-System-Backend", [("deps/agent-system-core", "Agent-System-Core", core,
self.url + "/untrusted/receiver")])
root = self.publish("Agent-System-Dev", [("modules/backend", "Agent-System-Backend", backend, None)])
result = self.run_checkout(root)
self.assertNotEqual(result.returncode, 0)
self.assertFalse(any("untrusted" in path for path, auth in self.requests))
self.assertFalse(any("Agent-System-Core.git/" in path for path, auth in self.requests))
def test_inherited_template_hook_does_not_execute_with_credentials(self):
template = self.base / "template"
(template / "hooks").mkdir(parents=True)
marker = self.base / "hook-executed"
hook = template / "hooks/post-checkout"
hook.write_text(f"#!/bin/sh\ntouch '{marker}'\n")
hook.chmod(0o755)
self.git(self.base, "config", "--global", "init.templateDir", str(template))
root = self.publish("Agent-System-Dev")
result = self.run_checkout(root)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertFalse(marker.exists())
def test_redirects_do_not_forward_credential_or_fetch_unlisted_target(self):
core = self.publish("Agent-System-Core")
root = self.publish("Agent-System-Dev", [("modules/core", "Agent-System-Core", core, None)])
self.redirect_repo = "Agent-System-Core.git"
result = self.run_checkout(root)
self.assertNotEqual(result.returncode, 0)
self.assertFalse(any("untrusted" in path for path, auth in self.requests))
def test_rdm_pin_fetch_is_three_shas_on_the_allowlist(self):
source = self.base / "Agent-System-RDM"
source.mkdir()
self.git(source, "init", "-q", "-b", "main")
shas = []
for index in range(3):
(source / "Makefile").write_text(
"build:\n\tmkdir -p bin\n\tprintf 'rev%d\\n' > bin/agent-system-rdm\n" % index)
self.git(source, "add", ".")
self.git(source, "-c", "user.name=Fixture",
"-c", "user.email=fixture@example.invalid",
"commit", "-q", "-m", "rev %d" % index)
shas.append(self.git(source, "rev-parse", "HEAD"))
target = self.published / "weichen" / "Agent-System-RDM.git"
target.parent.mkdir(parents=True, exist_ok=True)
self.git(self.base, "clone", "-q", "--bare", str(source), str(target))
self.git(target, "config", "uploadpack.allowReachableSHA1InWant", "true")
self.git(target, "config", "uploadpack.allowAnySHA1InWant", "true")
work = self.base / "rdm-job"
pin = work / "tests" / "integration" / "lifecycle"
pin.mkdir(parents=True)
(pin / "rdm.pin").write_text(
"sha=%s\nprev_sha=%s\nlegacy_sha=%s\n" % (shas[2], shas[1], shas[0]))
script = rdm_clone_script()
self.assertNotRegex(script, r"(?m)^\s*source\s")
self.requests.clear()
result = subprocess.run(
["bash", "-c", script], cwd=work,
env=self.env | {"SUBMODULE_TOKEN": SUB_TOKEN, "GITHUB_SERVER_URL": self.url},
capture_output=True, text=True, timeout=60)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertNotIn(SUB_TOKEN, result.stdout + result.stderr)
self.assertTrue(self.requests)
for path, auth in self.requests:
self.assertRegex(path, r"^/weichen/Agent-System-RDM\.git/")
self.assertEqual(auth, "token " + SUB_TOKEN)
for sha in shas:
self.assertEqual(self.git(work / "rdm", "cat-file", "-t", sha), "commit")
self.assertEqual(self.git(work / "rdm", "rev-parse", "HEAD"), shas[2])
# A lifecycle cache clones this isolated checkout. Local clones copy
# heads, so the fixed source must expose a deterministic main head.
lifecycle = self.base / "lifecycle-source"
self.git(self.base, "clone", "--no-hardlinks", str(work / "rdm"), str(lifecycle))
self.assertEqual(self.git(lifecycle, "rev-parse", "origin/main"), shas[2])
# Candidate builds are a separate phase: the shell has no checkout token,
# and lifecycle source fetches resolve through public local mirror paths.
from tests.test_dependency_ci import phase
built = subprocess.run(
["bash", "-c", phase(WORKFLOW, "Build fixed RDM pins without credentials")],
cwd=work, env=self.env, capture_output=True, text=True, timeout=60)
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
self.assertNotIn(SUB_TOKEN, built.stdout + built.stderr)
digests = (work / "rdm" / "pin-build-digests.txt").read_text()
for sha in shas:
self.assertIn(sha, digests)
clone = work / "lifecycle-source"
replay = subprocess.run(
["bash", "-c", 'source .ci-mirrors.env; git clone -q "$1" "$2"; '
'git -C "$2" fetch -q origin; env', "fixture",
SSH + "Agent-System-RDM.git", str(clone)], cwd=work, env=self.env,
capture_output=True, text=True, timeout=30)
self.assertEqual(replay.returncode, 0, replay.stderr)
self.assertNotIn(SUB_TOKEN, replay.stdout + replay.stderr)
self.assertEqual(self.git(clone, "rev-parse", "HEAD"), shas[2])
if __name__ == "__main__":
unittest.main()