👋

2025/9/21 Shai-Hulud対策としてsafe-chainをWindows11でインストール失敗!簡易検査スクリプトを作った!

に公開

はじめに

背景:Shai-Huludとは?

Shai-Huludは、複数の人気npmパッケージに感染し、インストール時に自動的に他のパッケージへ感染を広げる自己伝播型マルウェアです。CrowdStrikeやNativeScript関連のパッケージが多数含まれており、CI/CD環境や開発マシンへの影響が懸念されています。
2025年9月現在、npm のサプライチェーン攻撃対策として safe-chain を導入する動きが出ています。
safe-chain は npm / npx / yarn などのコマンドをラップして、マルウェアを含む依存関係を検知するツールです。
特に npm install 実行時に、インストール対象のパッケージやその依存関係を事前にスキャンし、Shai-Hulud のような既知の感染パッケージが含まれていないかをチェックします。

しかし、Windows 11 + PowerShell 環境で safe-chain setup を実行するとメモリ不足エラーで落ちました。
ChatGptはじめいろいろ聞いたけど結局、インストールできませんでした。
そこで今回は、実際に遭遇したエラーと、それを手動で修正したが失敗した件。
さらに、代替の簡易的な方法としてPythonを使って感染報告のモジュールがpackage-lock.jsonに入っていないか調べる方法をご紹介します。


環境

  • Windows 11Home 24H2
  • Node.js v22.14.0
  • npm v10 系
  • PowerShell 5.1

Windows 11 + PowerShell で safe-chain インストール失敗の詳細

結局、失敗したので実行しないほうがいいかもしれません。問題なくできるかもしれませんが、もしうまくいかなかったときの参考にしてください。

インストール

まずはグローバルに safe-chain をインストールします。

npm install -g @aikidosec/safe-chain

ここまでは問題ありません。(結局、最終的にはアンインストールしました。)


safe-chain setup での失敗

公式手順どおりに

safe-chain setup

を実行すると、次のようなエラーで失敗しました。

FATAL ERROR: invalid array length Allocation failed - JavaScript heap out of memory

原因は PowerShell の alias 設定を自動で書き込む処理がメモリ不足でクラッシュしている模様です。
Node.js のメモリ上限を8Gまで増やしてみましたがダメでした。


失敗した対策:PowerShell プロファイルに手動で関数を定義

そこで setup は使わず、PowerShell のプロファイルに自分でラッパー関数を書く方法を取りました。

プロファイルファイルを開きます:

notepad $PROFILE

例:C:\Users\<ユーザー名>\OneDrive\ドキュメント\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

ファイル末尾に以下を追記します:

function npm  { & "C:\Users\<ユーザー名>\AppData\Roaming\npm\safe-chain.ps1" npm  @args }

⚠️ safe-chain.ps1 のパスは環境に合わせて修正してください。通常は
C:\Users\<ユーザー名>\AppData\Roaming\npm\safe-chain.ps1
にあります。


動作確認

PowerShell を再起動してから、次を実行します。

Get-Command npm

結果が Function になっていれば OK。

次にテスト用パッケージをインストールします:

npm install safe-chain-test

結果

本来、危険と判定しインストールしないはずですが、
safe-chain が走らず、そのままインストールされました。
ということで、うまくいかなかった。

元に戻した

戻し方は以下の通りです。

npm uninstall -g @aikidosec/safe-chain

Microsoft.PowerShell_profile.ps1 に書いた safe-chain 関連の行は、コメントアウトまたは削除。


🛡️ 「Shai-Hulud」に感染報告されたモジュールがpackage-lock.json の中にないか検査するPythonスクリプト


はじめに

Pythonで package-lock.json の中に感染パッケージが入っていないか検査するスクリプトを作りました。

感染パッケージの一覧はこちらを参考にさせてもらいました。:Qiita記事

Pythonで感染チェックする方法

1. 感染パッケージリストの整形

Qiitaの記事の shaiHuludCompromisedPackages はJavaScript形式なので、Python辞書に変換する必要があります。以下のスクリプトで変換できます:

kansen_check\kansen_check_junbi.py
import json
import re

# JavaScript形式の配列(Qiitaからコピペ、最後の,なし)
input_js = """
[
  { package: "@ahmedhfarag/ngx-perfect-scrollbar", versions: ["20.0.20"] },
  { package: "@ahmedhfarag/ngx-virtual-scroller", versions: ["4.0.4"] },
  { package: "@art-ws/common", versions: ["2.0.22"] },
  { package: "@art-ws/config-eslint", versions: ["2.0.4", "2.0.5"] },
  { package: "@art-ws/config-ts", versions: ["2.0.7", "2.0.8"] },
  { package: "@art-ws/db-context", versions: ["2.0.21"] },
  { package: "@art-ws/di", versions: ["2.0.28"] },
  { package: "@art-ws/di-node", versions: ["2.0.13"] },
  { package: "@art-ws/eslint", versions: ["1.0.5", "1.0.6"] },
  { package: "@art-ws/fastify-http-server", versions: ["2.0.24"] },
  { package: "@art-ws/http-server", versions: ["2.0.21"] },
  { package: "@art-ws/openapi", versions: ["0.1.9"] },
  { package: "@art-ws/package-base", versions: ["1.0.5", "1.0.6"] },
  { package: "@art-ws/prettier", versions: ["1.0.5", "1.0.6"] },
  { package: "@art-ws/slf", versions: ["2.0.15"] },
  { package: "@art-ws/ssl-info", versions: ["1.0.9", "1.0.10"] },
  { package: "@art-ws/web-app", versions: ["1.0.3", "1.0.4"] },
  { package: "@crowdstrike/commitlint", versions: ["8.1.1", "8.1.2"] },
  { package: "@crowdstrike/falcon-shoelace", versions: ["0.4.1"] },
  { package: "@crowdstrike/foundry-js", versions: ["0.19.1", "0.19.2"] },
  { package: "@crowdstrike/glide-core", versions: ["0.34.2", "0.34.3"] },
  { package: "@crowdstrike/logscale-dashboard", versions: ["1.205.1", "1.205.2"] },
  { package: "@crowdstrike/logscale-file-editor", versions: ["1.205.1", "1.205.2"] },
  { package: "@crowdstrike/logscale-parser-edit", versions: ["1.205.1", "1.205.2"] },
  { package: "@crowdstrike/logscale-search", versions: ["1.205.1", "1.205.2"] },
  { package: "@crowdstrike/tailwind-toucan-base", versions: ["5.0.1", "5.0.2"] },
  { package: "@ctrl/deluge", versions: ["7.2.1", "7.2.2"] },
  { package: "@ctrl/golang-template", versions: ["1.4.2", "1.4.3"] },
  { package: "@ctrl/magnet-link", versions: ["4.0.3", "4.0.4"] },
  { package: "@ctrl/ngx-codemirror", versions: ["7.0.1", "7.0.2"] },
  { package: "@ctrl/ngx-csv", versions: ["6.0.1", "6.0.2"] },
  { package: "@ctrl/ngx-emoji-mart", versions: ["9.2.1", "9.2.2"] },
  { package: "@ctrl/ngx-rightclick", versions: ["4.0.1", "4.0.2"] },
  { package: "@ctrl/qbittorrent", versions: ["9.7.1", "9.7.2"] },
  { package: "@ctrl/react-adsense", versions: ["2.0.1", "2.0.2"] },
  { package: "@ctrl/shared-torrent", versions: ["6.3.1", "6.3.2"] },
  { package: "@ctrl/tinycolor", versions: ["4.1.1", "4.1.2"] },
  { package: "@ctrl/torrent-file", versions: ["4.1.1", "4.1.2"] },
  { package: "@ctrl/transmission", versions: ["7.3.1"] },
  { package: "@ctrl/ts-base32", versions: ["4.0.1", "4.0.2"] },
  { package: "@hestjs/core", versions: ["0.2.1"] },
  { package: "@hestjs/cqrs", versions: ["0.1.6"] },
  { package: "@hestjs/demo", versions: ["0.1.2"] },
  { package: "@hestjs/eslint-config", versions: ["0.1.2"] },
  { package: "@hestjs/logger", versions: ["0.1.6"] },
  { package: "@hestjs/scalar", versions: ["0.1.7"] },
  { package: "@hestjs/validation", versions: ["0.1.6"] },
  { package: "@nativescript-community/arraybuffers", versions: ["1.1.6", "1.1.7", "1.1.8"] },
  { package: "@nativescript-community/gesturehandler", versions: ["2.0.35"] },
  { package: "@nativescript-community/perms", versions: ["3.0.5", "3.0.6", "3.0.7", "3.0.8"] },
  { package: "@nativescript-community/sqlite", versions: ["3.5.2", "3.5.3", "3.5.4", "3.5.5"] },
  { package: "@nativescript-community/text", versions: ["1.6.9", "1.6.10", "1.6.11", "1.6.12"] },
  { package: "@nativescript-community/typeorm", versions: ["0.2.30", "0.2.31", "0.2.32", "0.2.33"] },
  { package: "@nativescript-community/ui-collectionview", versions: ["6.0.6"] },
  { package: "@nativescript-community/ui-document-picker", versions: ["1.1.27", "1.1.28"] },
  { package: "@nativescript-community/ui-drawer", versions: ["0.1.30"] },
  { package: "@nativescript-community/ui-image", versions: ["4.5.6"] },
  { package: "@nativescript-community/ui-label", versions: ["1.3.35", "1.3.36", "1.3.37"] },
  { package: "@nativescript-community/ui-material-bottom-navigation", versions: ["7.2.72", "7.2.73", "7.2.74", "7.2.75"] },
  { package: "@nativescript-community/ui-material-bottomsheet", versions: ["7.2.72"] },
  { package: "@nativescript-community/ui-material-core", versions: ["7.2.72", "7.2.73", "7.2.74", "7.2.75"] },
  { package: "@nativescript-community/ui-material-core-tabs", versions: ["7.2.72", "7.2.73", "7.2.74", "7.2.75"] },
  { package: "@nativescript-community/ui-material-ripple", versions: ["7.2.72", "7.2.73", "7.2.74", "7.2.75"] },
  { package: "@nativescript-community/ui-material-tabs", versions: ["7.2.72", "7.2.73", "7.2.74", "7.2.75"] },
  { package: "@nativescript-community/ui-pager", versions: ["14.1.36", "14.1.37", "14.1.38"] },
  { package: "@nativescript-community/ui-pulltorefresh", versions: ["2.5.4", "2.5.5", "2.5.6", "2.5.7"] },
  { package: "@nexe/config-manager", versions: ["0.1.1"] },
  { package: "@nexe/eslint-config", versions: ["0.1.1"] },
  { package: "@nexe/logger", versions: ["0.1.3"] },
  { package: "@nstudio/angular", versions: ["20.0.4", "20.0.5", "20.0.6"] },
  { package: "@nstudio/focus", versions: ["20.0.4", "20.0.5", "20.0.6"] },
  { package: "@nstudio/nativescript-checkbox", versions: ["2.0.6", "2.0.7", "2.0.8", "2.0.9"] },
  { package: "@nstudio/nativescript-loading-indicator", versions: ["5.0.1", "5.0.2", "5.0.3", "5.0.4"] },
  { package: "@nstudio/ui-collectionview", versions: ["5.1.11", "5.1.12", "5.1.13", "5.1.14"] },
  { package: "@nstudio/web", versions: ["20.0.4"] },
  { package: "@nstudio/web-angular", versions: ["20.0.4"] },
  { package: "@nstudio/xplat", versions: ["20.0.5", "20.0.6", "20.0.7"] },
  { package: "@nstudio/xplat-utils", versions: ["20.0.5", "20.0.6", "20.0.7"] },
  { package: "@operato/board", versions: ["9.0.36", "9.0.37", "9.0.38", "9.0.39", "9.0.40", "9.0.41", "9.0.42", "9.0.43", "9.0.44", "9.0.45", "9.0.46"] },
  { package: "@operato/data-grist", versions: ["9.0.29", "9.0.35", "9.0.36", "9.0.37"] },
  { package: "@operato/graphql", versions: ["9.0.22", "9.0.35", "9.0.36", "9.0.37", "9.0.38", "9.0.39", "9.0.40", "9.0.41", "9.0.42", "9.0.43", "9.0.44", "9.0.45", "9.0.46"] },
  { package: "@operato/headroom", versions: ["9.0.2", "9.0.35", "9.0.36", "9.0.37"] },
  { package: "@operato/help", versions: ["9.0.35", "9.0.36", "9.0.37", "9.0.38", "9.0.39", "9.0.40", "9.0.41", "9.0.42", "9.0.43", "9.0.44", "9.0.45", "9.0.46"] },
  { package: "@operato/i18n", versions: ["9.0.35", "9.0.36", "9.0.37"] },
  { package: "@operato/input", versions: ["9.0.27", "9.0.35", "9.0.36", "9.0.37", "9.0.38", "9.0.39", "9.0.40", "9.0.41", "9.0.42", "9.0.43", "9.0.44", "9.0.45", "9.0.46"] },
  { package: "@operato/layout", versions: ["9.0.35", "9.0.36", "9.0.37"] },
  { package: "@operato/popup", versions: ["9.0.22", "9.0.35", "9.0.36", "9.0.37", "9.0.38", "9.0.39", "9.0.40", "9.0.41", "9.0.42", "9.0.43", "9.0.44", "9.0.45", "9.0.46"] },
  { package: "@operato/pull-to-refresh", versions: ["9.0.36", "9.0.37", "9.0.38", "9.0.39", "9.0.40", "9.0.41", "9.0.42"] },
  { package: "@operato/shell", versions: ["9.0.22", "9.0.35", "9.0.36", "9.0.37", "9.0.38", "9.0.39"] },
  { package: "@operato/styles", versions: ["9.0.2", "9.0.35", "9.0.36", "9.0.37"] },
  { package: "@operato/utils", versions: ["9.0.22", "9.0.35", "9.0.36", "9.0.37", "9.0.38", "9.0.39", "9.0.40", "9.0.41", "9.0.42", "9.0.43", "9.0.44", "9.0.45", "9.0.46"] },
  { package: "@teselagen/bounce-loader", versions: ["0.3.16", "0.3.17"] },
  { package: "@teselagen/liquibase-tools", versions: ["0.4.1"] },
  { package: "@teselagen/range-utils", versions: ["0.3.14", "0.3.15"] },
  { package: "@teselagen/react-list", versions: ["0.8.19", "0.8.20"] },
  { package: "@teselagen/react-table", versions: ["6.10.19"] },
  { package: "@thangved/callback-window", versions: ["1.1.4"] },
  { package: "@things-factory/attachment-base", versions: ["9.0.43", "9.0.44", "9.0.45", "9.0.46", "9.0.47", "9.0.48", "9.0.49", "9.0.50"] },
  { package: "@things-factory/auth-base", versions: ["9.0.43", "9.0.44", "9.0.45"] }
]
"""

import re
import json

def convert_js_array_to_python_dict(js_text):
    # コメント行を削除
    js_text = re.sub(r'//.*', '', js_text)

    # const宣言やセミコロンを削除
    js_text = re.sub(r'const\s+\w+\s*=\s*', '', js_text)
    js_text = js_text.strip().rstrip(';')

    # JavaScriptのキーをJSON形式に変換
    js_text = re.sub(r'package\s*:', '"package":', js_text)
    js_text = re.sub(r'versions\s*:', '"versions":', js_text)

    # シングルクォートをダブルクォートに(必要なら)
    js_text = js_text.replace("'", '"')

    # 配列が [] で囲まれているか確認
    if not js_text.startswith('['):
        js_text = '[' + js_text
    if not js_text.endswith(']'):
        js_text = js_text + ']'

    # JSONとして読み込む
    try:
        js_array = json.loads(js_text)
    except json.JSONDecodeError as e:
        print("❌ JSON変換に失敗しました:", e)
        return {}

    # Python辞書形式に変換
    py_dict = {entry["package"]: entry["versions"] for entry in js_array}
    return py_dict

if __name__ == "__main__":
    # result = js_to_python_dict(input_js)
    result = convert_js_array_to_python_dict(input_js)
    print("Python形式の辞書:")
    print(json.dumps(result, indent=2))

    output_filename = 'kansen_check_junbi_output.txt'
    with open(output_filename, 'w', encoding='utf-8') as f:
        json.dump(result, f, indent=2, ensure_ascii=False)

    print(f"結果を {output_filename} に出力しました。")

2. package-lock.json の解析

kansen_check\kansen_check.py
import json

# Read the compromised packages list from the generated file
try:
    with open('kansen_check_junbi_output.txt', 'r', encoding='utf-8') as f:
        shai_hulud_packages = json.load(f)
except FileNotFoundError:
    print("エラー: kansen_check_junbi_output.txt が見つかりません。")
    print("先に kansen_check_junbi.py を実行してください。")
    shai_hulud_packages = {}
except json.JSONDecodeError:
    print("エラー: kansen_check_junbi_output.txt の内容が正しいJSON形式ではありません。")
    shai_hulud_packages = {}

def check_package_lock(file_path, compromised_list):
    with open(file_path, "r", encoding="utf-8") as f:
        data = json.load(f)

    results = []

    # npm v7+ の形式
    packages = data.get("packages", {})
    for pkg_path, pkg_info in packages.items():
        if pkg_path.startswith("node_modules/"):
            pkg_name = pkg_path.replace("node_modules/", "")
            version = pkg_info.get("version")
            if pkg_name in compromised_list and version in compromised_list[pkg_name]:
                results.append((pkg_name, version))

    # npm v6以前の形式
    dependencies = data.get("dependencies", {})
    def recursive_check(deps):
        for name, info in deps.items():
            version = info.get("version")
            if name in compromised_list and version in compromised_list[name]:
                results.append((name, version))
            if "dependencies" in info:
                recursive_check(info["dependencies"])

    recursive_check(dependencies)

    return results
if __name__ == "__main__":
    lock_path = "../package-lock.json"
    compromised = check_package_lock(lock_path, shai_hulud_packages)

    if compromised:
        print("感染パッケージが見つかりました!")
        for pkg, ver in compromised:
            print(f"{pkg}@{ver} は感染バージョンです")
    else:
        print("感染パッケージは見つかりませんでした")

使い方

NPMパッケージ感染チェッカー

このツールは、package-lock.jsonファイルに記載されているNPMパッケージの中に、潜在的に危険な(汚染された)パッケージが含まれていないかチェックするためのPythonスクリプトです。
参考:https://qiita.com/ebisawa_a/items/5e6872b82da116892b2f

ファイル構成

  • kansen_check_junbi.py: 汚染されたパッケージのリストを準備します。
  • kansen_check_junbi_output.txt: 生成された汚染パッケージのリスト(Pythonの辞書形式)。
  • kansen_check.py: あなたのプロジェクトに対してチェックを実行します。

ファイルの配置

 ├ package-lock.json
 ├ kansen_check
 │ ├ kansen_check_junbi.py
 │ ├ kansen_check.py
 │ └ kansen_check_junbi_output.txt
 │   

ステップ1:汚染パッケージリストの更新

  1. kansen_check_junbi.pyを編集する:
    このスクリプトを開き、input_js変数を最新の汚染パッケージリストで更新します。形式はJavaScriptのオブジェクト配列です。

  2. スクリプトを実行する:
    kansen_checkディレクトリ内で、以下のコマンドを実行します。

    python kansen_check_junbi.py
    

    これにより、kansen_check_junbi_output.txtファイルが生成または更新されます。(巻末に記載してあります)

ダミーを入れてみる(package-lock.jsonにあるモジュールをダミーで入れて検出できているかチェックする方法)

例:以下のモジュールがpackage-lock.jsonにある場合。

  "node_modules/@babel/code-frame": {
      "version": "7.27.1",
      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
      "integrity": "sha***Fg==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@babel/helper-validator-identifier": "^7.27.1",
        "js-tokens": "^4.0.0",
        "picocolors": "^1.1.1"
      },
      "engines": {
        "node": ">=6.9.0"
      }
    },

以下のように必要な情報だけに変更して、kansen_check\kansen_check_junbi_output.txtに追記
"@babel/code-frame": [
"7.27.1"
],

ステップ2:プロジェクトのチェック

  1. チェック用のスクリプトを実行する:
    package-lock.jsonと同階層のkansen_checkにあるkansen_check.pyスクリプトを実行します。

    cd kansen_check
    python kansen_check.py
    
  2. 結果を確認する:

    • 汚染されたパッケージが見つからない場合、感染パッケージは見つかりませんでしたと表示されます。
    • 汚染されたパッケージが見つかった場合、そのパッケージ名とバージョンの一覧が表示されます。

例:
感染パッケージが見つかりました!
@babel/code-frame@7.27.1 は感染バージョンです
注意;動作確認のためダミーで@babel/code-frame@7.27.1 入れた場合なので、これは、安全です。

動作の仕組み

  • 汚染されたパッケージのリストは、kansen_check_junbi.py内のinput_js変数で定義されます。このリストのキーは、node_modules/という接頭辞を含まない、正確なパッケージ名(例:@babel/code-frame)である必要があります。
  • kansen_check.pyは、生成された.txtファイルを読み込み、あなたのプロジェクトの../package-lock.jsonに記載されているパッケージ名およびバージョンと比較します。

まとめ

  • Shai-Huludはnpmのサプライチェーンを狙った自己伝播型マルウェア
  • 私のWindows11ではsafe-chainはインストールできなかった
  • Qiitaの感染リストをPython辞書に変換して活用 package-lock.json を解析した
  • safe-chainはnpm install前に検出可能だが、package-lock.jsonを解析する方法は、すでにインストールされているものを調べているので感染は防げない。
  • 私の場合、感染はしていなかった。ただし、現状報告されているモジュールだけしか調べられていない。
  • 今後、safe-chainの更新を待って再挑戦。

おまけ

kansen_check\kansen_check_junbi_output.txtの中身

kansen_check\kansen_check_junbi_output.txt
{
  "@ahmedhfarag/ngx-perfect-scrollbar": [
    "20.0.20"
  ],
  "@ahmedhfarag/ngx-virtual-scroller": [
    "4.0.4"
  ],
  "@art-ws/common": [
    "2.0.22"
  ],
  "@art-ws/config-eslint": [
    "2.0.4",
    "2.0.5"
  ],
  "@art-ws/config-ts": [
    "2.0.7",
    "2.0.8"
  ],
  "@art-ws/db-context": [
    "2.0.21"
  ],
  "@art-ws/di": [
    "2.0.28"
  ],
  "@art-ws/di-node": [
    "2.0.13"
  ],
  "@art-ws/eslint": [
    "1.0.5",
    "1.0.6"
  ],
  "@art-ws/fastify-http-server": [
    "2.0.24"
  ],
  "@art-ws/http-server": [
    "2.0.21"
  ],
  "@art-ws/openapi": [
    "0.1.9"
  ],
  "@art-ws/package-base": [
    "1.0.5",
    "1.0.6"
  ],
  "@art-ws/prettier": [
    "1.0.5",
    "1.0.6"
  ],
  "@art-ws/slf": [
    "2.0.15"
  ],
  "@art-ws/ssl-info": [
    "1.0.9",
    "1.0.10"
  ],
  "@art-ws/web-app": [
    "1.0.3",
    "1.0.4"
  ],
  "@crowdstrike/commitlint": [
    "8.1.1",
    "8.1.2"
  ],
  "@crowdstrike/falcon-shoelace": [
    "0.4.1"
  ],
  "@crowdstrike/foundry-js": [
    "0.19.1",
    "0.19.2"
  ],
  "@crowdstrike/glide-core": [
    "0.34.2",
    "0.34.3"
  ],
  "@crowdstrike/logscale-dashboard": [
    "1.205.1",
    "1.205.2"
  ],
  "@crowdstrike/logscale-file-editor": [
    "1.205.1",
    "1.205.2"
  ],
  "@crowdstrike/logscale-parser-edit": [
    "1.205.1",
    "1.205.2"
  ],
  "@crowdstrike/logscale-search": [
    "1.205.1",
    "1.205.2"
  ],
  "@crowdstrike/tailwind-toucan-base": [
    "5.0.1",
    "5.0.2"
  ],
  "@ctrl/deluge": [
    "7.2.1",
    "7.2.2"
  ],
  "@ctrl/golang-template": [
    "1.4.2",
    "1.4.3"
  ],
  "@ctrl/magnet-link": [
    "4.0.3",
    "4.0.4"
  ],
  "@ctrl/ngx-codemirror": [
    "7.0.1",
    "7.0.2"
  ],
  "@ctrl/ngx-csv": [
    "6.0.1",
    "6.0.2"
  ],
  "@ctrl/ngx-emoji-mart": [
    "9.2.1",
    "9.2.2"
  ],
  "@ctrl/ngx-rightclick": [
    "4.0.1",
    "4.0.2"
  ],
  "@ctrl/qbittorrent": [
    "9.7.1",
    "9.7.2"
  ],
  "@ctrl/react-adsense": [
    "2.0.1",
    "2.0.2"
  ],
  "@ctrl/shared-torrent": [
    "6.3.1",
    "6.3.2"
  ],
  "@ctrl/tinycolor": [
    "4.1.1",
    "4.1.2"
  ],
  "@ctrl/torrent-file": [
    "4.1.1",
    "4.1.2"
  ],
  "@ctrl/transmission": [
    "7.3.1"
  ],
  "@ctrl/ts-base32": [
    "4.0.1",
    "4.0.2"
  ],
  "@hestjs/core": [
    "0.2.1"
  ],
  "@hestjs/cqrs": [
    "0.1.6"
  ],
  "@hestjs/demo": [
    "0.1.2"
  ],
  "@hestjs/eslint-config": [
    "0.1.2"
  ],
  "@hestjs/logger": [
    "0.1.6"
  ],
  "@hestjs/scalar": [
    "0.1.7"
  ],
  "@hestjs/validation": [
    "0.1.6"
  ],
  "@nativescript-community/arraybuffers": [
    "1.1.6",
    "1.1.7",
    "1.1.8"
  ],
  "@nativescript-community/gesturehandler": [
    "2.0.35"
  ],
  "@nativescript-community/perms": [
    "3.0.5",
    "3.0.6",
    "3.0.7",
    "3.0.8"
  ],
  "@nativescript-community/sqlite": [
    "3.5.2",
    "3.5.3",
    "3.5.4",
    "3.5.5"
  ],
  "@nativescript-community/text": [
    "1.6.9",
    "1.6.10",
    "1.6.11",
    "1.6.12"
  ],
  "@nativescript-community/typeorm": [
    "0.2.30",
    "0.2.31",
    "0.2.32",
    "0.2.33"
  ],
  "@nativescript-community/ui-collectionview": [
    "6.0.6"
  ],
  "@nativescript-community/ui-document-picker": [
    "1.1.27",
    "1.1.28"
  ],
  "@nativescript-community/ui-drawer": [
    "0.1.30"
  ],
  "@nativescript-community/ui-image": [
    "4.5.6"
  ],
  "@nativescript-community/ui-label": [
    "1.3.35",
    "1.3.36",
    "1.3.37"
  ],
  "@nativescript-community/ui-material-bottom-navigation": [
    "7.2.72",
    "7.2.73",
    "7.2.74",
    "7.2.75"
  ],
  "@nativescript-community/ui-material-bottomsheet": [
    "7.2.72"
  ],
  "@nativescript-community/ui-material-core": [
    "7.2.72",
    "7.2.73",
    "7.2.74",
    "7.2.75"
  ],
  "@nativescript-community/ui-material-core-tabs": [
    "7.2.72",
    "7.2.73",
    "7.2.74",
    "7.2.75"
  ],
  "@nativescript-community/ui-material-ripple": [
    "7.2.72",
    "7.2.73",
    "7.2.74",
    "7.2.75"
  ],
  "@nativescript-community/ui-material-tabs": [
    "7.2.72",
    "7.2.73",
    "7.2.74",
    "7.2.75"
  ],
  "@nativescript-community/ui-pager": [
    "14.1.36",
    "14.1.37",
    "14.1.38"
  ],
  "@nativescript-community/ui-pulltorefresh": [
    "2.5.4",
    "2.5.5",
    "2.5.6",
    "2.5.7"
  ],
  "@nexe/config-manager": [
    "0.1.1"
  ],
  "@nexe/eslint-config": [
    "0.1.1"
  ],
  "@nexe/logger": [
    "0.1.3"
  ],
  "@nstudio/angular": [
    "20.0.4",
    "20.0.5",
    "20.0.6"
  ],
  "@nstudio/focus": [
    "20.0.4",
    "20.0.5",
    "20.0.6"
  ],
  "@nstudio/nativescript-checkbox": [
    "2.0.6",
    "2.0.7",
    "2.0.8",
    "2.0.9"
  ],
  "@nstudio/nativescript-loading-indicator": [
    "5.0.1",
    "5.0.2",
    "5.0.3",
    "5.0.4"
  ],
  "@nstudio/ui-collectionview": [
    "5.1.11",
    "5.1.12",
    "5.1.13",
    "5.1.14"
  ],
  "@nstudio/web": [
    "20.0.4"
  ],
  "@nstudio/web-angular": [
    "20.0.4"
  ],
  "@nstudio/xplat": [
    "20.0.5",
    "20.0.6",
    "20.0.7"
  ],
  "@nstudio/xplat-utils": [
    "20.0.5",
    "20.0.6",
    "20.0.7"
  ],
  "@operato/board": [
    "9.0.36",
    "9.0.37",
    "9.0.38",
    "9.0.39",
    "9.0.40",
    "9.0.41",
    "9.0.42",
    "9.0.43",
    "9.0.44",
    "9.0.45",
    "9.0.46"
  ],
  "@operato/data-grist": [
    "9.0.29",
    "9.0.35",
    "9.0.36",
    "9.0.37"
  ],
  "@operato/graphql": [
    "9.0.22",
    "9.0.35",
    "9.0.36",
    "9.0.37",
    "9.0.38",
    "9.0.39",
    "9.0.40",
    "9.0.41",
    "9.0.42",
    "9.0.43",
    "9.0.44",
    "9.0.45",
    "9.0.46"
  ],
  "@operato/headroom": [
    "9.0.2",
    "9.0.35",
    "9.0.36",
    "9.0.37"
  ],
  "@operato/help": [
    "9.0.35",
    "9.0.36",
    "9.0.37",
    "9.0.38",
    "9.0.39",
    "9.0.40",
    "9.0.41",
    "9.0.42",
    "9.0.43",
    "9.0.44",
    "9.0.45",
    "9.0.46"
  ],
  "@operato/i18n": [
    "9.0.35",
    "9.0.36",
    "9.0.37"
  ],
  "@operato/input": [
    "9.0.27",
    "9.0.35",
    "9.0.36",
    "9.0.37",
    "9.0.38",
    "9.0.39",
    "9.0.40",
    "9.0.41",
    "9.0.42",
    "9.0.43",
    "9.0.44",
    "9.0.45",
    "9.0.46"
  ],
  "@operato/layout": [
    "9.0.35",
    "9.0.36",
    "9.0.37"
  ],
  "@operato/popup": [
    "9.0.22",
    "9.0.35",
    "9.0.36",
    "9.0.37",
    "9.0.38",
    "9.0.39",
    "9.0.40",
    "9.0.41",
    "9.0.42",
    "9.0.43",
    "9.0.44",
    "9.0.45",
    "9.0.46"
  ],
  "@operato/pull-to-refresh": [
    "9.0.36",
    "9.0.37",
    "9.0.38",
    "9.0.39",
    "9.0.40",
    "9.0.41",
    "9.0.42"
  ],
  "@operato/shell": [
    "9.0.22",
    "9.0.35",
    "9.0.36",
    "9.0.37",
    "9.0.38",
    "9.0.39"
  ],
  "@operato/styles": [
    "9.0.2",
    "9.0.35",
    "9.0.36",
    "9.0.37"
  ],
  "@operato/utils": [
    "9.0.22",
    "9.0.35",
    "9.0.36",
    "9.0.37",
    "9.0.38",
    "9.0.39",
    "9.0.40",
    "9.0.41",
    "9.0.42",
    "9.0.43",
    "9.0.44",
    "9.0.45",
    "9.0.46"
  ],
  "@teselagen/bounce-loader": [
    "0.3.16",
    "0.3.17"
  ],
  "@teselagen/liquibase-tools": [
    "0.4.1"
  ],
  "@teselagen/range-utils": [
    "0.3.14",
    "0.3.15"
  ],
  "@teselagen/react-list": [
    "0.8.19",
    "0.8.20"
  ],
  "@teselagen/react-table": [
    "6.10.19"
  ],
  "@thangved/callback-window": [
    "1.1.4"
  ],
  "@things-factory/attachment-base": [
    "9.0.43",
    "9.0.44",
    "9.0.45",
    "9.0.46",
    "9.0.47",
    "9.0.48",
    "9.0.49",
    "9.0.50"
  ],
  "@things-factory/auth-base": [
    "9.0.43",
    "9.0.44",
    "9.0.45"
  ]
}

Discussion