v1_git_credential.py python
65 lines 2.6 KB
Raw
sha256:f88ec0083b3bcac6a4b7b99c8cc196194ef376aab2c4f2f63e8dfe26256ac6b7 docs: reconcile v1 prerelease publication and operating guides Human 7 days ago
1 """Ephemeral Git credential helper: one approved GitHub HTTPS repository only.
2
3 Invoked by isolated Git, never as a user-facing token command. Store/erase are
4 deliberate no-ops. gh output is held in memory and returned only on Git's pipe.
5 """
6 from pathlib import Path
7 import os
8 import re
9 import subprocess
10 import sys
11
12
13 def main():
14 if len(sys.argv) != 4:
15 return 1
16 gh, repository, operation = sys.argv[1:]
17 if operation in ('store', 'erase'):
18 return 0
19 path = Path(gh)
20 if (operation != 'get' or not path.is_absolute() or path.resolve() != path
21 or any(p.is_symlink() for p in (path, *path.parents))
22 or not path.is_file() or not os.access(path, os.X_OK)
23 or not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*', repository)):
24 return 1
25 raw = sys.stdin.buffer.read(16385)
26 if len(raw) > 16384:
27 return 1
28 fields = {}
29 for row in raw.decode('utf-8', errors='strict').split('\n'):
30 if not row:
31 continue
32 if any(ord(c) < 32 or ord(c) == 127 for c in row):
33 return 1
34 key, sep, value = row.partition('=')
35 if not sep:
36 return 1
37 # Git sends repeatable capability offers and HTTP authentication challenges.
38 # Ignore only these metadata arrays: they cannot change destination scope,
39 # select a credential type, or opt this username/password helper into state.
40 # The whole request remains bounded; unknown fields still fail closed.
41 if key in ('capability[]', 'wwwauth[]'):
42 continue
43 if key in fields or key not in ('protocol', 'host', 'path', 'username'):
44 return 1
45 fields[key] = value
46 if any(fields.get(k) != v for k, v in
47 {'protocol': 'https', 'host': 'github.com', 'path': repository + '.git'}.items()):
48 return 1
49 environment = {k: v for k, v in os.environ.items() if k not in ('GH_DEBUG', 'GH_HOST')}
50 environment.update(GH_HOST='github.com', GH_PROMPT_DISABLED='1')
51 proc = subprocess.run([gh, 'auth', 'token', '--hostname', 'github.com'],
52 env=environment, capture_output=True, timeout=15)
53 token = proc.stdout.strip()
54 if proc.returncode or not token or len(token) > 4096 or any(c < 33 or c > 126 for c in token):
55 return 1
56 sys.stdout.buffer.write(b'username=x-access-token\npassword=' + token + b'\n\n')
57 return 0
58
59
60 if __name__ == '__main__':
61 try:
62 raise SystemExit(main())
63 except (OSError, ValueError, subprocess.SubprocessError):
64 # Never echo credential subprocess output or input fields on failure.
65 raise SystemExit(1) from None
File History 1 commit
sha256:f88ec0083b3bcac6a4b7b99c8cc196194ef376aab2c4f2f63e8dfe26256ac6b7 docs: reconcile v1 prerelease publication and operating guides Human 7 days ago