advisory-scan: read-only GitHub token from env or file (rate limit only)

Anonymous GitHub API is 60 req/hr — a full weekly sweep across ~20 recipes exhausts
it and the scan then reports sources as failed (visible, but degraded coverage). A
token lifts it to 5000/hr.

_github_token(): GITHUB_TOKEN env wins, else GITHUB_TOKEN_FILE (default
/srv/cc-ci/.github-token, 0600, gitignored). Reading PUBLIC advisories needs NO
scopes — a classic PAT with nothing ticked, or fine-grained limited to 'Public
repositories: read'. The tool only ever GETs advisories; do not grant write scopes.
A missing token is not an error: the scan runs anonymously and surfaces failures.

Also gitignores .github-token and .hcloud-token.
This commit is contained in:
autonomic-bot
2026-08-10 18:38:45 +00:00
parent 5775fe23f8
commit cf26ef863a
2 changed files with 26 additions and 1 deletions
+4
View File
@@ -28,3 +28,7 @@ master-age.txt
# Python bytecode cache
__pycache__/
*.pyc
# Local API tokens — never committed (advisory-scan / hetzner recovery)
.github-token
.hcloud-token
+22 -1
View File
@@ -59,6 +59,27 @@ OSV_PACKAGES: dict[str, tuple[str, str]] = {
}
def _github_token() -> str | None:
"""Read-only GitHub token, for the API rate limit ONLY (60/hr anonymous → 5000/hr with a token).
Env `GITHUB_TOKEN` wins; otherwise the file at `GITHUB_TOKEN_FILE` (default
/srv/cc-ci/.github-token, chmod 600, never in git). Reading PUBLIC security advisories needs NO
scopes at all — create a classic PAT with every box unticked, or a fine-grained token limited to
"Public repositories: read". Do NOT grant repo/write scopes: this tool only ever GETs advisories.
A missing token is not an error — the scan simply runs anonymously and will report sources as
failed once the 60/hr limit bites, which is visible rather than silent.
"""
tok = os.environ.get("GITHUB_TOKEN")
if tok:
return tok.strip()
path = os.environ.get("GITHUB_TOKEN_FILE", "/srv/cc-ci/.github-token")
try:
with open(path) as f:
return f.read().strip() or None
except OSError:
return None
def _fetch(url: str, headers: dict | None = None) -> str:
h = {"User-Agent": UA, "Accept-Encoding": "gzip"}
h.update(headers or {})
@@ -112,7 +133,7 @@ def github_advisories(urls: list[str]) -> list[dict]:
seen.add((owner, repo))
api = f"https://api.github.com/repos/{owner}/{repo}/security-advisories?per_page=100"
hdrs = {"Accept": "application/vnd.github+json"}
tok = os.environ.get("GITHUB_TOKEN")
tok = _github_token()
if tok:
hdrs["Authorization"] = f"Bearer {tok}"
entry = {"source": f"github-advisories:{owner}/{repo}", "status": "ok", "advisories": []}