28 серпня 2026 року було скомпрометовано npm-пакет @7nohe/openapi-react-query-codegen, інструмент, що генерує TypeScript-клієнти та hook-и для TanStack Query зі схем OpenAPI.[1][2][3]
Атакувальнику не знадобилися ні npm-пароль maintainer-а, ні викрадений довгоживучий publish token.
Шлях атаки проходив через release workflow у GitHub Actions.
Репозиторій реагував на:
issue_comment
і коментар у pull request з точним текстом:
npm publish
міг запустити release path без належної перевірки, чи автор коментаря є maintainer, collaborator або повністю зовнішнім користувачем GitHub.[1][2]
Після цього workflow:
checkout-ив код із PR
→ запускав pnpm install
→ мав id-token: write
→ публікував через npm Trusted Publishing
Результат: 10 шкідливих версій було опубліковано з валідною npm provenance.[1][3]
Головний урок:
Provenance може коректно підтверджувати походження build-а, навіть якщо сам build є шкідливим.
Також потрібне важливе уточнення: @tanstack/react-query у цьому інциденті не був скомпрометований. Постраждав незалежний code generator, що використовується з TanStack Query.[6][7]
Стан інформації: 31 серпня 2026 року.
TL;DR
| Питання | Перевірена відповідь |
|---|---|
| Скомпрометований пакет | @7nohe/openapi-react-query-codegen |
| Дата | 28 серпня 2026 |
| Шкідливих версій | 10 |
| Стабільних шкідливих версій | 8 |
| Шкідливих prerelease | 2 |
@tanstack/react-query скомпрометований? | Ні |
| Основний вектор | Небезпечно спроєктований GitHub Actions issue_comment release workflow |
| Потрібен пароль maintainer-а? | Ні |
| Потрібен long-lived npm token? | Ні |
| Publish authority | id-token: write + npm Trusted Publishing |
| Валідна provenance? | Так |
| Provenance = безпечний код? | Ні |
| Execution paths | binding.gyp, а в частині версій також preinstall |
| Основний payload | 3FWCvzduYZg.js |
Поточний latest | 3.0.2 |
| Severity | Critical, CVSS 9.6 |
| CVE | Станом на 31 серпня advisory не вказує відомого CVE |
| Родина | Активність, пов’язана з Mini Shai-Hulud / похідними варіантами |
| Точна атрибуція оператора? | Ні |
| Пріоритетна реакція | Ізолювати host і з чистої машини ротувати всі доступні credentials |
Що сталося 28 серпня?
GitHub Security Advisory GHSA-9pvf-vcx3-x239 перелічує 10 шкідливих версій, опублікованих приблизно між 20:00 і 20:21 UTC.[1]
Вісім стабільних версій:
0.5.4
0.5.5
1.6.3
1.6.4
2.2.1
2.2.2
3.0.3
3.0.4
і два prerelease:
0.0.0-365d4eb738d3146583431948d3ba6e27a32556be
0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab
Tag latest вказував на шкідливу 3.0.4 приблизно з 20:19:29 UTC до близько 22:51 UTC, коли його повернули на чисту 3.0.2.[1]
TanStack Query сам не був атакований
Формулювання:
TanStack Query hacked
є неточним.
@7nohe/openapi-react-query-codegen — незалежний проєкт, який генерує код для:
@tanstack/react-query
Це технологічний зв’язок, а не офіційний TanStack package.
Офіційна npm-сторінка @tanstack/react-query є окремою, а TanStack у травневому postmortem також прямо вказав Query серед незачеплених сімейств.[7][11]
Що робить openapi-react-query-codegen?
Він генерує, зокрема:
- typed API clients,
- query keys,
queryOptions,infiniteQueryOptions,useQuery,useMutation,- suspense helpers,
- prefetch та SSR helpers.
Типовий потік:
OpenAPI schema
↓
openapi-react-query-codegen
↓
TypeScript client
↓
TanStack Query hooks/options
Зазвичай це dev dependency, але для install-time malware це не означає менший ризик.
Наскільки популярним був пакет?
Станом на 31 серпня npm показував приблизно 155 тисяч завантажень на тиждень.[6]
Це динамічна метрика.
Важливіше те, де запускається codegen: на машинах розробників та CI runners, часто поруч із GitHub, SSH, cloud та deployment credentials.
Атака почалася в GitHub Actions
Release workflow слухав:
on:
issue_comment:
types: [created]
Сам issue_comment не є вразливістю.
Небезпечною була комбінація:
зовнішній коментар
+
відсутність authorization автора
+
checkout PR-коду
+
виконання коду
+
id-token: write
+
publish
Один коментар міг відкрити release path
Workflow фактично перевіряв, чи коментар належить pull request і має точний текст npm publish.[1][2]
Надійної перевірки на ролі:
OWNER
MEMBER
COLLABORATOR
не було.
Зовнішній GitHub account міг відкрити fork PR і коментарем дістатися до привілейованого job.
Checkout fork-коду перетворив input на code execution
Після checkout PR-коду workflow запускав:
pnpm install
Так attacker-controlled content виконувався всередині release job з високими привілеями.
GitHub давно рекомендує не запускати недовірений PR-код у привілейованих workflows.[9]
id-token: write дозволив Trusted Publishing
Job мав:
permissions:
id-token: write
Це нормальна permission для npm Trusted Publishing через OIDC.[10]
Проблемою був не сам OIDC.
Проблемою було те, що код атакувальника запускався в job, який мав право отримати trusted workload identity.
Не потрібно було красти пароль чи token
StepSecurity підкреслює, що нападнику не був потрібен ні npm-пароль maintainer-а, ні довгоживучий publish token.[2]
Старий threat model:
вкрасти token
→ publish
Новий:
вплинути на trusted workflow
→ workflow отримує валідну identity
→ publish
Безпекова межа переміщується від account security до CI/CD architecture.
Шкідливі release мали валідну provenance
Публікації пройшли через справжній workflow і Trusted Publishing, тому отримали валідні provenance attestations.[1][3]
Provenance коректно підтверджувала:
цей artifact походить
з цього repository
через цей workflow
Але не визначала, чи PR-код був авторизований та безпечний.
Provenance не є malware scanner
Provenance може підтвердити:
- походження build,
- identity workflow,
- зв’язок із source,
- chain of custody.
Вона не гарантує автоматично:
- безпечний source code,
- авторизований PR,
- безпечну workflow logic,
- відсутність malware.
Тому:
valid provenance
≠
safe package
npm Trusted Publishing використовує OIDC для аутентифікації workload, а provenance для підтвердження походження, не як універсальний malware verdict.[10]
Перший execution path: binding.gyp
Деякі стабільні шкідливі версії не мали очевидного:
"preinstall": "..."
Натомість вони додавали:
binding.gyp
GYP/Python evaluation була використана так, щоб зрештою виконати JavaScript payload системною командою.[1][5]
Scanner, який перевіряє лише lifecycle scripts у package.json, може таку схему пропустити.
Другий execution path: preinstall
Наступні стабільні версії додали:
"preinstall": "node 3FWCvzduYZg.js"
Це:
0.5.5
1.6.4
2.2.2
3.0.4
Попередні стабільні:
0.5.4
1.6.3
2.2.1
3.0.3
використовували binding.gyp без явного lifecycle script.[1]
Два prerelease були іншими
Два 0.0.0-* prerelease містили шкідливий preinstall, але через:
"files": ["dist"]
payload files не потрапили до tarball.[1]
Усі 10 публікацій класифіковані як шкідливі, але технічно це не ідентичні artifacts з однаковою execution path.
Основний payload: 3FWCvzduYZg.js
Стабільні шкідливі tarballs містили:
3FWCvzduYZg.js
розміром приблизно 4,4–6,4 MB залежно від версії.[1]
StepSecurity показав, що 0.5.4 зросла з приблизно 41 KB у 0.5.3 до понад 5,6 MB.[2]
Різкий package-size delta є корисним supply-chain anomaly signal.
Що робив payload у runtime?
StepSecurity запускав уражені версії в ізольованих GitHub-hosted runners.[2]
Спостерігалася така активність:
Node запускає payload
→ curl завантажує Bun
→ Bun працює з /tmp/trinnyyyy-*
→ викликається gh auth token
→ опитується Git Credential Manager
→ перевіряються ssh / scp
→ перелічуються процеси
→ запускається тимчасовий updater.py
Також фіксували доступ до GitHub API та probing Google Cloud metadata hostname.[2]
Це malware, орієнтований на credentials.
Які credentials вважати потенційно скомпрометованими?
Реальний обсяг залежить від привілеїв процесу.
Якщо шкідлива версія виконалася, потенційно exposed є все, до чого мав доступ процес:
GitHub
npm
SSH
cloud
CI/CD
deployment
source repositories
Це не означає, що кожен secret був фактично викрадений на кожному host.
Чому dev dependency може бути критичною
Аргумент:
це лише codegen
в browser bundle не потрапляє
не враховує install-time malware.
Payload достатньо запуститися під час:
npm install
pnpm install
yarn install
Developer та CI environments часто містять найцінніші credentials у всьому software lifecycle.
Package install є code-execution surface
Package managers підтримують:
preinstall
install
postinstall
prepare
а native-addon flows можуть запускати node-gyp.
Тому installation фактично означає:
dependency resolution
+
download
+
potential code execution
Чи точно це TeamPCP?
JFrog, Socket та інші дослідники пов’язують payload із Mini Shai-Hulud та спорідненими варіантами.[3][4]
Але точна атрибуція operator не доведена публічно остаточно.
Можливі:
- той самий actor,
- залишковий доступ,
- reuse malware,
- copycat.
JFrog прямо відзначає цю невизначеність.[4]
Чому Mini Shai-Hulud знову з’являється?
Ця campaign family орієнтована на credential theft, CI/CD, GitHub, package registries та propagation.[12]
Повторюваний pattern:
увійти через release path
→ отримати publish authority
→ випустити trusted-looking package
→ виконатися на developer/CI host
→ шукати нові credentials
Травнева атака на TanStack була технічно іншою
11 травня 2026 року постраждав repository Router/Start.[11]
Атака поєднувала:
pull_request_target,- GitHub Actions cache poisoning,
- витяг OIDC token з пам’яті runner.
Результат:
42 packages
84 шкідливі версії
Це інший root cause, ніж 28 серпня.
Query був чистим і в травні
TanStack у своєму official postmortem зазначив, що постраждав лише Router/Start, а Query був серед unaffected families.[11]
Отже:
травень → Router/Start
серпень → незалежний codegen для Query
Жоден випадок не означає компрометацію @tanstack/react-query.
Травневий інцидент мав реальний downstream impact
OpenAI підтвердила, що два пристрої співробітників були зачеплені травневою атакою TanStack/Mini Shai-Hulud.[13]
OpenAI описала credential-focused activity та обмежену exfiltration credential material з частини source repositories, але не знайшла доказів доступу до user data чи компрометації production systems або IP.[13]
Коротке exposure window не гарантує малого впливу.
Серпень vs травень
Травень
fork PR
→ pull_request_target
→ poisoned cache
→ trusted release відновлює cache
→ OIDC token із memory
→ publish
Серпень
fork PR
→ публічний коментар "npm publish"
→ privileged workflow checkout-ить PR
→ pnpm install запускає attacker code
→ job уже має id-token: write
→ Trusted Publishing
Серпневий шлях був простішим.
Як перевірити, чи проєкт був уражений?
Перевірте всі lockfiles, SBOM та історичні CI records на наявність усіх 10 версій.[1]
Зокрема:
package-lock.json
pnpm-lock.yaml
yarn.lock
bun.lock
SBOM
CI install logs
dependency caches
Поточний package.json недостатній.
Важливі IOC
3FWCvzduYZg.js
binding.gyp
is_it_this_simple.js
nu.js
/tmp/trinnyyyy-*/bun
/tmp/*/updater.py
Також шукайте несподівані Bun downloads, gh auth token, Git Credential Manager calls та node-gyp execution у пакеті, якому native code не потрібен.
Що робити після ураженої інсталяції?
GitHub advisory та StepSecurity рекомендують вважати host потенційно compromised.[1][2]
Мінімальна response:
1. зупинити build
2. ізолювати host
3. використати окрему clean machine
4. ротувати всі доступні credentials
5. invalidate sessions
6. перевірити GitHub/npm/cloud audit logs
7. видалити dependency caches
8. відкинути artifacts з ураженого runner
9. rebuild із чистих inputs
Недостатньо ротувати лише npm token.
Які версії безпечні?
GitHub advisory вказує 3.0.2 як known-good і зазначає, що версії, опубліковані до 28 серпня, цим інцидентом не зачеплені.[1]
StepSecurity вказує чистих попередників:
0.5.3
1.6.2
2.2.0
3.0.2
31 серпня npm знову показує:
latest = 3.0.2
Що виправив upstream?
За advisory:[1]
- видалив
issue_commenttrigger, - переніс release на tag push,
- revoked npm Trusted Publisher,
- ротував long-lived tokens,
- повернув
latestна3.0.2, - прибрав
predist-tag, - deprecated усі 10 шкідливих версій,
- запросив видалення в npm.
Snyk також класифікує компрометацію як Critical із CVSS 9.6, що підтверджує серйозність виконання шкідливого коду під час installation.[8] Важливим контекстом є й додатковий hardening cache, workflows та release pipeline, який TanStack запровадив після травневого інциденту.[14]
Як harden GitHub Actions
- Ніколи не запускати untrusted PR code у privileged job.
- Для comment-driven workflows перевіряти authorization actor-а.
id-token: writeдавати лише справжньому publish job.- Розділяти build і publish.
- Використовувати trusted trigger або approval.
- Third-party Actions pin-ити на commit SHA.[9]
Як harden dependency installation
Практичні controls:
- lockfiles,
- frozen installs,
- minimum release age/cooldown,
- monitoring нових publish,
- package-size anomaly detection,
- lifecycle-script policy,
- sandboxed CI,
- outbound network restrictions,
- malicious-package scanning поряд із CVE,
- SBOM,
- мінімальні credentials на runners.
npm Trusted Publishing залишається корисним.[10]
Але:
Trusted Publishing
має поєднуватися з:
Trusted Workflow Design
Production checklist
Dependencies
- Lockfile обов’язковий.
- CI блокує несподівані зміни lockfile.
- Minimum release age/cooldown.
- Нова версія не потрапляє одразу в production.
- Моніторимо package-size changes.
- Перевіряємо install scripts.
- Несподіваний
binding.gypвикликає alert. - Malware scanning доповнює CVE scanning.
GitHub Actions
- Fork code ніколи не виконується privileged.
-
issue_commentмає authorization gate. -
pull_request_targetлише коли справді потрібно. -
GITHUB_TOKENмає least privilege. -
id-token: writeлише там, де необхідно. - Publish потребує trusted trigger.
- Build і publish розділені.
- Actions pinned to SHA.
- Comment-driven automation має негативний test для неавторизованого користувача.
- Release workflow ніколи не checkout-ить код із недовіреного fork.
npm
- Trusted Publisher обмежений необхідними workflows.
- Немає зайвих long-lived publish tokens.
- Publish моніториться realtime.
- Незапланований release викликає alert.
- Provenance перевіряється, але не вважається malware verdict.
- Є runbook для deprecation/revocation.
Runners та endpoints
- CI runner ephemeral.
- Мінімум secrets у workspace.
- Cloud credentials short-lived.
- SSH keys мають мінімальний scope.
- Outbound traffic моніториться.
- Dependency install не має широкого доступу до infrastructure.
- Endpoint-и мають EDR/runtime monitoring.
- Host можна швидко ізолювати.
Incident response
- Owner dependency incident визначений.
- Можливий історичний search у lockfiles.
- CI caches можна швидко очистити.
- Sessions можна invalidувати.
- Є карта credentials, доступних runner.
- Rotation виконується з clean machine.
- Перевіряються unauthorized npm publishes.
- Аналізуються GitHub audit logs.
Висновок POLPROG
Компрометація @7nohe/openapi-react-query-codegen є дуже показовим прикладом того, як у 2026 році змінюються software supply-chain attacks.
Атакувальнику не потрібно було:
зламувати npm
красти пароль maintainer-а
обходити 2FA
красти класичний publish token
Достатньо було домогтися виконання untrusted code у workflow, який уже мав легітимну publish identity.
Безпекова межа зміщується до:
CI/CD
workflow triggers
OIDC
release authorization
dependency execution
Другий урок — provenance.
Валідна provenance тут не «помилилася». Вона правильно підтвердила походження шкідливого build-а.
Третій урок — точність.
TanStack Query сам не був скомпрометований.
Постраждав незалежний інструмент його екосистеми.
Станом на 31 серпня шкідливі версії відомі, advisory опублікований, latest повернуто на 3.0.2, а release workflow змінено.[1][6]
Ключове питання для JavaScript-команди не лише:
Чи використовували ми цей package?
А:
Чи може в нашому release pipeline недовірений input потрапити до job із publish authority?
Якщо відповідь не очевидна одразу, варто провести аудит GitHub Actions.

