hookcheck について
hookcheckは、パッケージ、エディター、エージェント、ワークフロー設定にある自動実行フックをローカルリポジトリから検出します。決定的な分類と証拠でメンテナーの永続化リスク確認を支援しますが、提示されたREADMEは機能とMCP情報の記載前に途切れています。
主な機能
- 01自動実行箇所の一覧化
- 02フックを安全・危険・不明に分類
- 03ファイル、コマンド、ルール、証拠の表示
活用シーン
- 01リポジトリを開く前の確認
- 02依存関係をインストールする前のフック確認
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
2掲載時のツール一覧 · 6 日前に更新
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| path | string | はい | スキャンするローカルディレクトリ。例:/home/user/project |
詳細
hookcheck
自分より先に実行されるフックを見つけましょう。1 回のスキャンで、すべての自動実行箇所を調べ、1 つの判定を表示します。
クイックスタート
npx @reneturcios/hookcheck scan ./some-repo
現代のサプライチェーン攻撃では、何かを実行させる必要すらありません。npm の postinstall スクリプト、.claude/settings.json 内の Claude Code の SessionStart フック、フォルダーを開いたときに実行されるよう設定された VS Code タスクなど、自動実行されるフックを通じて侵入状態を維持します。Shai-Hulud npm ワームもまさにこの手法を使い、preinstall ドロッパーを仕掛けた後、エージェントやエディターのフックを通じて侵入状態を維持しました。そのため、駆除後も残り、次に開いたときに認証情報を盗み出しました。
hookcheck はローカルリポジトリをスキャンして、すべての自動実行フックを見つけて分類し、CLEAN、REVIEW、COMPROMISED-PATTERN のいずれかの判定を表示します。各検出結果には、ファイル、コマンド、ルール、証拠が含まれます。判定は決定論的です。LLM は使用せず、コアスキャンでネットワークにも接続しません。
開発の背景
2026 年 10 月の報告では、AI エージェントが攻撃を組織化した事例や、数百のパッケージにバックドアを仕込んだ自己増殖型 npm ワームが取り上げられました。その永続化の仕組みは、奇抜なマルウェアではありませんでした。すべての開発者とコーディングエージェントがすでに信頼している、ごく普通の設定ファイルでした。たとえば、package.json のインストールスクリプト、.claude/settings.json のフック、.vscode/tasks.json の folderOpen タスクです。防御側には、リポジトリを開く、インストールする、または信頼する前に、エージェント自身が実行できる高速なローカルチェックが必要でした。それが hookcheck です。
チェック対象
| 対象 | チェック内容 |
|---|---|
package.json | preinstall、install、postinstall、prepare スクリプト。ルートの package.json と、ネストされたワークスペースの package.json ファイル(例:packages/*/package.json、apps/*/package.json、ルートから最大 8 階層下までの任意のディレクトリ。node_modules は除外)を同じ方法で一覧化します |
.claude/settings.json と settings.local.json | SessionStart、PreToolUse、その他のフックコマンド。GNU stow のレイアウト(ドットなしの claude/settings.json)や、設定ディレクトリ直下にネストされた設定ファイル(例:config/.claude/settings.json)も検出します。設定パスがディレクトリとして存在する場合は、黙ってスキップせず警告として報告します |
| Claude Code プラグインのフック | プラグインルート内の hooks/hooks.json:リポジトリのルート、またはプラグインディレクトリ内(例:plugins/<name>/hooks/hooks.json)。プラグインルートはスキャン対象のルートから最大 8 階層下までです。このファイルには、.claude/settings.json の hooks キーと同じイベントマップ(SessionStart、PreToolUse、PostToolUse、Stop、UserPromptSubmit、その他の Claude Code イベント一式)が含まれ、コマンドもまったく同じ方法で分類されます。同階層にある hooks/hooks-cursor.json は Cursor のイベント名で同じコマンドオブジェクト形式を使用しており、同じ方法で一覧化されます |
.vscode/tasks.json | runOn が folderOpen のタスクと、タスク一覧 |
.husky/ | すべてのフックファイルとその完全なコマンド内容 |
lefthook.yml | 実行コマンド |
.pre-commit-config.yaml | エントリーコマンドとリモートリポジトリ URL |
.cursor/ と .codex/ | 設定コマンドとファイル |
| MCP 設定 | 自動起動する .mcp.json、mcp.json、.vscode/mcp.json、.cursor/mcp.json、.windsurf/mcp.json、.codex/mcp.json の各サーバーを、それぞれ 1 回だけ一覧化します |
.github/workflows/ | 一覧としての実行ステップ。ブロックスカラー本文(`run: |
.githooks/ | フックファイル |
上記の各サーフェスは、スキャンのルートだけでなく、範囲を限定した再帰的な探索によって検出されます。たとえば、サブディレクトリ内にあるサーフェス(packages/foo/.claude/settings.json、sub/.vscode/tasks.json、sub/.github/workflows/ci.yml など)は、Limits に記載された探索範囲内で、実際の相対パスを検出結果に含めて、親ディレクトリのスキャンからインベントリに登録されます。サーフェスファイルは、検出経路がいくつあっても、インベントリに一度だけ登録されます。 |
フックごとの分類:
-
SAFE:test、lint、build、typecheck、husky、lint-staged などの既知の安全な開発パターンに一致し、リスクのあるルールには一致しません。
-
RISKY:リスクのあるルールの少なくとも1つに一致します。対象となるのは、ネットワーク経由の取得、インタープリターにパイプで渡す curl、インタープリターにパイプで渡す base64 デコード、認証情報ストアのパスまたは認証情報ファイル(内容とパスに基づいて照合し、ファイル名に credentials という語が含まれるだけのスクリプトは対象にしません)、認証情報の形式を持つ環境変数名(token、secret、password、api key)または環境変数への一括アクセス、base64 デコードまたは eval 型の難読化、インラインコード実行、リモート URL です。単一の名前付き環境変数の通常の読み取り(NODE_ENV、HOME、RUNNER_DEBUG)は検出対象ではありません。読み取りではない操作も対象外です。
env -u NAMEは変数の設定を解除し、os.environ.setdefault(...)は1つのキーにデフォルト値を書き込み、[ -n "$NAME" ]は存在を確認します。フックの対象がシェルスクリプトの場合、シェルスクリプトであるという事実ではなく、そのスクリプトの内容に基づいて分類します。分類の前にコメントは除去されるため、コメント内のドキュメント URL は取得とはみなされません。また、URL がすべてループバックアドレス(localhost のヘルスチェック)への取得は、ネットワーク動作として扱われません。スクリプトファイル(JavaScript、Python)では、文字列リテラルと正規表現リテラルの内容は動作ではなくデータです。パターンリストにeval(やcurl | shを文字列として含む防御用ガードは、どちらも実行せず、hookcheck は攻撃者として分類しません。execSync("...")やos.system("...")などの exec 型呼び出しに渡されるリテラルは、構造上コードであるため、引き続き照合対象となります。2つのルールは単なる同時出現ではなく、値の流れを対象とします。ENV-EXFIL は、認証情報の値が外部への送信(リクエストペイロード、クエリ文字列、ネットワークツールへのパイプ、または送信と併せた出力)に明確に流れた場合にのみ発動します。CRED-EXFIL は、認証情報ストアの内容が外部への送信に直接パイプまたは POST された場合に発動します。環境変数の継承は送信ではありません。環境変数の移動が、サブプロセス環境に渡される継承または一括アクセス(env=os.environ、{**os.environ, ...} の展開、dict(os.environ)、os.environ.copy() と、それらに相当する JavaScript の形式)だけの場合、ENV-EXFIL の発動には、フロー内にループバック以外の宛先がさらに必要です。そのため、127.0.0.1 上で独自のサーバーを起動し、継承した環境変数を渡すテストハーネスは、情報流出とは判定されません。同じ形式でも、ペイロードにシリアライズするか、リモートの宛先と組み合わせると発動します。ENV-BULK の証拠には、検出した形式が示されます。継承およびコピーの形式は、シリアライズまたはダンプではなく、継承またはコピーによる一括アクセスとして記述されます。認証に使われるトークン(Authorization ヘッダー、リポジトリと同じホストへのプッシュ URL のユーザー情報、--tokenフラグ)は認証であり、情報流出ではないため、どちらのルールも発動しません。認証として扱う位置は、Authorization という表記だけではなく、その形式によって判定します。curl の-Hヘッダー引数は名前を問わずすべて、1ホップのスクリプトファイル内にある認証ヘッダー名をキーとする辞書またはオブジェクトのエントリー(Authorization、x-api-key、*-api-key、*-auth-tokenなど)、およびヘッダー構築内のヘッダー形式のエントリー(headers という変数に代入された辞書、headers=引数、リクエストオプションオブジェクトの headers プロパティ)は、すべて認証位置として扱われます。さらに、同じ形式に基づいて以下の位置も認証として扱います。curl の-uまたは--userの Basic 認証ペア、git のhttp.extraheaderキーに設定された値(どのように組み立てられた場合でも Authorization ヘッダーの値)、::add-mask::への登録(そこへの値の出力は、それ自体がマスキングの慣用表現です)、認証情報の受け渡し位置(git のcredential.helperキーに設定された値、およびその値が指定するシェル関数の本体を含みます。git はヘルパーがエコーするユーザー名とパスワードの行を認証プロトコルとして読み取るため、そのエコーは開示ではなく提示です。同じ分類にはcore.askPassの値、GIT_ASKPASSまたはSSH_ASKPASSの代入、git credential呼び出しへのパイプ入力、およびそれに入力を渡すヒアドキュメントも含まれます)、そして送信先 URL 内の宛先位置です。これには、URL パスの1セグメント全体、URL 全体、URL のオリジンに相当する認証情報(ビルドフック URL、パス内のプレビュートークン、トークンのクエリパラメーターなど、ケイパビリティ URL は設計上認証情報です)、認証情報を示す名前のクエリパラメーター(token、api_key、access_token、auth、secret、signature、key)の値、curl の URL オペランド位置にある単独の参照、および取得型呼び出しの第1引数の宛先が含まれます。同じ分類には、もう1つ認証位置があります(v0.1.18 以降)。~/.npmrcは npm クライアント専用の認証ファイルです。これをリダイレクト先として認証情報を書き込む場合(標準的な npm publish 手順である echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc など)、認証情報は専用クライアントに提示されるため、それだけでは認証情報ストアへのアクセスとして扱われません。~/.npmrcを読み取る、コピーする、その内容を投稿する、またはトークンを他の場所に送る場合は、引き続き通常どおり検出されます。判定は本体の内容に基づきます。これらの位置で提示された認証情報でも、本体に認証情報や一括データが含まれなければ認証として扱われます。一方、同じ認証情報が本体、データを含む名前付きクエリパラメーター、部分的な URL パスセグメント、または出力に含まれる場合は、引き続き情報流出として扱われます。ケイパビリティ URL の形式に認証情報を含む本体が組み合わさっている場合も発動します。汚染は、ローカルで定義されたヘルパー関数の戻り値を介しては伝播しません。ただし、ヘルパー自体が汚染データを返す場合は例外です(その本体は同じテキストの一部として解析されます)。値の長さまたは個数の出力(len(x)、x.length、${#NAME})は値の開示ではなくメタデータです。また、オブジェクトキー、プロパティ名、代入先、またはパラメーター宣言としての名前の出現は、その名前の変数への参照ではなく束縛です。同じテキスト内で、認証情報の要素を含まない値から代入されたシェル変数(--outputの出力先として使われる curl のリテラルなファイルパスに束縛された、token という名前のレスポンス変数など)、または認証情報の要素が宛先位置にのみ含まれるコマンド置換から代入された変数(HTTP ステータスコード変数など)は、それ自体は認証情報の値ではありません。取得の検出にはコマンド位置にあることが必要です。curl または wget がネットワーク経由の取得とみなされるのは、実行されるコマンドである場合(文またはパイプラインセグメントの先頭、sudo や env のラッパーの後、またはインタープリターに渡されたコマンド文字列の先頭)に限られ、インストール行でパッケージ名として使われているだけの場合(pacman -S curl、apt-get install curl)は該当しません。スクリプトファイルでは、リモート URL のルールも他の動作ルールと同じデータの見方に従います。文字列リテラルまたはコメント内だけにある URL(エラーメッセージ、ドキュメントリンク、API ベース定数など)はデータですが、ネットワーク呼び出し(fetch、requests、http.get、urlopen、axios、sendBeacon)に渡された URL は取得先であり、引き続き発動します。インラインコード実行(DOWNLOAD-EXEC)は、評価対象の内容自体にダウンロードまたはリモート参照が含まれる場合にのみ発動します。node -eによるバージョン比較のような定数のインラインコードは何もダウンロードしないため、検出されません。v0.1.17 以降、ネットワークから取得した内容が、直接または変数を介して eval 型呼び出し(eval、Function、exec、vm runIn context 呼び出し)に渡されるというファイルレベルのローダー形式にも、同じルールが適用されます(取得結果を1つ目の変数に代入し、その本文テキストを2つ目に代入して、2つ目を eval に渡す場合など)。この場合、検出結果には DOWNLOAD-EXEC と REMOTE-EVAL が併記され、判定は COMPROMISED-PATTERN になります。取得結果をログ出力するかデータとして解析するだけの場合、および定数や取得元をたどれない変数を eval する場合は、発動しません。 -
UNKNOWN:安全パターンにもリスクパターンにも一致しません。ローカルスクリプト名だけの場合は、人が確認するまで UNKNOWN です。フックが
node setup.mjsのようにローカルファイルを実行すると、hookcheck はそのファイルを読み取り、ファイル自体がリスクのあるルールに一致する場合は検出結果をエスカレーションします。同じ1ホップの追跡は、直接のスクリプト実行(./setup.sh)、node --require ./setup.mjs、および package.json のスクリプトチェーン(postinstall: npm run other-script)にも適用されます。参照先のファイル自体がさらに別のローカルスクリプトを起動する場合、チェーンは1ホップの上限を超えるため、検出結果は UNKNOWN を下回らず、決して SAFE にはなりません。証拠には2ホップ目のファイルが記載され、最終判定は人が確認する REVIEW になります。 Verdict rules: -
CLEAN:すべての検出結果がSAFE、またはフックが見つかりませんでした。終了コードは0です。何も見つからなかった場合、監査対象が見つからなかったことを示す注記が、JSONと人間向けの出力の両方に含まれるため、対象が空でもすべて問題なしとは表示されません。
-
REVIEW:UNKNOWNの検出結果が1つ以上あるか、RISKYの検出結果があり、その証拠だけでは弱い場合です(リモートURLのみ、ダウンロードのみ、ネットワーク動作を伴わない認証情報へのアクセスなど)。終了コードは1です。
-
COMPROMISED-PATTERN:強い証拠を伴うRISKYの検出結果が1つ以上ある場合です。強い証拠には、取得またはデコードしたコンテンツをコードとして直接実行すること(curlの出力をインタープリターにパイプする、base64でデコードした出力をインタープリターにパイプする、取得したコンテンツをevalする)、検証済みの情報流出フロー(認証情報の値が外部送信に流れる、または認証情報ストアの内容をネットワークツールにパイプする)、あるいは同じ検出結果内での認証情報ストアへのアクセスとネットワーク動作の組み合わせがあります。これはShai-Huludの永続化パターンです。証明されるまでは、リポジトリを信頼できないものとして扱ってください。終了コードは1です。この判定基準では、独自のバイナリをダウンロードする正規のインストーラー(esbuild、puppeteer)はCOMPROMISED-PATTERNではなくREVIEWになります。ダウンロードは証拠を伴うRISKYの検出結果として報告されますが、最上位の判定で過剰な断定はしません。リポジトリ独自のリリース自動化にも同じことが当てはまります。トークンを読み取って自身のリポジトリにプッシュする、自身のワークフローのディスパッチを実行する、または自身のhomebrew tapを更新するステップは、トークンを認証に使用しており、認証情報の値がサードパーティーへのペイロードに流れることはないため、判定はREVIEWになります。同じ系統の判定基準がもう1つあります。CIワークフローのステップで深刻な証拠がcurlの出力をHTTPS経由で指定ベンダーの公式インストーラーにパイプすることだけであり(/installや/uv/install.shのようなインストーラー形式のパス、またはastral.shやsh.rustup.rsのような既知のインストーラーホスト)、同じ検出結果に認証情報、永続化、その他の深刻なシグナルがない場合、検出結果はRISKYのままREVIEWと判定されます。同じパイプでも、自動実行フックの対象(インストールスクリプト、Claudeの設定またはプラグインフック、folderOpenタスク)にある場合、インストーラーではない配布元へのパイプである場合、または他のシグナルを伴う場合は、COMPROMISED-PATTERNに達します。インストーラー形式の判定は、特定のリストに依存せず、決定論的に一般化されます。token境界にあるinstall、installer、setupというパスセグメントまたはファイル名トークンが該当します(そのためcargo-hawk-installer.shやsetup_lts.xは該当しますが、uninstall.shは該当しません)。また、先頭のホストラベルがインストーラーサブドメインである場合(get、install、installer、setup、sh。たとえばget.maestro.mobile.devやinstall.python-poetry.org)、文書化されたインストーラーホスト(astral.sh、sh.rustup.rs、pyenv.run)、またはテスト対象の不変なコミットにあるリポジトリ独自のスクリプトをパイプする場合(github.comまたはraw.githubusercontent.comで、${{ github.repository }}がリポジトリを、${{ github.sha }}がrefを示す)も該当します。不変なrefにある同一リポジトリのコンテンツは、すでに実行中のコードを超える信頼境界を追加しないため、深刻なパイプ規則におけるリモートダウンロードとはみなしません。インストール自体の処理も、付随シグナルを判定する際のパイプ形式の一部として扱われます。つまり、パイプ自体のNET-FETCHとREMOTE-URL、およびWRITE-OUTSIDE(インストーラーは定義上$HOMEに書き込みます)は該当しますが、同じ検出結果にある独立した深刻なシグナル(認証情報へのアクセス、情報流出フロー、永続化)があれば、この判定基準は適用されません。この判定基準は判定レイヤーで検出結果ごとにも適用されます。判定基準に該当するパイプは最上位の判定を左右せず、リポジトリ内の別の場所にある無関係な深刻な検出結果によって、再び判定対象になることもありません。その検出結果自体の根拠に基づいて判定します。最後に、コンテンツに関する2つの規則があります。検索パターン内のコンテンツは動作ではなくパターンテキストであり、強い証拠の組み合わせを構成するどちらの要素についても同様です。コマンドが検索ツール(grep、egrep、fgrep、rg、ag、ack)を実行する場合、パターン引数とファイルオペランドはデータの検索に使われるため、そこに記された認証情報パス文字列は参照であり、認証情報へのアクセスではありません。また、パターン内のURLが実際にアクセスされることもありません。フィードをブロックするために.npmrcとpackage-lock.jsonをパッケージフィードのパターンで検索するlintは、防御的な制御であり、認証情報へのアクセスとネットワークの検出結果ではありません。同じ参照の原則は出力テキストにも適用されます。認証情報ファイルを示すメッセージ(lint独自のエラー行である「Could not scan .npmrc and package-lock.json」など)は、アクセスではなく参照です。そのため、認証情報の規則ではechoとprintfの引数テキストもデータとして扱います。ただし、メッセージ内のコマンド置換($(cat ~/.npmrc)またはバッククォート形式)は、メッセージの出力時に実行されるコードであり、引き続き完全に検出対象となります。認証情報ファイルを実際に読み取って内容を送信する場合や、URLを検索してから取得する場合は、引き続き最大の強度で検出されます。また、インタープリターへのパイプに対して深刻なパイプ規則が適用されるのは、インタープリターが標準入力を実行する場合に限られます。引数のないインタープリター、単独のダッシュ、またはモードフラグのみの場合が該当します。プログラムが別の方法で与えられている場合(python3 -m json.toolのようなインタープリターモジュール、インラインの-eまたは-cプログラム、あるいはスクリプトオペランド)、パイプされたストリームは実行されるコードではなく解析されるデータです。そのため、ローカルのJSONレスポンスをフォーマッターにパイプするヘルスチェックは、ダウンロードして実行する形式ではありません。ただし、標準入力をそれ自体で実行するプログラム(標準入力に対してexecまたはevalを呼び出すインラインプログラム)は、引き続き検出されます。 各検出結果には、ファイル、フック ID、コマンドテキスト、分類、ルール ID、証拠が報告されます。不正な JSON は警告として報告され、黙って無視されることはありません。存在しないパス、またはディレクトリではなくファイルであるパスをスキャンすると、終了コード 2 で明確なエラーになります。レポートはフラッシュ対応の emit を通じて出力されます。パイプで出力されたレポートの読み取り側がパイプを早期に閉じた場合、レポートが配信されなかったにもかかわらず判定コードを報告するのではなく、プロセスは 141 で終了します(シェルから見た SIGPIPE による終了と同等です)。
制限
test/hardening.test.ts のテストで固定された、明確な境界は次のとおりです。
- 1 ホップのみです。hookcheck はフックをたどって、そのフックが直接呼び出すローカルファイルまたは package.json のスクリプトを分類します。2 ホップ目はたどりません。1 つのファイルまたはコマンド内では、前述のデータ流出チェックと、(v0.1.17 以降)ダウンロードして実行するローダーを特定する fetch から eval へのチェックという、2 つの限定的な値フローチェックを実行します。スクリプト内の一般的なデータフローは追跡しません。単一ファイル内に認識可能なパターンとして現れない断片から実行時に組み立てられるペイロードは特定されませんが、それを実行するフックは通常、UNKNOWN または REVIEW になります。これは手作業で確認すべきという合図です。最初のホップのファイルがさらに別のローカルスクリプトを起動する場合、その着地点の判定は通常より厳格になり、検出結果は UNKNOWN を下回らず、根拠には 2 ホップ目が記載されます。
- 実行ではなく、パターンに基づきます。hookcheck は検出したフックを実行しません。RISKY という判定は、コマンドのテキストが既知の危険な形式に一致するという意味であり、ペイロードが実際に害を及ぼす様子を確認したという意味ではありません。CLEAN という判定は、既知の形式に一致しなかったという意味であり、リポジトリの安全性が証明されたという意味ではありません。
- データ流出の検出は、1 つの検出結果内での静的な値フローチェックです。認証情報の値が、同じコマンドまたは参照先ファイル内の送信構文に明示的に到達している必要があります(変数への代入が 1 回連鎖する場合も含みます)。複数のファイルを経由して変換された値、実行時に組み立てられる値、または引数にその値が記されていないツールによって送信される値は、データ流出として特定されません。ただし、他の根拠に基づいて検出結果は引き続き RISKY または UNKNOWN になります。反対に、リクエストボディ、data という名前のクエリパラメーター、または URL パスの一部に認証情報の値を置くと、送信先が自社のホストでもデータ流出の形式として扱われます。認証情報はペイロードではなく、認証に用いる位置に置くべきためです。認証情報の値が URL パス全体のセグメント、URL 全体、または authentication という名前のクエリパラメーターの値として提示され、ボディに認証情報や大量のデータが含まれない場合は、ケイパビリティ URL の提示として扱われ、認証に分類されます。
- 引用符の判定には例外があります。スクリプトファイルでは、文字列リテラルおよび正規表現リテラルの内容は動作ルール上、データとして扱われます。文字列に隠されたペイロードが組み立てられ、認識済みの exec 呼び出しを経由せずに間接的に実行される場合、検出できないことがあります。シェルテキストでは、引用符で囲まれた文字列もコマンドテキストとして見える状態が保たれます。ただし、echo と printf の引数は出力データとして扱われます。
- リモートの pre-commit リポジトリとリモートの MCP サーバーは一覧に記録されますが、検証はされません。hookcheck はリモートの pre-commit リポジトリの中身を確認できないため、該当エントリは UNKNOWN となり、固定された rev の確認を促す注記が付きます。リモートの MCP サーバー URL は、サーバーが自動起動し、リモートであるため、構造上 RISKY となります。
- 重複する JSON キーは、JSON.parse、Node、npm とまったく同じく、最後に出現した値が優先されます。そのため、スキャナーは実際に実行される値を認識します。
- 文字列以外の package.json スクリプト値(数値、オブジェクト、null、真偽値、または postinstall などの監視対象ライフサイクルスロットにある配列)は、コマンドとして分類できません。v0.1.18 以降、これらは黙ってスキップされなくなりました。それぞれの名前を示す警告が出され、UNKNOWN の検出結果として一覧に記録され、判定は REVIEW を下回りません。エントリは存在するものの、その内容を検証できないためです。
- 分類では、フックファイルまたは参照先のローカルファイルを最大 200,000 文字まで読み取ります。この上限を超える部分にはペイロードが隠れている可能性があります。悪意のあるファイルによってスキャンが無制限に続かないよう、この上限が設けられています。
- GitHub ワークフローの run ステップは、ブロックスカラー(
run: |、run: >、および chomping 付きの形式)を解析し、1 行のコマンドとして分類します。ブロックヘッダーのない通常の複数行スカラーは結合されず、再利用可能なワークフローの内部も解決されません。run ステップの YAML アンカー、エイリアス、マージキーは、同じファイル内でアンカーが定義されている場合に解決されます。その他のステップフィールドに適用されたアンカーや、別のファイルで定義されたアンカーは解決されません。CRLF 改行のワークフローファイルも、LF のファイルとまったく同じように解析されます。 - ネストされた検出は、一覧に記録されるすべての対象を網羅する、上限付きの 1 回の走査です。対象には、package.json ファイル、
.claude/settings.jsonとsettings.local.json(stow レイアウトのclaude/settings.jsonを含む)、プラグインのhooks/hooks.jsonとhooks/hooks-cursor.jsonファイル、.vscode/tasks.json、.github/workflows/*.ymlと*.yaml、.husky/と.githooks/のフックファイル、lefthook の設定(lefthook.yml、lefthook.yaml、.lefthook.yml)、.pre-commit-config.yaml、.cursor/と.codex/の設定ファイル、および MCP の設定(.mcp.json、mcp.json、.cursor/mcp.json、.windsurf/mcp.json、.codex/mcp.json、.vscode/mcp.json)が含まれます。プロジェクトディレクトリがスキャンルートから最大 8 階層下にある対象を、ソート順に走査します(プラグインの hooks ディレクトリ自体は、プラグインルートから 1 階層下です)。各対象種別で走査するネストされたファイルは最大 200 個です。スキャンルート直下のファイルは常にこの上限に含まれます。node_modules、.git、ドットで始まるディレクトリ、シンボリックリンクされたディレクトリには入りません。そのため、深さの上限を超える場所や node_modules 配下にある対象はスキャンされません。通常のファイルでないもの(FIFO、ソケット、デバイスファイル)は開かれません。スキャナーが読み取るすべてのファイルは、まず stat で種類を確認し、通常のファイルでないものは名前を示す警告とともにスキップされます。FIFO を開くと書き込み側が現れるまで処理がブロックされ、スキャンが停止するためです。上限に達した場合も黙って処理されることはありません。走査中に深さの上限に達し、その先にもツリーが続いている場合、レポートには深さの上限を示す警告と、その直下にフック対象ファイルまたは設定ディレクトリがあるディレクトリ数が記載されます。対象種別ごとのファイル数が上限に達した場合は、警告に上限値と検出数が記載されます。深さの上限、ファイル数の上限、または読み取り不能なファイルによって途中で打ち切られたスキャンは部分的なスキャンとなり、警告にもその旨が記載されます。対象の見落としが判明している場合、判定が単なる CLEAN になることはありません。プラグインスキーマのうち一覧に記録されるのは、コマンドオブジェクト形式のみです。コマンドオブジェクトではなく単なる文字列で記述されたフックエントリは、文書化されたスキーマの範囲外であり、一覧に記録されません。 - スキャナーが一覧への記録を試みたファイルまたはディレクトリを読み取れない場合(たとえば、mode 000 のワークフローファイル)は、黙ってスキップされません。レポートには名前を示す警告が記載され、対象全体のいずれかの部分を読み取れない間、判定は REVIEW を下回りません。そのため、部分スキャンが全件確認済みの状態として示されることはありません。同じことは、シンボリックリンクをたどらない方針によりスキップされる対象にも当てはまります。シンボリックリンクされたディレクトリには入りません。v0.1.15 以降、リンクの名前と理由を示す警告がディレクトリごとに 1 件出され、対象をスキップしたスキャンの判定は REVIEW を下回りません。v0.1.16 以降、
.claude、.husky、.pre-commit-config.yamlなど、一覧に記録する対象パス自体に置かれたシンボリックリンクも対象です。リンク先を解決できない場合は、対象が消失したかのように扱うのではなく、名前を示す警告が出されます。解決できるリンクは、従来どおり対象を走査するスキャナーによってたどられます。v0.1.18 以降、判定の下限は、読み取り不能な対象だけでなく、一覧に記録できなかった名前付きのすべての対象に適用されます。不正な settings ファイルまたはマニフェストファイル、ディレクトリになっている settings パス、対象パスにある通常ファイルでないもの(FIFO、ソケット、デバイス)は、それぞれ名前を示す警告が出され、判定は REVIEW を下回りません。スキャンルートをまったく一覧表示できない場合(たとえば、root 以外のユーザーが chmod 000 のディレクトリを読み取る場合)も警告が出され、対象全体が空で問題ないかのように示されるのではなく、REVIEW と判定されます。 - 同一の SAFE 検出結果が繰り返される場合は集約されます(v0.1.15)。同じ SAFE 検出結果(ファイル、ソース、フック、コマンド、ルール、根拠が同一)が 3 回以上繰り返されると、出現ごとに 1 件ずつ記録する代わりに、件数と最初および最後の行を示す集約レコードが 1 件レポートに記載されます。また、JSON と人間向けの出力の両方に、集約によって何件の検出結果がまとめられたかを示す注記が記載されます。RISKY と UNKNOWN の検出結果は集約されません。判定は集約前の全件を対象に計算されるため、判定と終了コードは変わりません。約 92,000 件の同一で無害なステップを含む 5 MB のワークフローは、91,980 件、35 MB の代わりに、約 1 KB の JSON と集約レコード 1 件で報告されます。
- core.hooksPath で設定されたカスタム Git フックディレクトリはスキャンされません。hookcheck が一覧に記録するのは標準の
.githooks/ディレクトリと.husky/であり、カスタムの core.hooksPath ディレクトリを見つけるために Git の設定やコミット済みの.gitconfigを解析することはありません。カスタムの hooksPath ディレクトリにのみ保存されたフックは一覧の対象外です。監査するには、それらが標準の場所にあるツリーをスキャンしてください。 - エージェント設定の対象範囲に関する制限:
.gemini/settings.jsonはまだスキャンされません。テンプレート設定(たとえばtemplates/settings.json.template)もスキャンされません。これらは配置場所にコピーされるまでは動作せず、コピーされた時点で実際の名前に基づいてスキャンされます。 - 大規模な実際のリポジトリでは、UNKNOWN が最も多い分類です(Round 1 の検証では、langfuse と cli/cli の検出結果の約 80 パーセントを占めると測定されました)。分類は一覧の作成を目的とするもので、疑わしさを示すものではありません。大規模な対象全体に対して REVIEW と判定された場合、自動実行の対象が把握され、人間による確認が必要という意味です。各検出結果の根拠には、各フックが実行する内容が明記されます。今後、より多くのワークフローステップパターンが分類される予定です。それまでは hookcheck が推測で判断することはありません。
使用方法
CLI
npx @reneturcios/hookcheck scan
npx @reneturcios/hookcheck scan ./some-repo
npx @reneturcios/hookcheck scan ./some-repo --json
npx @reneturcios/hookcheck explain ./some-repo package.json:postinstall
出力例:
hookcheck ./some-repo: COMPROMISED-PATTERN
2 hook(s): 0 SAFE, 2 RISKY, 0 UNKNOWN
[RISKY] package.json:postinstall (package.json)
command: node setup.mjs
rules: NET-FETCH, REMOTE-URL, ENV-BULK, ENV-EXFIL
evidence: referenced local file setup.mjs is RISKY: NET-FETCH: performs a network fetch
COMPROMISED-PATTERN: at least one risky hook shows strong evidence: fetched or decoded content executed as code, credential values flowing into an outbound send, or credential store access combined with network behavior.
MCP サーバー
MCP クライアントの設定に追加します:
{
"mcpServers": {
"hookcheck": {
"command": "npx",
"args": ["-y", "@reneturcios/hookcheck", "mcp"]
}
}
}
ツール:
scan_hooks(path):判定結果、検出内容、件数、警告を含む完全なスキャンを実行します。explain_hook(path, hookId or file):1 つのフックを、その根拠とともに平易な言葉で説明します。
Docker
docker run -i ghcr.io/cloudcorprecords/hookcheck mcp
docker run --rm -v "$PWD:/repo" ghcr.io/cloudcorprecords/hookcheck scan /repo
ツールキット
hookcheck は、同じ作者による小規模な単一目的の開発者ツール群の一部です。すべて同じ方法で構築されており、1 つの実行ファイルで CLI と MCP サーバーを提供し、根拠を示す決定論的なチェックを行い、答えを捏造しません。
- greenlight:オープンソースへの貢献に向けた事前チェックです。この issue は本当に取り込める状態でしょうか?
- shipcheck:リリースがすべての提供先に実際に反映されたことを検証します。そうでなければ NO-GO です。
開発
npm install
npm run build
npm test
ライセンス
MIT
R e n e T u r c i o s が開発。SF で、バイブコーディング/エージェントネイティブな開発を公開しながら進めています。次のツールについては @CloudCorpRecords をフォローしてください。
ツール群:greenlight · injectcheck · actionpin · shipcheck。すべて npm の @reneturcios/ 配下と、公式 MCP Registry で公開されています。
hookcheck が時間の節約に役立ったら、このリポジトリにスターを付けてください。他の開発者が見つけやすくなります。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。