[verified] Define non-file Git credential recipe
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
"""Local credential protocol checks only; no real credentials or network."""
|
||||
import os
|
||||
import secrets
|
||||
import shlex
|
||||
import stat
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
POLICY = ROOT / "harness/operations/repository-cache.md"
|
||||
REWORK = ROOT / "harness/agents/impl/skills/gitea-issue-rework/SKILL.md"
|
||||
HEADING = "### Non-file Git credential helper"
|
||||
|
||||
|
||||
class GitCredentialRecipeTests(unittest.TestCase):
|
||||
def recipe(self):
|
||||
policy = POLICY.read_text(encoding="utf-8")
|
||||
self.assertTrue(HEADING in policy, "canonical non-file credential recipe is missing")
|
||||
section = policy.split(HEADING, 1)[1].split("\n## ", 1)[0]
|
||||
block = section.split("```sh\n", 1)[1].split("```", 1)[0]
|
||||
return section, shlex.split(block.replace("\\\n", ""))
|
||||
|
||||
def test_canonical_nonfile_recipe_preserves_permission_and_delivery_contract(self):
|
||||
section, command = self.recipe()
|
||||
self.assertEqual(command[0], "git")
|
||||
self.assertEqual(command[1:3], ["-c", "credential.helper="])
|
||||
helper = command[4]
|
||||
self.assertTrue(helper.startswith("credential.helper=!"))
|
||||
self.assertIn("GIT_ROLE_USERNAME", helper)
|
||||
self.assertIn("GIT_ROLE_TOKEN", helper)
|
||||
self.assertIn("credential.interactive=false", command)
|
||||
self.assertIn("remote.origin.mirror=false", command)
|
||||
self.assertEqual(command[-4:], ["push", "--dry-run", "$VERIFIED_REPOSITORY_URL", "$VERIFIED_REFSPEC"])
|
||||
for required in ("0600", "GIT_TERMINAL_PROMPT=0", "GIT_ASKPASS=/bin/false", "SSH_ASKPASS=/bin/false",
|
||||
"same-UID", "store", "erase", "stdout", "readback"):
|
||||
self.assertIn(required, section)
|
||||
policy = POLICY.read_text(encoding="utf-8")
|
||||
self.assertIn("Lock 파일과 임시 credential helper는 실행 role 소유와 mode `0600`을 요구한다", policy)
|
||||
delivery = REWORK.read_text(encoding="utf-8").split("### 7. 기존 PR branch 전달", 1)[1].split("###", 1)[0]
|
||||
self.assertIn("#non-file-git-credential-helper", delivery)
|
||||
self.assertNotIn("필요한 temporary askpass helper", delivery)
|
||||
|
||||
def test_nonfile_get_missing_variables_and_store_erase_do_not_persist_credentials(self):
|
||||
_, documented = self.recipe()
|
||||
options = documented[:documented.index("push")]
|
||||
with tempfile.TemporaryDirectory(dir=os.environ.get("TMPDIR", "/opt/data/cache/scratch")) as d:
|
||||
root = Path(d)
|
||||
token = secrets.token_hex(24)
|
||||
env = {"PATH": os.environ["PATH"], "HOME": str(root), "GIT_CONFIG_NOSYSTEM": "1",
|
||||
"GIT_CONFIG_GLOBAL": os.devnull, "GIT_TERMINAL_PROMPT": "0",
|
||||
"GIT_ASKPASS": "/bin/false", "SSH_ASKPASS": "/bin/false",
|
||||
"GIT_ROLE_USERNAME": "gitea-impl-agent", "GIT_ROLE_TOKEN": token}
|
||||
initialized = subprocess.run(["git", "init", "-q"], cwd=root, env=env,
|
||||
capture_output=True, timeout=10)
|
||||
self.assertEqual(initialized.returncode, 0)
|
||||
config = root / ".git/config"
|
||||
before, mode = config.read_bytes(), stat.S_IMODE(config.stat().st_mode)
|
||||
request = "protocol=https\nhost=example.invalid\n\n"
|
||||
|
||||
def invoke(action, packet=request, child_env=env):
|
||||
# Values travel only in the private child env/protocol, never argv.
|
||||
self.assertFalse(any(token in arg for arg in options))
|
||||
result = subprocess.run([*options, "credential", action], cwd=root, env=child_env,
|
||||
input=packet, text=True, capture_output=True, timeout=10)
|
||||
self.assertFalse(token in result.stderr, "credential exposed outside private protocol")
|
||||
return result
|
||||
|
||||
valid = invoke("fill")
|
||||
self.assertEqual(valid.returncode, 0)
|
||||
fields = dict(line.split("=", 1) for line in valid.stdout.splitlines() if "=" in line)
|
||||
self.assertTrue(fields.get("username") == env["GIT_ROLE_USERNAME"], "username binding mismatch")
|
||||
self.assertTrue(fields.get("password") == token, "synthetic credential binding mismatch")
|
||||
for missing in ("GIT_ROLE_TOKEN", "GIT_ROLE_USERNAME"):
|
||||
with self.subTest(missing=missing):
|
||||
incomplete = dict(env)
|
||||
incomplete.pop(missing)
|
||||
rejected = invoke("fill", child_env=incomplete)
|
||||
self.assertNotEqual(rejected.returncode, 0)
|
||||
self.assertTrue(rejected.stdout == "", "failed credential lookup emitted data")
|
||||
packet = ("protocol=https\nhost=example.invalid\nusername=" + env["GIT_ROLE_USERNAME"]
|
||||
+ "\npassword=" + token + "\n\n")
|
||||
for action in ("approve", "reject"):
|
||||
with self.subTest(action=action):
|
||||
result = invoke(action, packet=packet)
|
||||
self.assertEqual(result.returncode, 0)
|
||||
self.assertTrue(result.stdout == "", "store/erase emitted credential data")
|
||||
self.assertTrue(config.read_bytes() == before, "Git config changed")
|
||||
self.assertEqual(stat.S_IMODE(config.stat().st_mode), mode)
|
||||
self.assertFalse(token.encode() in config.read_bytes(), "credential persisted")
|
||||
self.assertEqual(sorted(path.name for path in root.iterdir()), [".git"])
|
||||
|
||||
def test_mode0600_direct_askpass_fails_without_permission_relaxation(self):
|
||||
with tempfile.TemporaryDirectory(dir=os.environ.get("TMPDIR", "/opt/data/cache/scratch")) as d:
|
||||
root = Path(d)
|
||||
askpass = root / "askpass.sh"
|
||||
askpass.write_text("#!/bin/sh\nexit 0\n")
|
||||
askpass.chmod(0o600)
|
||||
env = {"PATH": os.environ["PATH"], "HOME": str(root), "GIT_CONFIG_NOSYSTEM": "1",
|
||||
"GIT_CONFIG_GLOBAL": os.devnull, "GIT_TERMINAL_PROMPT": "0", "GIT_ASKPASS": str(askpass)}
|
||||
result = subprocess.run(["git", "-c", "credential.helper=", "credential", "fill"],
|
||||
cwd=root, env=env, input="protocol=https\nhost=example.invalid\n\n",
|
||||
text=True, capture_output=True, timeout=10)
|
||||
self.assertEqual(result.returncode, 128)
|
||||
self.assertIn("Permission denied", result.stderr)
|
||||
self.assertTrue(result.stdout == "", "failed credential lookup emitted data")
|
||||
self.assertEqual(stat.S_IMODE(askpass.stat().st_mode), 0o600)
|
||||
Reference in New Issue
Block a user