- ระหว่าง 2026-05-11 19:20~19:26 UTC ผู้โจมตีได้เผยแพร่เวอร์ชันอันตราย 84 เวอร์ชัน ครอบคลุมแพ็กเกจ npm @tanstack/ จำนวน 42 แพ็กเกจ
- ห่วงโซ่การโจมตีผสาน pull_request_target แบบ “Pwn Request”, การปนเปื้อนแคชของ GitHub Actions และการดึง OIDC token จากหน่วยความจำของ runner
- npm token และเวิร์กโฟลว์ publish ไม่ได้ถูกขโมยหรือถูกเจาะ โดยมัลแวร์ส่ง POST ไปยัง registry โดยตรงด้วยสิทธิ์ OIDC trusted publisher
- หากติดตั้งเวอร์ชันที่ได้รับผลกระทบ อาจทำให้ ข้อมูลรับรอง AWS, GCP, Kubernetes, Vault, GitHub, npm, SSH รั่วไหล และจำเป็นต้องเปลี่ยนใหม่
- เวอร์ชันที่ได้รับผลกระทบทั้งหมดถูกทำเครื่องหมาย deprecated แล้ว และได้ดำเนินการร่วมกับ npm security เพื่อลบ tarball พร้อมเผยแพร่ issue ติดตามและ GitHub Security Advisory
ภาพรวมของเหตุการณ์
- ระหว่าง 2026-05-11 19:20~19:26 UTC ผู้โจมตีได้เผยแพร่เวอร์ชันอันตราย 84 เวอร์ชัน ครอบคลุมแพ็กเกจ npm
@tanstack/*จำนวน 42 แพ็กเกจ - ห่วงโซ่การโจมตีผสาน แพตเทิร์น
pull_request_target“Pwn Request”, การปนเปื้อนแคชของ GitHub Actions ที่ข้ามขอบเขตความไว้วางใจระหว่าง fork↔base และการดึง OIDC token จากหน่วยความจำของโปรเซส GitHub Actions runner - ยืนยันแล้วว่า npm token ไม่ได้ถูกขโมย และตัวเวิร์กโฟลว์ npm publish เองก็ไม่ได้ถูกเจาะ
- เวอร์ชันอันตรายถูกตรวจพบแบบสาธารณะภายใน 20 นาทีโดยนักวิจัยภายนอก
ashishkurmiจากstepsecurity - เวอร์ชันที่ได้รับผลกระทบทั้งหมดถูกทำเครื่องหมาย deprecated แล้ว และกำลังดำเนินการร่วมกับ npm security เพื่อลบ tarball ออกจาก registry
- ผู้ใช้ที่ติดตั้งเวอร์ชันที่ได้รับผลกระทบในวันที่ 2026-05-11 ต้องเปลี่ยน ข้อมูลรับรอง AWS, GCP, Kubernetes, Vault, GitHub, npm, SSH ที่โฮสต์เครื่องติดตั้งนั้นเข้าถึงได้
- issue ติดตามคือ TanStack/router#7383 และ GitHub Security Advisory คือ GHSA-g7cv-rxg3-hmpx
ขอบเขตผลกระทบ
-
แพ็กเกจที่ได้รับผลกระทบ
- ขอบเขตผลกระทบครอบคลุม 42 แพ็กเกจและ 84 เวอร์ชัน โดยมีการเผยแพร่ 2 เวอร์ชันต่อแพ็กเกจ ห่างกันประมาณ 6 นาที
- รายการทั้งหมดอยู่ใน issue ติดตาม
- กลุ่มผลิตภัณฑ์ที่ยืนยันว่าไม่ได้รับผลกระทบคือ
@tanstack/query*,@tanstack/table*,@tanstack/form*,@tanstack/virtual*,@tanstack/store, เมตาแพ็กเกจ@tanstack/start @tanstack/start-*ไม่ได้รวมอยู่ในรายการที่ยืนยันว่าไม่ได้รับผลกระทบ
-
การทำงานของมัลแวร์
- เมื่อสภาพแวดล้อมนักพัฒนาหรือ CI รัน
npm install,pnpm install,yarn installกับเวอร์ชันที่ได้รับผลกระทบ npm จะประมวลผลรายการoptionalDependenciesที่เป็นอันตรายและดึง orphan payload commit จาก fork network - จากนั้นสคริปต์ lifecycle
prepareจะถูกรัน และrouter_init.jsที่ถูกทำให้อ่านยากขนาดประมาณ 2.3MB ซึ่งซ่อนอยู่ใน tarball ที่ได้รับผลกระทบจะเริ่มทำงาน - สคริปต์อันตรายจะรวบรวมข้อมูลรับรองจากตำแหน่งทั่วไป เช่น AWS IMDS/Secrets Manager, GCP metadata, Kubernetes service-account token, Vault token,
~/.npmrc, GitHub token,ghCLI,.git-credentials, SSH private key - ข้อมูลที่ขโมยได้จะถูกส่งออกผ่าน เครือข่ายอัปโหลดไฟล์ของ Session/Oxen messenger โดยปลายทางคือ
filev2.getsession.org,seed{1,2,3}.getsession.org - เนื่องจากเครือข่ายดังกล่าวเข้ารหัสแบบ end-to-end และไม่มี C2 ที่ผู้โจมตีควบคุม มาตรการบรรเทาทางเครือข่ายจึงมีเพียงการบล็อก IP/โดเมนเท่านั้น
- ลอจิกการแพร่กระจายตัวเองจะไล่รายการแพ็กเกจอื่นที่เหยื่อเป็นผู้ดูแลผ่าน
registry.npmjs.org/-/v1/search?text=maintainer:<user>แล้วเผยแพร่ซ้ำด้วยวิธีการฝังแบบเดียวกัน - เนื่องจาก payload ทำงานเป็นส่วนหนึ่งของ lifecycle การติดตั้ง npm โฮสต์ที่ติดตั้งเวอร์ชันที่ได้รับผลกระทบในวันที่ 2026-05-11 ควรถูกปฏิบัติว่าอาจถูกเจาะแล้ว
- เมื่อสภาพแวดล้อมนักพัฒนาหรือ CI รัน
ไทม์ไลน์
-
ก่อนการโจมตี: ขั้นแคชปนเปื้อน
- เมื่อ 2026-05-10 17:16 UTC ผู้โจมตีสร้าง github.com/zblgg/configuration ซึ่งเป็น fork ของ TanStack/router และเปลี่ยนชื่อเพื่อหลีกเลี่ยงการค้นหาในรายการ fork
- เมื่อ 2026-05-10 23:29 UTC มีการเขียนคอมมิตอันตราย
65bf499d16a5e8d25ba95d69ec9790a6dd4a1f14ลงใน fork โดยใช้อัตลักษณ์ที่ถูกปลอมแปลงclaude <claude@users.noreply.github.com> - คอมมิตดังกล่าวเพิ่ม
packages/history/vite_setup.mjsซึ่งเป็น bundle JS payload ขนาดราว 30,000 บรรทัด และใส่[skip ci]ไว้ในข้อความคอมมิตเพื่อยับยั้ง CI ของ push event - ราว 2026-05-11 10:49 UTC
zblggเปิด PR #7378 ไปยังmainของ TanStack/router ในชื่อ “WIP: simplify history build” - ทั้ง
bundle-size.ymlและlabeler.ymlถูกเรียกทำงานอัตโนมัติสำหรับ PR ด้วยpull_request_targetและpull_request_targetข้าม approval gate สำหรับผู้มีส่วนร่วมครั้งแรกได้ จึงไม่ต้องมีการอนุมัติเพิ่มเติม pr.ymlที่ใช้pull_requestถูกบล็อกไว้ในสถานะรออนุมัติและไม่ได้รัน- ระหว่าง 2026-05-11 11:01~11:11 UTC
zblggทำ force-push ไปยัง PR head หลายครั้งเพื่อกระตุ้นให้เกิดการรันpull_request_targetเพิ่มเติม - เมื่อ 2026-05-11 11:11 UTC คอมมิตอันตราย
65bf499dถูกนำขึ้นไปยัง PR head และ jobbenchmark-prของbundle-size.ymlได้ checkoutrefs/pull/7378/mergeจากนั้นรันpnpm installและpnpm nx run @benchmarks/bundle-size:buildทำให้vite_setup.mjsถูกเรียกทำงาน - เมื่อ 2026-05-11 11:29 UTC มีการบันทึก GitHub Actions cache ขนาด 1.1GB ชื่อ
Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11ไว้ใน TanStack/router - แคชนี้ถูกเก็บไว้ใน scope
refs/heads/mainและถูกจัดให้ตรงกับคีย์ที่release.ymlจะดึงมาใช้ใน push ของmainครั้งถัดไป - เมื่อ 2026-05-11 11:31 UTC ผู้โจมตีรีเซ็ต PR กลับไปที่
b1c061afซึ่งเป็นmainHEAD ณ ตอนนั้น เพื่อให้ PR ที่มองเห็นดูเหมือน no-op 0 ไฟล์ และในนาทีเดียวกันก็ปิด PR และลบ branch แต่แคชที่ปนเปื้อนยังคงอยู่
-
การเริ่มปะทุ: ขั้นเผยแพร่
- เมื่อ 2026-05-11 19:15 UTC Manuel merge PR #7369 ทำให้เกิด
mainpush และ workflow run25613093674ของrelease.ymlเริ่มที่ 19:15:44 ก่อนจะล้มเหลว - เมื่อ 2026-05-11 19:20:39 UTC npm registry ได้รับการ publish ของ
@tanstack/history@1.161.9และ sibling package อีก 41 แพ็กเกจ - โดยรวมมีการเผยแพร่ประมาณ 84 เวอร์ชัน ครอบคลุม 42 แพ็กเกจ แต่ในวินาทีที่ระบุนี้มองเห็นได้ประมาณครึ่งหนึ่ง ส่วนที่เหลือถูกเผยแพร่ใน run ที่สอง
- การยืนยันตัวตนในการ publish เกิดขึ้นผ่าน OIDC trusted-publisher binding สำหรับ TanStack/router
release.yml@refs/heads/mainแต่ไม่ได้เกิดจาก stepPublish Packagesของ workflow ที่ถูกข้ามไปเพราะการทดสอบล้มเหลว - ผู้เผยแพร่ตัวจริงคือ malware ที่ทำงานระหว่างขั้นทดสอบ/cleanup ซึ่ง mint OIDC token ด้วยสิทธิ์
id-token: writeแล้ว POST ไปยังregistry.npmjs.orgโดยตรง - เมื่อ 2026-05-11 19:20:47 UTC run
25613093674จบลงด้วยสถานะ failure - เมื่อ 2026-05-11 19:16 UTC Manuel merge PR #7382 ทำให้เกิด
mainpush ครั้งที่สอง และ workflow run25691781302เริ่มที่ 19:16:22 - run ที่สองก็ restore แคชที่ปนเปื้อนเดียวกัน และเมื่อ 2026-05-11 19:26:14 UTC ก็มีการเผยแพร่เวอร์ชันชุดที่สองต่อแพ็กเกจ เช่น
@tanstack/history@1.161.12ผ่านกลไก OIDC เดียวกัน - เมื่อ 2026-05-11 19:26:20 UTC run
25691781302ก็จบลงด้วยสถานะ failure เช่นกัน
- เมื่อ 2026-05-11 19:15 UTC Manuel merge PR #7369 ทำให้เกิด
-
การตรวจพบและการตอบสนอง
- ราว 2026-05-11 19:50 UTC นักวิจัยภายนอก
carliniเปิดอีชู #7383 พร้อม fingerprint ของoptionalDependenciesอันตรายและรายชื่อแพ็กเกจ - รายชื่อเริ่มต้นมี 14 จากทั้งหมด 42 แพ็กเกจ และนักวิจัยได้แจ้งไปยัง npm security โดยตรงด้วย
- ราว 2026-05-11 20:00 UTC Manuel ยืนยันเหตุการณ์ใน #7383 และเริ่มการตอบสนอง
- ราว 2026-05-11 20:10 UTC Manuel ถอดสิทธิ์ push บน GitHub ของสมาชิกทีมคนอื่น ๆ เพื่อป้องกันกรณีที่เครื่องผู้ใช้ถูกเจาะ
- ราว 2026-05-11 20:30 UTC Tanner ส่งรายการ IOC ทั้งหมดและคำขอให้ลบ tarball ฝั่ง registry ไปยัง security@npmjs.com และส่งรายงาน malware อย่างเป็นทางการผ่าน npm
- ราว 2026-05-11 21:00 UTC หลังสแกน
@tanstack/*ทั้ง 295 แพ็กเกจ จึงยืนยันขอบเขตได้ว่าได้รับผลกระทบ 42 แพ็กเกจ 84 เวอร์ชัน - Tanner เริ่มทำ npm deprecation ให้กับแพ็กเกจที่ได้รับผลกระทบทั้ง 84 รายการ และ
@tan_stackกับผู้ดูแลได้แจ้งเตือนสาธารณะบน Twitter/X, LinkedIn และ Bluesky - เมื่อ 2026-05-11 21:30 UTC มีการระบุเวกเตอร์ cache poisoning ของ
pull_request_targetในbundle-size.ymlและ forkzblgg/configuration - รายการแคชของ GitHub repository ภายใต้ TanStack/* ทั้งหมดถูกลบผ่าน API
- hardening PR ถูก merge แล้ว โดยมีการปรับโครงสร้าง
bundle-size.yml, เพิ่มrepository_ownerguard และตรึง ref ของ third-party action ไว้ที่ SHA - มีการเผยแพร่ GitHub Security Advisory อย่างเป็นทางการ และมีการขอ CVE
- ราว 2026-05-11 19:50 UTC นักวิจัยภายนอก
สาเหตุรากฐาน
-
การผสมกันของช่องโหว่สามจุด
- การโจมตีนี้ต้องอาศัยช่องโหว่ทั้งสามอย่างร่วมกัน และแค่เพียงอย่างใดอย่างหนึ่งไม่เพียงพอ
- โค้ดจาก fork PR ข้ามไปยัง cache ของ base repository, cache ของ base repository ข้ามไปยัง runtime ของ release workflow, และ runtime ของ release workflow นำไปสู่สิทธิ์เขียนบน npm registry ทำให้แต่ละช่องโหว่เชื่อมขอบเขตความเชื่อถือของกันและกัน
-
แพตเทิร์น
pull_request_targetแบบ “Pwn Request”bundle-size.ymlทำงานกับ fork PR ด้วยpull_request_targetและภายใต้ trigger context นั้นได้ checkout PR merge ref ของ fork แล้วจึงรัน build- โครงสร้างหลักมีดังนี้
on: pull_request_target: paths: ['packages/**', 'benchmarks/**'] jobs: benchmark-pr: steps: - uses: actions/checkout@v6.0.2 with: ref: refs/pull/${{ github.event.pull_request.number }}/merge # fork's merged code - uses: TanStack/config/.github/setup@main # transitively calls actions/cache@v5 - run: pnpm nx run @benchmarks/bundle-size:build # executes fork-controlled code- ผู้เขียน workflow พยายามแยก job
comment-prกับ jobbenchmark-prออกจากกันเพื่อแยกขอบเขตความเชื่อถือ และในคอมเมนต์ของ YAML ก็ระบุเจตนาว่าต้องการคงbenchmark-prให้เป็น “untrusted with read-only permissions” - แต่ post-job save ของ
actions/cache@v5ไม่สามารถถูกปิดกั้นด้วยpermissions:ได้ และการเขียน cache ใช้ token ภายใน runner ไม่ใช่ workflowGITHUB_TOKEN - ดังนั้นการตั้งค่า
permissions: contents: readจึงไม่สามารถป้องกันการแก้ไข cache ได้ - ขอบเขตของ cache อยู่ในระดับ repository และ
pull_request_targetrun ที่ใช้ขอบเขต cache ของ base repository จะแชร์ร่วมกับการ push ไปยังmain - PR ที่รันอยู่ในขอบเขต cache ของ base repository จึงสามารถทำให้ cache entry ที่ production workflow ของ
mainจะมา restore ในภายหลังปนเปื้อนได้
-
การทำให้ GitHub Actions cache ปนเปื้อน
vite_setup.mjsที่เป็นอันตรายถูกออกแบบให้เขียนข้อมูลให้ตรงกับ pnpm-store key ที่release.ymlworkflow ปกติจะคำนวณและเรียกใช้- key เป้าหมายอยู่ในรูปแบบ
Linux-pnpm-store-${hashFiles('**/pnpm-lock.yaml')} - เมื่อ job
benchmark-prสิ้นสุดลง post-step ของactions/cache@v5จะบันทึก pnpm store ที่ปนเปื้อนด้วย key นี้อย่างพอดี - หลังจากนั้นเมื่อ
release.ymlทำงานจากการ push ไปยังmainstepSetup Toolsก็จะ restore entry ที่ปนเปื้อนดังกล่าวตามแผน - การโจมตีประเภทนี้อยู่ในตระกูล GitHub Actions cache poisoning ที่ Adnan Khan บันทึกไว้ในปี 2024 และไม่ใช่บั๊กที่จำกัดเฉพาะ TanStack แต่เป็นประเด็นด้านการออกแบบของ GitHub Actions ที่ต้องมีการป้องกันอย่างตั้งใจ
-
การดึง OIDC token จากหน่วยความจำของ runner
release.ymlประกาศid-token: writeอย่างถูกต้องเพราะจำเป็นต่อ npm OIDC trusted publishing- เมื่อ pnpm store ที่ปนเปื้อนถูก restore ลงบน runner ก็จะมี binary ที่ผู้โจมตีควบคุมอยู่บนดิสก์และถูกเรียกใช้ใน step ของ build
- binary ดังกล่าวค้นหาโปรเซส GitHub Actions
Runner.Workerผ่าน/proc/*/cmdlineแล้วอ่าน/proc/<pid>/mapsและ/proc/<pid>/memเพื่อ dump หน่วยความจำของ worker - จากนั้นจึงดึง OIDC token ที่ runner สร้างแบบ lazy mint ภายใต้การตั้งค่า
id-token: writeออกมาจากหน่วยความจำ - ด้วย token ที่ดึงมาได้ ผู้โจมตีจะยืนยันตัวตนเพื่อส่งคำขอ POST ไปยัง
registry.npmjs.orgโดยตรง ทำให้ข้าม stepPublish Packagesของ workflow ไปทั้งหมด - วิธีดึงจากหน่วยความจำนี้เป็นวิธีเดียวกับที่ใช้ในการ compromise ของ
tj-actions/changed-filesเมื่อเดือนมีนาคม 2025 และมีการใช้ Python script เดียวกันที่มี attribution comment รวมอยู่ด้วย - ผู้โจมตีไม่ได้คิดค้นเทคนิคใหม่ แต่เป็นการนำงานวิจัยสาธารณะที่มีอยู่มาประกอบเข้าด้วยกัน
-
เหตุผลที่แต่ละองค์ประกอบเพียงลำพังไม่เพียงพอ
- ตัว
pull_request_targetเองยังสามารถใช้กับงานที่เชื่อถือได้ เช่น label หรือ comment - การทำ cache poisoning ภายใน dependency ที่ถูก compromise ไปแล้วเพียงอย่างเดียว ยังต้องมีช่องทางสำหรับ publish แยกต่างหาก
- การดึง OIDC token เพียงอย่างเดียว ยังต้องอาศัยการรันโค้ดบน runner อยู่ก่อนแล้ว
- ตัว
การตรวจจับและ IOC
-
เส้นทางการตรวจจับ
- การตรวจจับไม่ได้เกิดขึ้นจากภายใน แต่เกิดจากภายนอก
carliniเปิด issue #7383 พร้อมให้การวิเคราะห์ทางเทคนิคทั้งหมดภายในราว 20 นาทีหลังมีการ publish- Tanner ได้รับโทรศัพท์จาก Socket.dev เพื่อยืนยันสถานการณ์ทันทีหลังเริ่ม war room
-
fingerprint สำหรับ downstream maintainer และเครื่องมือความปลอดภัย
- ใน manifest ของแพ็กเกจ
@tanstack/*รายการoptionalDependenciesต่อไปนี้คือ IOC สำคัญ
"optionalDependencies": { "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c" }- IOC ระดับไฟล์คือ
router_init.jsที่ root ของ package ซึ่งมีขนาดประมาณ 2.3MB และไม่ได้รวมอยู่ใน"files" - cache key คือ
Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11 - URL ของ payload ระยะที่ 2 คือ
https://litter.catbox.moe/h8nc9u.js,https://litter.catbox.moe/7rrc6l.mjs - เครือข่ายที่ใช้ในการรั่วไหลของข้อมูลคือ
filev2.getsession.org,seed{1,2,3}.getsession.org - identity ของ commit ที่ปลอมแปลงคือ
claude <claude@users.noreply.github.com>ซึ่งไม่ใช่ Anthropic Claude จริง แต่เป็น GitHub no-reply email ที่ถูกปลอมขึ้น - บัญชีผู้โจมตีจริงคือ
zblggid 127806521,voicproducoesid 269549300 - fork ของผู้โจมตีคือ github.com/zblgg/configuration ซึ่งเป็นการเปลี่ยนชื่อจาก fork ของ TanStack/router เพื่อหลบเลี่ยงการค้นหา
- orphan payload commit ภายในเครือข่าย fork คือ
79ac49eedf774dd4b0cfa308722bc463cfe5885c - workflow run ที่ใช้ publish ที่เป็นอันตรายคือ github.com/TanStack/router/actions/runs/25613093674 attempt 4 และ github.com/TanStack/router/actions/runs/25691781302
- ใน manifest ของแพ็กเกจ
บทเรียน
-
สิ่งที่ทำได้ดี
- นักวิจัยภายนอกตรวจพบเหตุการณ์ภายในประมาณ 20 นาทีหลังเกิดเหตุ และรายงานพร้อมรายละเอียดทางเทคนิคทั้งหมด
- ทีมผู้ดูแลประสานงานกันได้ทันทีข้ามหลายเขตเวลา
- ชุมชนด้านการตรวจจับได้รูปแบบ IOC ที่เปิดเผยต่อสาธารณะและชัดเจนภายในไม่กี่ชั่วโมง
-
สิ่งที่ควรปรับปรุง
- ไม่มีการ alerting ภายใน และทราบเรื่องการ compromise จากบุคคลที่สาม
- จำเป็นต้องมีการ monitoring การ publish ของตนเอง และมีแผนจะทำงานร่วมกับบริษัทวิจัยด้านความปลอดภัยของ ecosystem ที่สามารถตรวจจับปัญหาแบบนี้ได้รวดเร็วยิ่งขึ้น พร้อมลด feedback loop ให้สั้นลง
- workflow
pull_request_targetเป็นรูปแบบที่ทราบกันมานานแล้วว่าอันตราย แต่ไม่ได้รับการ audit - floating ref ของ third-party action อย่าง
@v6.0.2,@mainสร้าง supply-chain risk อย่างต่อเนื่องโดยไม่เกี่ยวกับเหตุการณ์นี้โดยตรง - เนื่องจากนโยบายของ npm ที่ว่า “หากมี dependent จะ unpublish ไม่ได้” จึงไม่สามารถ unpublish ได้ในแพ็กเกจที่ได้รับผลกระทบเกือบทั้งหมด
- ต้องพึ่งพา npm security ในการลบ tarball ฝั่ง registry และทำให้มีเวลาเพิ่มอีกหลายชั่วโมงที่ tarball อันตรายยังคงอยู่ในสถานะที่ติดตั้งได้
- รายชื่อ maintainer 7 คนใน npm scope หมายความว่าเกิด credential-theft target แยกกัน 7 เป้าหมายสำหรับ blast radius เดียวกัน
- OIDC trusted-publisher binding ไม่มีการ review รายการ publish แต่ละครั้ง และเมื่อกำหนดไว้แล้ว code path ใดก็ตามใน workflow ก็สามารถ mint token ที่ใช้ publish ได้
- ทางเลือกที่ต้องการคือย้ายไปใช้ classic token ระยะสั้นที่มีการ review แบบ manual หรือเพิ่ม provenance-source-verification เพื่อตรวจจับการ publish จาก workflow step ที่ไม่คาดคิด
-
สิ่งที่โชคดี
- ผู้โจมตีเลือก payload ที่ทำให้การทดสอบพัง จึงทำให้ขั้นตอน publish ปกติถูกข้ามไป และไม่มีการสร้าง tarball ที่ดูสะอาดกว่านี้
- ด้วยเหตุนี้ การโจมตีจึงแสดงออกอย่างชัดเจนพอที่จะถูกตรวจพบได้อย่างรวดเร็ว
- หากเป็นผู้โจมตีที่ระมัดระวังกว่านี้และไม่ทำให้การทดสอบพัง ก็อาจ publish ต่อไปอย่างเงียบ ๆ ได้อีกหลายชั่วโมง
- ผู้โจมตีนำ memory-dump script แบบเปิดเผยที่มี attribution comment กลับมาใช้ซ้ำ และไม่ได้เขียนโค้ดใหม่ ทำให้การจับคู่ IOC เร็วขึ้น
คำถามที่ยังคงค้างอยู่
- ต้องยืนยันว่า step
Setup Toolsในbundle-size.ymlได้เรียกactions/cache@v5จริงหรือไม่ - ต้องตรวจสอบโดยอ่าน post-job log ของหนึ่งใน
pull_request_targetrun สำหรับ PR #7378 โดยตัวอย่าง run id คือ25666610798 - ต้องตรวจสอบว่าใน PR head commit แรกสุดก่อนจะหายไปจาก force-push มีอะไรอยู่บ้าง ซึ่งอาจยังเหลืออยู่ใน GitHub reflog
- ต้องยืนยันว่า malicious commit เข้าไปอยู่ใน git object store ของ fork ด้วยวิธี git push โดยตรง หรือถูกสร้างผ่าน GitHub web UI ซึ่งจะทิ้ง audit-log entry ไว้
- ต้องตรวจสอบว่า
voicproducoesเป็นบัญชีจริงหรือเป็น sock puppet โดยเทียบกับประวัติกิจกรรม - ต้องตรวจสอบว่า npm cache ที่ดูเหมือนเป็น entry
linux-npm-store-*ซ้ำกัน 6 รายการ ถูกทำให้ปนเปื้อนด้วยหรือไม่ และถูกใช้งานจริงหรือไม่ - ต้องตรวจสอบว่าการโจมตีจำเป็นต้องใช้ Nx Cloud หรือไม่ หรือเพียง GitHub Actions cache ก็เพียงพอให้ทำงานได้
- ต้องตรวจสอบว่าสามารถระบุ fork อื่นใน TanStack/router fork network ที่มี orphan payload commit ได้หรือไม่
- หาก fork อื่นยัง host commit ดังกล่าวอยู่ การเข้าถึงแบบ
github:tanstack/router#79ac49ee...จะยังคงใช้ได้และทำให้การ cleanup ยากขึ้น - repo อื่นของ TanStack เช่น router, query, table, form, virtual จำเป็นต้อง audit ว่าใช้รูปแบบ
bundle-size.ymlในลักษณะเดียวกันหรือไม่ - ต้องขอข้อมูลจาก npm support ว่ามีผู้ใช้จำนวนเท่าใดที่ดาวน์โหลดเวอร์ชันที่ได้รับผลกระทบจริงในช่วง publish window
- ต้องตรวจสอบว่าเครื่องของ maintainer ทั้ง 7 คนถูก compromise แยกกันด้วยหรือไม่
- แม้ malicious publish จะไม่ได้ใช้ maintainer npm token แต่เครื่องของ maintainer อาจเป็นเป้าหมายลำดับที่สองของ self-propagation logic
เอกสารอ้างอิง
- TanStack/router#7383: issue สำหรับการติดตาม
- GHSA-g7cv-rxg3-hmpx: GitHub Security Advisory
- The Monsters in Your Build Cache: Github Actions Cache Poisoning: งานวิจัยปี 2024 ของ Adnan Khan เกี่ยวกับ GitHub Actions cache poisoning
- Keeping your GitHub Actions and workflows secure: Preventing pwn requests: เอกสารป้องกัน pwn request จาก GitHub Security Lab
- Harden-Runner detection: tj-actions/changed-files action is compromised: เอกสารการตรวจจับที่เกี่ยวข้องของ StepSecurity ในเดือนมีนาคม 2025
- Unpublish: นโยบาย unpublish ของ npm
- Provenance: เอกสาร npm provenance
- GHSA-g7cv-rxg3-hmpx: GitHub Security Advisory ที่ครอบคลุมรายการเวอร์ชันที่ได้รับผลกระทบทั้งหมด
1 ความคิดเห็น
ความเห็นจาก Hacker News
ต้องระวังเมื่อเพิกถอนโทเค็น ดูเหมือนว่าเพย์โหลดจะติดตั้ง dead-man's switch ไว้ที่
~/.local/bin/gh-token-monitor.shและลงทะเบียนเป็นบริการผู้ใช้ systemd บน Linux และเป็นLaunchAgent com.user.gh-token-monitorบน macOSมันจะคอย poll
api.github.com/userทุก 60 วินาทีด้วยโทเค็นที่ขโมยมา และถ้าโทเค็นถูกเพิกถอนจนได้ HTTP 40x ก็จะสั่งrm -rf ~/https://github.com/TanStack/router/issues/7383#issuecomment-...
อีก 5 ปีข้างหน้า โลกซอฟต์แวร์น่าจะดุเดือดมาก และ ระบบ air-gap น่าจะยิ่งสำคัญขึ้นมาก
แพ็กเกจ npm
@mistralai/mistralaiก็ถูกโจมตีเป็นส่วนหนึ่งของ เวิร์ม นี้ด้วยhttps://github.com/mistralai/client-ts/issues/217
ตอนนี้ถูกถอดออกจาก npm registry แล้ว
น่าเสียดาย แต่นี่ดูเป็นหลักฐานว่า Trusted Publishing เพียงอย่างเดียวยังไม่พอสำหรับการ deploy จาก CI อย่างปลอดภัย ถ้าผู้โจมตีอยู่ใน CI pipeline หรือขโมยสิทธิ์ผู้ดูแล repository ได้ ก็ deploy ได้ง่ายมาก
นี่ไม่ใช่ข้อมูลใหม่ และ Trusted Publishing ก็ไม่ได้ถูกออกแบบมาเพื่อรับประกันเรื่องนี้อยู่แล้ว แต่เมื่อย้ายจากการ deploy แบบ local พร้อมการยืนยันตัวตนสองขั้นตอนไปเป็น Trusted Publishing ก็จะเกิดเส้นทางโจมตีผ่านการเจาะ CI แบบนี้ขึ้นมา เท่ากับปัจจัยที่สองซึ่งเคยช่วยกัน
npm publishตอนทำงานจากเครื่อง local หายไปจากสถานการณ์ตอนนี้ ดูเหมือนว่าผู้โจมตีจะยึด CI/CD pipeline ได้ และเพราะ
npm publishไม่มีปัจจัยที่สอง จึงขโมย OIDC token ไปใช้ deploy ต่อได้ ที่น่าสนใจแต่เป็นอีกประเด็นหนึ่งคือ ตัวงาน deploy เองล้มเหลว แต่เพย์โหลดใน malicious commit เหมือนจะใช้ OIDC token ของ workflow เพื่อ deploy ตัวเองได้สิ่งที่ต้องการคือยังคงโมเดล Trusted Publisher ที่ไม่มี long-term token ไว้ แต่ให้การ deploy จาก CI ยังมีปัจจัยที่สองจากภายนอก GitHub อยู่ด้วย นั่นคือควรมี การ deploy แบบเป็นขั้นตอน ที่ต้องมีคนไปยืนยันฝั่ง npm ด้วยการยืนยันตัวตนสองขั้นตอนเพื่อเลื่อนอาร์ติแฟกต์ให้เป็นสถานะเผยแพร่จริง
ถ้าการ deploy ทำได้ภายใน trust model ของ GitHub อย่างเดียว ใครก็ตามที่ขโมยโทเค็นผู้ดูแล repository หรือฝังโค้ดอันตรายใน pipeline ได้ ก็จะ deploy สำเร็จได้ง่ายมาก แต่ถ้ามีปัจจัยที่สองจริง ๆ ที่อยู่นอกบริบทของ GitHub อย่างน้อยพวกเขาจะทำลาย repository หรือฝังโค้ดอันตรายได้ แต่จะเผยแพร่ขึ้น registry ไม่ได้หากไม่มีปัจจัยที่สองนั้น
บทวิเคราะห์หลังเหตุการณ์: https://tanstack.com/blog/npm-supply-chain-compromise-postmo...
อยากรู้ว่ามีหลักฐานหรือไม่ว่าซับแพ็กเกจที่อาจดึงหรือรวมแพ็กเกจ TanStack เข้าไปนั้นปลอดภัยแล้ว
สคริปต์ postinstall ร้ายแรงมาก ทุกคนควรใช้ pnpm
มันไร้สาระมากที่ commit “กำพร้า” ซึ่งถูก push ไปยัง FORK จะไปก่อเรื่องแบบนี้ใน npm client ได้ ฉันคิดว่า GitHub ก็ต้องรับผิดชอบหนักเหมือนกัน โครงสร้างที่ทำให้ commit จาก malicious fork เข้าถึงได้ด้วย URI ที่แยกไม่ออกจาก repository ปกติผ่าน shared object store ของ GitHub นี่บ้ามาก
ตอนอ่านครั้งแรก ฉันคิดว่าเขาใช้คำว่า “fork” ผิด และจริง ๆ หมายถึง branch ใน repository ทางการ เพราะไม่คิดว่าเรื่องแบบนี้จะเป็นจริงได้ โอ้โห
https://tanstack.com/blog/npm-supply-chain-compromise-postmo...
TanStack เพิ่งเผยแพร่ บทวิเคราะห์หลังเหตุการณ์ ของกรณีนี้
เป็นการเตือนให้ตั้งค่าสภาพแวดล้อม npm ให้ปลอดภัย
https://gajus.com/blog/3-pnpm-settings-to-protect-yourself-f...
แค่ตั้งค่าไม่กี่อย่างก็ลดปัญหาใหญ่ได้มาก
allow-git=noneด้วย: https://github.blog/changelog/2026-02-18-npm-bulk-trusted-pu...min-release-age2) ด้วยเหตุผลบางอย่างมันถูกทำเป็นหน่วยวันแทนนาที: https://docs.npmjs.com/cli/v11/using-npm/config#min-release-...ฉันคิดว่าพื้นที่ของตัวจัดการ dependency มันแตกย่อยแบบไม่จำเป็นสุด ๆ
ถ้าการพึ่งพาเวอร์ชันของแพ็กเกจยังเป็น
^1.0.0หรือถึงขั้น"*"ก็ไม่ต้องอ่านต่อแล้ว ให้รีบตรึงเป็นเวอร์ชันที่ปลอดภัยทันทีฉันรีบทำอันนี้ด้วย Claude เพื่อช่วยลดการแพร่กระจาย แน่นอนว่าควรตรวจสอบเองอีกที แต่มันจะสแกนว่ามี แพ็กเกจที่ถูกโจมตี ตามที่พูดถึงอยู่ในเครื่องหรือไม่: https://github.com/PaulSinghDev/tanstack-shai-hulud-fix
ดูเหมือนตอนนี้เรามาถึงจุดที่ทุกคนควรรันแต่ละโปรเจ็กต์ใน VM แยกกัน แล้ว
เมื่อดูจากช่องโหว่ยกระดับสิทธิ์บนเครื่อง local ช่วงหลัง ๆ จะเห็นว่า Docker อย่างเดียวไม่พอแน่นอน และแต่แรก container ก็ไม่ได้ถูกออกแบบให้เป็นขอบเขตความปลอดภัยหลักอยู่แล้ว
ถ้ามีบริการคลาวด์อื่นที่ต้องเข้าถึงจากในคอนเทนเนอร์ ตัวขโมยข้อมูลรับรองนี้ก็จะเอาส่วนนั้นไปด้วย ถึงอย่างนั้นมันก็ยังช่วยลด ขอบเขตความเสียหาย ได้ จึงอย่างน้อยก็ยังดีขึ้น
โอ้โห นี่ก็เป็นอีกแพ็กเกจใหญ่หนึ่ง ฉันเลยเอาประกาศเพื่อสาธารณประโยชน์ที่เคยโพสต์หลัง Axios และ LiteLLM ถูกโจมตีมาโพสต์ซ้ำ เพราะเรื่อง lifecycle script ก็ใช้ได้กับกรณีนี้เหมือนกัน
ตอนนี้ npm/bun/pnpm/uv รองรับการตั้งค่าอายุขั้นต่ำของ release ของแพ็กเกจแล้วทั้งหมด ฉันใส่
ignore-scripts=trueไว้ใน~/.npmrcด้วย และจากการวิเคราะห์ ดูเหมือนแค่นี้ก็ช่วยบรรเทาช่องโหว่นี้ได้ bun และ pnpm จะไม่รัน lifecycle script โดยค่าเริ่มต้นวิธีตั้งอายุขั้นต่ำของ release เป็น 7 วันแบบ global มีดังนี้
~/.config/uv/uv.tomlexclude-newer = "7 days"~/.npmrcmin-release-age=7 # daysignore-scripts=true~/Library/Preferences/pnpm/rcminimum-release-age=10080 # minutes~/.bunfig.toml[install]minimumReleaseAge = 604800 # secondsหากต้อง override การตั้งค่า global ก็ใช้ CLI flag ได้
npm install --min-release-age 0pnpm add --minimum-release-age 0uv add --exclude-newer "0 days"bun add --minimum-release-age 0ขอเสริมอีกอย่างหนึ่ง ฉันเห็นความกังวลว่าถ้านำการหน่วงเวลา dependency มาใช้ในวงกว้าง จะทำให้การตรวจพบช่องโหว่ช้าลง หรือการหน่วงเวลา dependency เป็นเหมือนการอาศัยคนอื่นตรวจให้ฟรี ซึ่งฉันไม่เห็นด้วย สิ่งที่เราแลกด้วยการหน่วงเวลา dependency คือ ความชอบด้านเวลา และย่อมมีคนที่มีความชอบด้านเวลาสูงกว่าฉันอยู่เสมอ
0: https://news.ycombinator.com/item?id=47582220
1: https://news.ycombinator.com/item?id=47513932
ถ้าอยากหลีกเลี่ยงการเปลี่ยนแปลงที่ไม่คาดคิด ให้ใช้
pnpm install --frozen-lockfileและต้องจำไว้ว่าถ้าไม่ได้ตั้งmin-release-ageคุณก็อาจดึงแพ็กเกจที่ได้รับผลกระทบเข้ามาผ่าน dependency ทางอ้อมได้ด้วย ถ้าเป็นไปได้ควรตรึงเวอร์ชันของ package manager ไว้ด้วย