はじめに
前回、AI エージェントに自分の PC を読み取り専用で点検させる MCP サーバーを紹介しました。
その後、点検できる対象を PC から Android 端末と家庭用ルーターに広げ、名前も Personal Device Scanner MCP に変えました。
スキャン履歴とダッシュボードも加え、npm から npx で起動できるようにしています。
npx -y personal-device-scanner-mcp@0.5.2 setup
この記事では、追加した機能と、作る中でつまずいた点を紹介します。
前回と同じく、「安全である」と判定するツールではありません。所見が0件でも、検査範囲の不足や、公表されていない脆弱性の
可能性があります。最終判断は機器の所有者が行う前提です。
追加した機能
| MCPツール / CLI | 対象 | 内容 |
|---|---|---|
collect_android / check_android_monitoring_apps
|
Android 端末 | USB でつないだ自分の端末を adb で読み取り、既知の監視アプリ(ストーカーウェア)と照合 |
check_router |
家庭用ルーター | ルーターに一切アクセスせず、所有者が書いた機種・バージョン・設定を公表情報と照合 |
history(CLI のみ) |
すべて | スキャンごとの要約を PC に記録し、表で表示 |
dashboard(CLI のみ) |
すべて | 履歴を1つの HTML ページにして開く |
旧パッケージ名 personal-pc-scanner-mcp は npm から削除し、新しい personal-device-scanner-mcp に移りました。
旧リポジトリの URL は新しい URL に転送されます。
Android 端末:USB でつないだ自分の端末だけ
PC に USB でつないだ自分の Android 端末を、adb の固定コマンドで読み取ります。端末への書き込みやアプリの削除はしません。
-
device:Android のバージョンとセキュリティパッチの日付 -
apps:利用者が入れたアプリと、端末が記録している導入元 -
privileged_access:ユーザー補助・通知へのアクセス・端末管理を持つアプリ(ストーカーウェアがよく悪用する権限) - 全パッケージを、Echap の stalkerware-indicators(CC-BY 4.0)と照合
Wi-Fi 経由の adb は点検しない
adb は Wi-Fi 経由(ワイヤレスデバッグ)でもつながります。
意図しない端末を点検しないよう、USB でつながっていると確かめられた端末だけを対象にしました。
シリアル番号の形(192.168...:5555 など)から推測するのではなく、adb track-devices --proto-text が返す
connection_type: USB を読んで判定します。エミュレーターで試すと SOCKET になり、正しく拒否されました。
USB デバッグを有効にすると、端末の攻撃面が増えます。README では「点検のときだけ有効にし、終わったら無効にして、
USB デバッグの承認を取り消す」よう案内しています。
家庭用ルーター:ルーターに触らずに点検する
ルーターの点検というと、ポートスキャンや脆弱性スキャンを思い浮かべるかもしれません。しかしそれはルーターに想定外の影響を
与えうるうえ、調査と攻撃の境目があいまいになります。そこで、ルーターには一切アクセスしない設計にしました。
所有者が、管理画面で確かめた機種・ファームウェアのバージョンと設定を設定ファイルに書くと、次と照合します。
-
JVN iPedia(IPA の MyJVN API):国内メーカーの脆弱性情報。所有者が
router.jvn_lookupを有効にしたときだけ問い合わせ、
送るのは機種の CPE 名だけです - CISA KEV:悪用が確認された脆弱性の一覧(CC0)。所有者がダウンロードしたファイルを読みます
-
設定:初期パスワードの変更・自動更新・遠隔管理・VPN・UPnP・WPS・Wi-Fi の暗号化などを、警察庁・総務省・NOTICE の推奨と照らし合わせます。
回答のない項目は「問題なし」ではなく「未確認」にします
ここからは、作る中で見つけた落とし穴です。どれもレビューや受入試験で見つかり、実データで再現してから直しました。
1. MyJVN は、日付を指定しないと「過去1週間」だけを検索する
MyJVN API の脆弱性一覧(getVulnOverviewList)は、発見日・更新日・発行日のパラメーターを省くと、それぞれ既定の
「過去1週間」で絞ります(公式 FAQ に記載)。
ある国内メーカーの機種で試すと、日付を省いた場合は 0件、3つとも「範囲指定なし(n)」にした場合は 2件
(2017年と2026年の公表)でした。古いまま修正されていない脆弱性ほど漏れるので、毎回3つとも n を付けています。
rangeDatePublic=n&rangeDatePublished=n&rangeDateFirstPublished=n
1回の応答は最大50件なので、totalRes(総数)に達するまでページを進めます。上限や期限で打ち切ったら、
取得できなかった件数と理由を返して partial にし、取得できなかった分を「該当しない」とは数えません。
2. 1つの脆弱性情報に、別の機種の範囲が並ぶ
1つの脆弱性情報に複数の機種が載り、機種ごとに影響を受けるバージョンが違うことがあります(例:機種Aは「Ver.1.71より前」、機種Bは
「Ver.2.55より前」)。別の機種の範囲で比べると、誤って「該当しない」になります。
そこで、所有者が登録した機種の CPE と一致する項目だけで比べます。ただし JVN では、同じ機種でも CPE の種別が h:(ハードウェア)と
o:(OS)で混在していたため、種別は区別せず、メーカーと製品名の部分の完全一致で照合しています。
3. 「2.6」と「2.55」はどちらが新しいか
影響を受けるバージョンは「ファームウェア Ver.2.55より前のバージョン」のような自由記述です。決まった形だけを読み取って比べますが、
**「2.6」と「2.55」**の比較で迷いました。
- バージョン番号として比べると、6 < 55 なので 2.6 のほうが古い
- 小数として比べると、0.6 > 0.55 なので 2.6 のほうが新しい
どちらが正しいかはメーカーの番号の付け方次第です。そこで、2つの比べ方の結果が食い違うときは判定せず、記述を添えて
unknown にしました(「1.07」と「1.7」も同じ)。誤って「該当しない」と言うより、確認を促すほうを選んでいます。
4. 「DIR-85」が「DIR-859」に一致してしまう
KEV にはバージョンがなく、製品名も自由記述(例:「DIR-859 Router」「Multiple Routers」)です。最初は機種名を部分文字列で
照合していたため、受入試験で「DIR-85」を入れると「DIR-859」の項目が高い深刻度で出てしまいました。メーカー名だけの一致で、
NAS の項目(「Multiple NAS Devices」)が混じる問題も見つかりました。
今は、製品名を語に分け、連続する1〜4語と機種名が完全に一致したときだけ一致とし、メーカーだけの一致はルーター全般の項目に限っています。
スキャン履歴とダッシュボード
AI が何を実行したかを後から確認できるよう、スキャンごとの要約を PC に記録するようにしました。
TIME VIA OPERATION STATUS FINDINGS SECONDS TARGET
2026-10-15 09:12:04 mcp malware completed 0 41.8 downloads
2026-10-15 09:13:30 cli android_check partial 1 (high 1) 6.2 phone
- 記録するのは要約だけで、ファイル名・アプリ名・CVE などの所見そのものは残しません
- 履歴を読む MCP ツールは作らず、AI からは読めません。所有者が
historyコマンドで見ます -
dashboardで、履歴を1つの HTML ページにして開けます。スクリプトを動かさず、外部から何も読み込みません
(Content Security Policy で自身のスタイルだけを許可)
所見の詳細もページで見たい場合は、history.details = true で結果全体を残せます。ただし既定では無効です。
たとえばスマートフォンで監視アプリが見つかった記録は、この PC を使える人(監視アプリを入れた本人を含む)にも見られるためです。
Windows だけで起きたこと
CI では Windows / macOS / Linux でテストしています。
Windows でだけ失敗したものが3つあり、どれも実際の不具合(か、テストの不備)でした。
| 症状 | 原因 |
|---|---|
| 書き込みの失敗をまねたテストで、書きかけのファイルが残った |
os.open の後の open() が失敗すると、ディスクリプターが閉じられずに残る。Windows は開いたままのファイルを消せない(macOS / Linux は消せるので気づかない)。open(..., opener=...) にして Python に閉じさせた |
| 古い詳細から消すはずが、新しいものを消した | 詳細のファイル名は保存時刻で、Windows では時計の細かさが足りず、続けて保存すると同じ時刻になった。新しい名前の時刻を、既存の最新より必ず後にした |
| ソースを読むテストが失敗した | テストが OS の既定の文字コード(cp1252)で .py を読み、日本語を含むファイルで失敗した。Python のソースは UTF-8 なので、テストを直した |
制限事項
- Android の実機(USB 接続)での確認はこれからです。今はエミュレーター(Android 10 と 16)で確かめています
- ルーターの結果は、所有者が書いた機種・バージョン・設定に基づきます。照合できるのは公表された脆弱性だけで、KEV には国内メーカーがほとんど載っていません
- 影響を受けるバージョンの記述が決まった形でない場合は
unknownになり、メーカーの告知で確かめる必要があります - クラウドの AI に結果を渡すと、アプリ名やファイル名などが送信されます。送信範囲は所有者が決めてください
おわりに
前回は「選択肢を絞る」「受け取る文字列を疑う」「検査できたかを自分で確かめる」の3つを書きました。
今回はそれに、「触らずに確かめられるなら触らない」(ルーター)と「記録するなら、誰に見られるかを考える」(履歴)が加わりました。
ソースコードは MIT ライセンスで公開しています。
