unmask

docs

JA4 取得, LB 設定, 対応 distro, よくある質問.

FAQ

JA4 ってどの構成で取れるの?
JA4 を取得できるかどうかは TLS 終端の位置で決まります。nginx + unmask-plugin-nginx (native mode) なら取得できます。gateway コンテナも自分で TLS を終端するため、任意の HTTP server の前段で取得できます。前段の LB / CDN で TLS を終端する構成では、その LB / CDN が JA4 を header で転送した場合だけ unmask に届きます。構成ごとの図解は JA4 取得方法 をご覧ください。
JA4 verdict が「(none)」のイベントがあるけど大丈夫?
問題ありません。verdict は JA4 verdict のルールが付けるラベルなので、(none) の大半は「どのルールにも一致しなかった」という意味で、普通のブラウザはすべてこれに当たります。JA4 そのものが無いリクエストもここに入ります。平文 HTTP のリクエスト (指紋を取る TLS handshake がありません) と、TLS を終端した前段の LB / CDN が指紋を header で転送しなかったリクエストです (JA4 取得方法を参照)。0.1.35 からは、verdict が無いことをどの画面でも (none) と表示します (それ以前は画面によって -、none、ok と表示がばらばらでした)。
stealth 列が赤く表示される。問題ない?
stealth 列 (統計ページのファネル表) は、自動化の痕跡 (flags) が一つも無いのに JA4 verdict が block または observe (bot / suspect) だった challenge の読み込み数です。ブラウザ側の痕跡は隠せても TLS handshake で正体が出ている、最新のヘッドレス系クライアントの兆候です。読み込みは通過ではありません。そのうち CAPTCHA まで突破して通過 cookie を得たものがあるかは、同じページの「JA4 bot/suspect 判定 × CAPTCHA 通過」カードで分かります。ここは 0 のままであるべきです。

verdict 自体の働きは次のとおりです。observe は記録用のラベルなので、通常どおりの challenge になります。block には既定で CAPTCHA で終わる chain が出され、PoW の cookie では通れません。ただし block の verdict だけで challenge が始まることはなく、判定は動作モードの action に委ねられます (既定ではこうした訪問者は全員 challenge 対象です。次の項目を参照)。
既定ではどの訪問者が challenge 対象になる?
既定では、bypass IP / bypass path / 検索 bot のいずれにも該当しない訪問者は全て challenge 対象です (no-match 経路の KnownBrowserAction / UnknownUAAction は、未設定時はいずれも pow_only に解決されます)。PoW は自動で実行され操作も不要なため、通常の利用者が感じる負荷はごくわずかです。検索 bot は素通しします。クローラーの IP range を公開しているベンダーについてはアドレスで本物を見分け (それ以外の場所から同じ UA を名乗っても通りません)、range を公開していないクローラーは UA パターンで救済されるので、SEO 事故も起きず、詐称の口も開きません。詳しくは クローラー偽装対策 をご覧ください。「普通のブラウザはそのまま通し、curl / scraper だけ challenge する」といった軽めの運用にしたい場合は、settings → 動作モード tab で KnownBrowserAction を pass に設定します。

その場合に一緒に通るものも把握しておいてください。JA4 の bot 判定だけでは challenge にならず、判定はこの動作モードの action に委ねられます。そのため pass にすると、ブラウザの UA を名乗り bot 判定の指紋を持つクライアントも、他のルール (UA フィルタ、ヘッダ整合性、国 / ASN のルール、rate limit、保護パス) に掛からない限り通過します。
Nextcloud・Jellyfin・Home Assistant・Git のホスティングの前に置ける?
置けますが、アプリやツールの通信を通す設定が要ります。challenge を解くのは JavaScript を実行するブラウザです。同期クライアント・スマホアプリ・CalDAV クライアント・git はそれを実行できないので、unmask の後ろでは応答の代わりに challenge ページを受け取って失敗します。unmask が守るのは、公開 Web から届き、スクレイパーが持ち去るもの (Web サイト、フォーラム、wiki、Git ホスティングの Web ページ) です。Home Assistant のように自分と自分の端末だけが使うサービスは、unmask を通らないホスト名で外に置くほうが向いています。unmask の後ろに置く必要があるサービスは、設定 → ホワイトパス で機械の通信を通してください。
・Git ホスティング (Gitea・Forgejo・GitLab・cgit): Git over HTTP の preset で、clone・fetch・push・Git LFS を Git のエンドポイントだけ通せます。リポジトリの Web ページは challenge のままです。
・ほかのアプリ: クライアントが呼ぶパスを追加します。たとえば Nextcloud なら ^/remote\.php/・^/ocs/・^/status\.php$ です。どのパスで challenge されたかは bot ハント 画面の各リクエストで分かります。
・Jellyfin などのメディアサーバーは、アプリへの応答がサイトのルート直下にあり、1 つの接頭辞にまとまっていません。パスではアプリと Web ページを分けられないので、unmask の外に置いてください。
ホワイトパスに入れたパスは、rate limit や BAN を含むすべての判定を素通りします。サイト全体ではなく、クライアントに必要なエンドポイントだけを入れてください。
前段に CDN / リバースプロキシがある。正しいクライアント IP で判定される?
IP ベースの判定 (BAN / rate limit / 国別フィルタ / ホワイトリスト IP) は Web サーバーが見ているクライアントのアドレスを使います。前段に CDN / proxy / LB があると既定ではその前段のアドレスになるため、転送ヘッダから実際のアドレスを復元してください:
・nginx (自前の nginx に module を入れる構成): nginx 標準の set_real_ip_from (信頼する前段の範囲) と real_ip_header (Cloudflare なら CF-Connecting-IP、一般には X-Forwarded-For など) を設定します。これは unmask 固有ではなく nginx 標準の real_ip 機能です。
・gateway コンテナ: コンテナ内の nginx は編集せず、前段の LB の範囲を 設定 → ネットワーク → 信頼する LB / CDN に追加します (主要な事業者は preset あり)。訪問者のアドレスは X-Forwarded-For から読まれます。

復元しないと、すべてのアクセスが前段の同一 IP に見えて rate limit や国別判定が意図どおり効きません。JA4 の取得可否はこれとは別で TLS 終端の位置で決まります (前段で TLS を終端する構成では header forward が必要)。詳しくは JA4 取得方法 と ロードバランサ構成 を参照してください。
Nginx Proxy Manager・Traefik・Caddy・Cloudflare Tunnel をすでに使っている。unmask はどこに置く?
gateway コンテナを、次のどちらかの位置に置きます。
・今のプロキシの前: unmask が 80 / 443 番ポートで TLS を終端し、今のプロキシはその転送先 (設定 → Gateway) になります。TLS handshake を unmask が見るので、JA4 を含むすべての層が効きます。
・今のプロキシの後ろ: TLS は今のプロキシが終端して unmask に転送し、unmask がアプリへ転送します。判定に訪問者のアドレス (X-Forwarded-For) を使うよう、プロキシのアドレスを 設定 → ネットワーク → 信頼する LB / CDN に追加してください。TLS handshake はプロキシで終わるため、プロキシが指紋を header で渡さない限り JA4 の層は使えません (JA4 取得方法)。ほかの判定はすべて効きます。
Cloudflare Tunnel は後者です。TLS は Cloudflare が終端するので、tunnel の転送先を unmask にし、信頼する LB / CDN で Cloudflare (preset があります) を選びます。Nginx Proxy Manager は独自にビルドした nginx (OpenResty) を使っていて unmask の module の対象外なので、ほかと同じくコンテナを前か後ろに置きます。
forward-auth モードはどうなった?
forward-auth モードは凍結しました。使っている環境 (nginx の auth_request、Apache の mod_lua) はそのまま動き、不具合と脆弱性の修正も引き続き届きます。daemon が落ちている間の fail-open も変わらず、include より前に定義した location @unmask_daemon_down で挙動を変えられるのも同じです。新しい機能は nginx module (自前の nginx か、gateway コンテナの中) だけに入るので、新しく導入する場合はこの 2 つのどちらかを使ってください。移行のしかた:
・nginx の auth_request: unmask-plugin-nginx を入れ、forward-auth の include を server {} 直下の include /var/lib/unmask/nginx/server.inc; と、保護したい場所の include /var/lib/unmask/nginx/protect.inc; に置き換えて、インストールガイドの nginx module の手順どおり nginx を restart します (reload では新しい module が読み込まれません)。
・Apache の mod_lua: Apache の前に gateway コンテナを置き (インストールガイドの gateway container の手順)、LuaHookAccessChecker の行を削除します。
正規のユーザーが誤って challenge / ブロックされた。どう対処する?
まず既定の動作は即ブロックではなく PoW / CAPTCHA なので、本物のユーザーは challenge を解けばそのまま通れます (PoW は自動、CAPTCHA も 1 回)。特定の相手や経路を確実に素通しさせたい場合は、狙い撃ちで緩めるのが安全です:
・信頼できる IP (社内 / 監視 / LB の出口) → settings → ホワイトリスト IP
・特定のパス (公開 API など) → settings → ホワイトパス
・特定の JA4 を bot と誤判定している → settings → JA4 verdict tab でその verdict の action を observe / allow に
・全体的に厳しすぎる → 動作モード tab で KnownBrowserAction を pass に (普通のブラウザは素通し、curl / scraper だけ challenge。注意点は上の項目を参照)
どのアクセスが何で止まったかは bot ハント画面で JA4 / fingerprint / 理由を確認できます。原因を見てから、該当する分だけ緩めてください。
リリース前の修正版を試したい / 特定のバージョンだけ入れたい
確認をお願いした修正は、まず testing チャネルのプレリリースとして公開します。testing チャネルはどのインストールにも最初から設定済み (無効の状態) なので、試すのはコマンド 1 つです。鍵の追加もリポジトリ設定の追加も要りません。
# RHEL / Rocky / Alma / CentOS
sudo dnf --enablerepo=unmask-testing update unmask

# Debian / Ubuntu -- お伝えしたバージョンを、入れている unmask パッケージそれぞれに
sudo apt update
sudo apt install unmask=<version> unmask-plugin-nginx=<version> unmask-web-nginx=<version>

# Alpine
sudo apk upgrade --repository https://unmask.sh/dl/testing/apk/main
プレリリースは、目指すリリースより前に並ぶように番号を付けています。たとえば 0.1.46-0.1.rc1、0.1.46-0.2.rc2、そしてリリース本体が 0.1.46-1 です (Alpine では 0.1.46_rc2-r0 が 0.1.46-r1 より前)。なので後から元に戻す作業はありません。通常の更新で前のリリースに戻ることはなく、正式版が出ればそちらへ自動的に上がります。各プレリリースの中身は CHANGELOG の版ごとの見出し (例 [0.1.49-rc2]) にあります。その版で入った変更と、前のプレリリースからの手直しが書いてあり、それより下の見出しの分もすべて入っています。

ファイルを 1 つ直接入れたい場合: そのマシンのリポジトリ設定が必要なバージョンより古いときや、コマンド単位でリポジトリを切り替えられない古いディストリビューションでは、パッケージマネージャにファイルを直接指定します。
# RHEL / Rocky / Alma / CentOS  (yum でも同じ)
sudo dnf install -y https://unmask.sh/dl/testing/rpm/x86_64/RPMS/unmask-<version>.x86_64.rpm

# Debian / Ubuntu
curl -fsSLO https://unmask.sh/dl/testing/deb/pool/main/u/unmask/unmask_<version>_amd64.deb
sudo apt install -y ./unmask_<version>_amd64.deb
最新リリースなら testing を stable のパス (/dl/rpm/, /dl/deb/, /dl/apk/) に置き換えてください。これらのリポジトリには最新版しかありません。旧版 (パッケージ・静的 binary・署名済 checksums.txt、最新 6 版) は unmask.sh/dl/releases/ に、全バージョンは GitHub Releases にあります。付随パッケージは core を完全一致のビルドに固定するので、unmask / unmask-plugin-nginx / unmask-web-nginx は同一コマンドで入れてください。1 つずつ入れるとバージョン固定で失敗します。

EOL ディストリビューションで HTTPS が通らない場合: CA ストアが古く unmask.sh の証明書を検証できないと、上記の URL 直指定はそもそもダウンロードで失敗します (証明書の項)。CA を更新できない環境では、別のマシンでファイルを取得して転送し、ローカルファイルとして入れてください。
# 別マシンで取得 → 対象ホストへ転送 → ローカルインストール
sudo yum --nogpgcheck localinstall unmask-*.x86_64.rpm
--nogpgcheck になるぶん、転送前に取得したマシン側で署名を検証してください (rpm --checksig。鍵は https://unmask.sh/dl/keys/RPM-GPG-KEY-unmask)。

Alpine で手動導入する場合: index は oss@unmask.sh-260509.rsa.pub という鍵名で署名されており、/etc/apk/keys/ のファイル名が違うと apk はリポジトリ全体を untrusted 扱いにします。パッケージが存在しないように見えるだけで、エラーは出ません。unmask-release を入れれば正しい名前で配置されます。それ無しで始める場合は、この名前で取得してください:
sudo wget -O /etc/apk/keys/oss@unmask.sh-260509.rsa.pub \
     https://unmask.sh/dl/keys/unmask.rsa.pub
unmask は WAF ですか? 脆弱性スキャンや攻撃を防げますか?
unmask は WAF ではなく bot management ゲートウェイです。WAF が「リクエストの中身 (SQLi / XSS などの payload)」を検査するのに対し、unmask は「誰がアクセスしてきたか」を JA4 / PoW / CAPTCHA / 行動分析で判定し、自動化されたクライアントを入口で止めます。

セキュリティ上の効果は副次的だが実在します。wp-login.php や .env、既知の CVE path を機械的に探る脆弱性スキャナや、総当たり・credential stuffing の大半は JS を実行しない非ブラウザのクライアントなので、PoW / CAPTCHA を課す入口で弾かれ、攻撃の試行回数そのものが減ります (その結果、突破や情報漏えいのリスクが下がります)。AI によって脆弱性の探索や攻撃が自動化・高速化していく時代でも、「自動化された大量アクセス」にコストを課し続けられるのが強みです。

ただし unmask は脆弱性そのものを塞ぐものではありません。payload の検査も行いません。チャレンジを突破した本物のブラウザからの単発の攻撃や、既知 CVE のパッチ適用は、別のレイヤーの責務です。unmask は多層防御 (defense in depth) の一層として、WAF やパッチ運用と併用する位置づけです。
PoW のアルゴリズムは?
SHA-256 hashcash です (challenge.js に内蔵した pure-JS の SHA-256 実装で、先頭 N bit がゼロになる nonce を探します)。難易度は settings → challenge 設定 tab で leading-zero の bit 数を調整できます (既定 18 bit、通常 1 秒前後)。

難易度に出来ること・出来ないこと: 難易度はchallenge の JavaScript を実際に実行する相手にしかコストを課しません。獲得済みの pass cookie を使い回す相手は (ローミング再バインドで IP をまたぐ場合も含めて) クレデンシャルあたり 1 回しか解かないため、難易度を上げても一切遅くならず、初見の人間の訪問者だけが重くなります。締め出したい crawler は、難易度ではなく UA ルールの action deny (pass cookie より前に適用) で止めてください。
SQLite から MariaDB/MySQL へ (またはその逆へ) 移行したい (DB 接続先を変えたい)
管理画面の再設定ウィザード、または config.yml の直接編集で切り替えられます。どちらの方向 (SQLite → MariaDB/MySQL でも、MariaDB/MySQL → SQLite でも) も手順は同じです。ただし過去の event / ban / user などの履歴は切り替え先の DB に引き継がれません (元の DB はそのまま残ります。切り替え先に既存の unmask データがあればそれを利用し、空なら新しいスキーマを作成します)。

方法 A (再設定ウィザード、簡単): superadmin でログインして /unmask/admin/setup/ を開くと再設定モードになります。DB ステップで切り替え先の driver (SQLite / MariaDB/MySQL) を選び、接続情報 (MariaDB/MySQL は host・DB・user、SQLite はファイルパス) を入力 → 接続先が空なら最初の管理者を作成 (既に管理者がいれば「スキップ」で流用) → 確認 → 適用。DB を切り替えると background worker を新しい DB に張り直すため daemon が自動で再起動します (手動操作は不要)。

方法 B (CLI で手動): (1) unmask を停止 (2) config.yml の db.driver を切り替え先 (mariadb / sqlite) に変更して接続情報を記入 (MariaDB/MySQL は事前に DB と接続用ユーザーを作成、SQLite は sqlite_path を指定) (3) unmask doctor で接続を検証 (不足テーブルも報告) (4) unmask migrate で schema 投入 (5) unmask user create admin -role superadmin で管理者作成 (6) unmask を起動。
DB が保持期間を超えて増え続ける / doctor に「prune が追いついていない」と出る
生の event log は daemon 自身が events_retention_days まで削除 (prune) します。0.1.41 からこの prune は毎時、小さな chunk に分けて走り、書き込みロックを握るのは 1 回 1 秒程度、その間隔は握った時間以上空けるので、記録・毎時集計・BAN 参照は prune 中も流れます。予算で止まった回は 1 時間後に続きから再開します。最古の event が保持期間を 2 日以上超えたとき、または prune が 2 日以上完走していないときは unmask doctor とログ管理タブが警告します (以前は daemon の log にしか出ませんでした)。

保持期間をはるかに超える backlog (7 日設定に対して数週間分、保持期間を大きく下げた直後など) は、オフラインで片付けるのが最速です。daemon を止め (サイトは fail-open で配信を続けます)、次を実行します:
sudo systemctl stop unmask        # SysVinit なら: sudo service unmask stop
sudo unmask db-prune -config /etc/unmask/config.yml -mode rebuild -vacuum -analyze
sudo systemctl start unmask
-mode rebuild は保持期間内の行を新しいテーブルに写して残りを捨てます (index 9 本を持つ大きなファイルで行ごとに削除すると数時間かかるところが、数分で終わります)。-vacuum は空いた領域をファイルシステムに返します (先に DB と同じディレクトリへ生きているデータの複製を書くので、そこに結果サイズ程度の空き disk と、時間が要ります)。-analyze は query planner の統計を更新します。0.1.43 からこのコマンドは SQLite の一時領域を DB のディレクトリ (disk) に置き、空きが足りなそうなら rebuild / VACUUM を実行せずに止まります (-skip-space-check で強行可)。それ以前は一時領域が RAM だったため、数 GB のファイルでは結果サイズ分のメモリを要求していました。daemon が socket で応答している間は実行を拒否します。MariaDB/MySQL では -mode delete を使ってください。WAL ファイル (unmask.sqlite-wal) は性質上「最大到達サイズ」のまま残りますが、0.1.41 から checkpoint 後 64 MB に切り詰められ、daemon の停止が最後まで待ってもらえれば (数秒で KILL する init では次回起動時の復旧に回ります) checkpoint されて消えます。
更新したら管理画面に「データベースの更新があります」と出ました。これは何で、いつ実行すればよいですか?
0.1.49 から、大きなテーブルに index を作るだけのデータベース変更は、20 秒ほどを超える見込みのとき、daemon の起動時には適用しません。以前の版は、この index を作り終えてから待ち受けを始めていました。数 GB のデータベースでは、その数分間 daemon がいないことになります。サイトの配信は続きますが (fail-open)、challenge は出ず、イベントも記録されず、そのことを知らせる表示もありませんでした。いまは daemon がすぐに起動し、index が無いまま通常どおり動き、更新が待っていることを知らせます。パッケージ更新時の出力、管理画面の全ページ上部の通知、unmask doctor、unmask migrate -status、メトリクス unmask_schema_update_pending です。小さな導入先でこれを見ることはありません。見積もりが短ければ、これまでどおり起動時に適用します。

都合のよいときに実行してください。superadmin が通知の 今すぐ実行 を押すか、ホストで次を実行します。
sudo unmask migrate           # コンテナなら: docker exec <container> unmask migrate
sudo unmask migrate -status   # 待っている更新と見積もりを表示するだけ
どちらも同じ処理で、daemon は動かしたままです。index を作っている間も challenge は通常どおり動き、管理画面は閲覧できます。SQLite では構築がデータベースの書き込みロックを持つため、その間のイベントとアクセスログの集計は保持しておき、完了後に書き込みます。ユーザーや手動 BAN の編集など、データベースに直接書く変更は完了まで待ちます。設定の保存は実行中も行えます。MariaDB/MySQL では何も待ちません。通知には経過時間と見積もりが出ます。途中で中止でき、その場合は作りかけの index を破棄して、更新は待機中に戻ります。

実行するまで、何かが壊れているわけではありません。その index を使うクエリが、更新前と同じ速さのままというだけです。しきい値は config.yml の db.schema_update_defer_seconds です (既定 20。負の値にすると、以前の版と同じくすべて起動時に適用します)。テーブルそのものを変える更新は、常に起動時に適用します。
統計タブの「強制 CAPTCHA 判定 (理由別)」カードの reason の意味は?
none=通常の PoW (強制なし) / ja4_bot=JA4 verdict が bot / honeypot=罠を踏んだ / banned=この環境の BAN list にヒット (手動・honeypot など、この環境で登録された BAN。各エントリの登録元は BAN 管理ページで確認できます) / community_bans=アドレスまたは指紋が、共有 BAN (Community Bans) の feed に challenge の動作で載っている (0.1.28 から) / protected=保護パス mode / rate_limit=rate limit 超過 / header=header integrity (Chromium を名乗るのに Sec-CH-UA が無い) / stale=stale-browser 閾値より古いブラウザ版 / geo=国ルール / asn=ASN ルール / ua_target=UA が UA フィルタのブラックリストに一致 (0.1.18 から) / test=debug 経路。特定の reason に偏っていれば、その原因を狙い撃ちで設定を見直せます。
「(SSE 接続エラー)」が頻発する
多くは、前段の LB / proxy が idle connection を切ることで起こります (例:GCP LB の backend timeout は既定で 30 秒)。heartbeat は 5 秒間隔で送っています。「(再接続中)」と表示されている間は、EventSource が自動でリトライしています。完全に closed になったときだけ「接続エラー」と表示します。
DB を直接叩けないが、数字を見たい
unmask stats が読み出します。DB を手で開けない場面向けです — sqlite3 が WAL に対応していない古い distro(ファイルごと開けません)、3.38 より古い sqlite3(RHEL 8/9・Debian 11・Ubuntu 22.04 の標準。0.1.52 以降は統計用の索引で「malformed database schema」と出ます。DB は壊れていません)、認証情報が config.yml にしかない MariaDB/MySQL、DB クライアントを配るより読み取り専用コマンドを配る方が筋がよいホストなど。
# 直近 24h のリクエスト構成
unmask stats

# 1 サイト・1 週間を phase 別に
unmask stats -kind phase -since 7d -site www.example.com

# 問い合わせに貼る用に全部
unmask stats -kind all
-kind は traffic / phase / verdict / ip / ua / ja4 / all。-tsv を付けると awk で扱えるタブ区切りになります。読むだけで、DB を書き換えることはありません。
Prometheus メトリクスは出せる? 監視はどうする?

はい。unmask の daemon は Prometheus 形式の metrics endpoint を GET /unmask/metrics で公開します。scraper(30 秒間隔ほどで polling する agent)がこれを取得し、challenge の量、behavioral score の分布、DB latency などを Grafana で可視化します。 Go 標準ライブラリだけで Prometheus テキスト形式を直接出力するので、ランタイム依存は増えません。

公開しないでください。この endpoint にはその site の bot 圧(例: 「1 日に challenge が 10 万回発動」)が含まれるため、アプリ層でアクセスを絞っています。既定の allowlist は loopback のみ(127.0.0.0/8 + ::1)で、同 host の scraper なら設定不要で動きます。 リモートの Prometheus から取りたいときは、その scraper の IP / CIDR だけを 設定 → ネットワーク の metrics_allow_from に足してください。それ以外のアドレスからの request は 403 になります。Prometheus を使っていなければ無視で構いません(endpoint はありますが、誰も触りません)。

公開しているメトリクス

メトリクス 型 中身
unmask_event_total
{phase, verdict}
counter (宣言上) 直近 24 時間のイベント数を、challenge の phase と JA4 の verdict で分けたもの (verdict の無いイベントは (none))。「いま何が challenge されているか」の主要シグナル。endpoint は counter と宣言していますが、値は直近 24 時間の件数で、古いイベントが窓から外れると減ります。gauge としてグラフにしてください。rate() や increase() は、減少をすべて counter のリセットと読んでしまいます。
unmask_event_unique_ip gauge 直近 24 時間に、いずれかの phase で観測した送信元 IP のユニーク数。
unmask_verify_score
_bucket{le} / _sum / _count
histogram /api/verify が出す behavioral score の分布(bucket は 0.1 〜 1.0)。daemon 起動以降をプロセス内で数えます。閾値 0.5 を境に人間と bot がどこに寄るかを見られます。
unmask_captcha_math_no_input_total counter 標準 CAPTCHA の代替手段である足し算の問題で、回答欄に入力イベントが一つも無いまま届いた正解の数 (つまりスクリプトが値を入れたもの)。こうした通過は CAPTCHA 相当ではなく PoW 相当の扱いで発行されます。daemon 起動以降をプロセス内で数えます (0.1.33 から)。
unmask_db_query_seconds
_sum / _count {op}
counter この endpoint が裏で実行する 2 つの DB クエリ(op="metrics_phase_group" と op="metrics_unique_ip")の latency(sum と count)。endpoint が読み直すたびに測ります(最短 30 秒ごと。下記参照)。DB の健全性をざっくり見る目安。
unmask_schema_update_pending gauge 実行を待っているデータベース更新の数 (0.1.49 から。データベースの更新についての質問を参照)。待っているものが無ければ 0。
unmask_schema_update_running gauge その更新を適用している間は 1、それ以外は 0 (0.1.49 から)。

DB 由来のメトリクス(event_total / unique_ip)は 30 秒キャッシュするので、scrape 間隔を 短くしても DB 負荷は増えません。

scrape 設定

site 経由(推奨)。nginx が /unmask/ をすべて daemon に proxy するので、daemon の bind 方式(TCP / unix socket)に 関係なく動きます。Prometheus を host に向け、その IP を metrics_allow_from に足してください:

# prometheus.yml
scrape_configs:
  - job_name: unmask
    scheme: https
    metrics_path: /unmask/metrics
    static_configs:
      - targets: ['your-host']

同 host(既定・allowlist 変更不要)。Prometheus を unmask と同じ host で動かし、daemon が TCP bind(既定 bind: 127.0.0.1)なら、loopback を 直接 scrape できます — 既定の allowlist がそのまま許可します:

metrics_path: /unmask/metrics
static_configs:
  - targets: ['127.0.0.1:9477']   # daemon の server.port (既定 9477)

死活監視

単純な死活監視(uptime monitor、load balancer の health check、コンテナ orchestrator)には、認証なしの health endpoint を使ってください。200 と本文 ok を返すだけで、stats は一切出しません:

curl -sf https://your-host/unmask/healthz   # -> ok

healthz は何も明かさないからこそ、あえて allowlist なしで開けています。stats は /unmask/metrics とその allowlist の裏に置いています。

admin_allowed_ips を空にすると?
「allow all」 (全 IP を許可) として扱われ、防御は認証だけになります (unmask doctor が警告します)。空でなければ、daemon 自身の admin HTTP 層が、ログイン画面や再設定ウィザードを含むすべての管理画面でこの一覧を適用します。どの導入形態でも同じで、nginx の設定には書き出されません。設定 → ネットワーク で編集できます。
サードパーティ CAPTCHA に変えたら管理画面に入れなくなった
保護パスのプリセット unmask itself が有効だと /unmask/admin/ は CAPTCHA で覆われます。Turnstile / hCaptcha / reCAPTCHA はいずれも許可ドメインの登録が必須なので、管理画面にアクセスしているホスト名をその CAPTCHA の許可ドメインに登録していないと CAPTCHA ボタンが表示されず入室できなくなります (特にサーバ名で直接 admin を開く運用や multi-host 構成)。復旧は /etc/unmask/config.yml の challenge.default.captcha.provider を builtin に変更して unmask を再起動してください。unmask 標準 CAPTCHA は全 host で動くので管理画面に入れます。multi-host 環境では builtin が安全な既定です。
unmask は自分から外部に何を送る? 止められる?
既定では、訪問者に関する情報は送りません。自分から行う通信は次のとおりで、それぞれ止められます (宛先はすべて unmask.sh。IP-geo DB だけは、取れないときに db-ip.com から取ります)。
・クローラーの IP レンジとブラウザの版 (1 日 1 回): 設定 → About の「クローラーの IP レンジとブラウザの版を最新に保つ (1 日 1 回取得)」。OFF のあいだは、各リリースに同梱したデータを使います。
・共有 BAN (Community Bans): 初回起動時の登録と、1 時間ごとの一覧の取得。取得には、稼働中の install を数えるための token が付きます。止めるには Community Bans タブで購読モードを off に、数えられることだけ止めるなら publish_liveness を OFF にします。hub が保管する内容は プライバシーポリシー にあります。
・IP-geo DB (DB-IP Lite。unmask.sh のミラー、予備に db-ip.com): セットアップ後に取得し、以後は月 1 回更新します。止めるにはネットワーク タブの ipgeo.auto_fetch / auto_update を OFF にします。
・最新版のチェック (管理画面を開いたとき、最短 12 時間おき): 設定 → About。
これ以外は、自分で有効にして宛先を決めたときだけ行います。hub への BAN の共有 (既定 OFF。報告した IP と JA4 を送ります)、通知、自分の API キーを使う BAN 候補の AI 相談、外部の CAPTCHA、Advanced の機能 (Web Bot Auth と Privacy Pass は、検証に使う鍵を取得します)、gateway コンテナが Let's Encrypt で証明書を取るとき、です。
IP 位置情報 DB (mmdb) を入れたい / 最新化したい
通常は何もする必要がありません。セットアップが完了すると、daemon が登録不要の DB-IP Lite (CC BY 4.0) の国 DB と ASN DB を自分で /var/lib/unmask/ipgeo/ にダウンロードし、0.1.17 からは最新の状態も保ちます。毎日確認し、手元のファイルが 1 ヶ月より古くなっていれば、ダウンロードを検証してから新しい月次 snapshot に差し替えます。cron への登録は不要です (ipgeo.auto_fetch / auto_update / auto_update_asn、いずれも既定 ON。外部へ通信できないホストでは OFF にしてください)。

すぐに取得したい場合は、settings → ネットワーク の「📥 DB-IP Lite をダウンロード」ボタン (ファイルがあると「DB-IP Lite を更新」になります。国別フィルタ tab からもここへ案内されます) を使うか、sudo unmask install-ipgeo を実行します (ASN DB は -kind asn、両方なら -kind all)。ボタンと daemon 自身の更新は再起動なしで新しいファイルを読み込みます。シェルから install-ipgeo を実行した場合は、動作中の daemon に読ませるため unmask を再起動してください。nginx module では国と ASN のルールは render 済みの nginx 設定に書き込まれるため、新しい DB が反映されるのは、設定を render し直し (設定画面での保存、または unmask render-nginx)、nginx を reload した後です。
install 後にユーザーを作成し忘れた
sudo unmask user create <username> -role superadmin -password <pw> で作成できます (コンテナ install では、docker run の例のコンテナ名で docker exec unmask unmask user create …、compose なら docker compose exec unmask unmask user create …)。または /unmask/admin/setup/ のウィザードからでも作成できます。
パスワードを忘れた
sudo unmask user reset-password <username> -password <new> で再設定できます。
CentOS 6 / 7 で feed 同期が certificate signed by unknown authority で失敗する
EOL distro の CA bundle が古く、unmask の feed 同期 / auto-update の TLS 検証で Let's Encrypt の現行 root を持っていないために起こります (unmask のバグではなく、host 側の信頼ストアが原因です。unmask は system の信頼ストアに従う方針です)。sudo yum update ca-certificates を実行するか、ISRG Root X1 / X2 を信頼ストアに追加して unmask を再起動してください。同期できない間も、同梱の snapshot で検索 bot の救済は継続します。trust store を直せない場合や、外向き HTTPS がまったく無いホストは、次の項目の手順で range を更新してください。
unmask.sh に届かないホスト (外向き HTTPS が無い・trust store が古い) で、クローラーの IP range を最新に保つには?

unmask は検証済みクローラーの IP range (Googlebot、bingbot、GPTBot、ChatGPT-User、 ClaudeBot、Amazonbot など) を HTTPS の feed で最新に保ちます。この取得ができないホストが 2 種類あります: trust store が ISRG Root X1 より古いマシン (非常に古い企業向けディストリビューション。ログに x509: certificate signed by unknown authority が出て、 unmask doctor が crawler IP ranges を警告します) と、 外向き HTTPS 自体が許可されていないマシンです。以下が両方の運用手順です。

同期が一切なくても動くもの

すべてのリリースは全ベンダー range の組込スナップショットを binary 内に同梱しており、 リリースパイプラインは古いスナップショットの出荷を拒否します。つまり通常のペースで アップグレードしているだけでも range はそれなりに新しく保たれます。とはいえ鮮度は重要です: ベンダーは range を追加しますし、アドレスでクローラーを検証する機能は、手元のデータが 30 日より古くなると (doctor の警告付きで) 自動的に停止します。 0.1.49 からは、定期的な取得を意図して止めることもできます (設定 → About)。 その場合も、新しいリリースに同梱したデータが、以前に取得した古いコピーに代わって使われます。

オフライン更新 (0.1.33+)

# 1) unmask.sh に到達できる任意のマシンで:
curl -O https://unmask.sh/dl/feed/iprange/bypass-iprange-all.json

# 2) 制限ホストへ転送 (scp でも USB でも)
scp bypass-iprange-all.json restricted-host:/tmp/

# 3) 取り込み -- ネットワーク不要、一時 HTTP サーバも不要:
unmask update-iprange -file /tmp/bypass-iprange-all.json

# 4) 再レンダリングして nginx を reload
unmask render-nginx && nginx -t && nginx -s reload

稼働中の daemon は更新されたファイルを数秒以内に自動検知します (0.1.33+)。手順 4 の render + reload で、 range が nginx 自身の map に入ります。

0.1.32 以前では daemon は再起動まで起動時のスナップショットを使い続けます: render-nginx の前に service unmask restart (または systemctl restart unmask) を実行してください。さもないと次に設定を 保存した瞬間、古い range が静かに焼き直されます (これらのバージョンには -file も無いので、代わりに loopback 限定の一時 HTTP サーバを立てて update-iprange -url を向けます)。

自動化

# 両側に到達できる踏み台で、例えば週次:
curl -sO https://unmask.sh/dl/feed/iprange/bypass-iprange-all.json \
  && scp -q bypass-iprange-all.json restricted-host:/tmp/ \
  && ssh restricted-host 'unmask update-iprange -file /tmp/bypass-iprange-all.json \
       && unmask render-nginx && nginx -t && nginx -s reload'

社内ミラーから取らせる (0.1.46+)

対象ホストから社内の Web サーバーには届くなら、2 つの feed 文書をそこへミラーして、daemon の 取得先に指定できます。daemon はいつもの 1 日 1 回の間隔で取りに行くので、手で取り込む作業は 要りません。2 つ目の文書は、古いブラウザの判定で比べる現行ブラウザの版数です。

# unmask.sh に届くマシンで毎日 — 各文書を署名ごと取得:
for f in bypass-iprange-all.json browser-majors.json; do
  curl -fsSO https://unmask.sh/dl/feed/iprange/$f && curl -fsSO https://unmask.sh/dl/feed/iprange/$f.sig
done
# 社内の Web サーバーで配信する (各 .sig は文書の隣に置く)

# 対象ホストの daemon 設定 (パッケージ導入なら /etc/unmask/config.yml) の nginx: の下:
  sync_hub_url: "https://mirror.example.internal/unmask/bypass-iprange-all.json"
  browser_majors_hub_url: "https://mirror.example.internal/unmask/browser-majors.json"
  sync_require_signature: true   # .sig の無い bypass-IP 文書は拒否する

設定したら unmask を再起動します。daemon は各文書の隣の <url>.sig を取得し、 公開 feed と同じように検証します。公開 feed のときと同じく、更新された range は nginx の次の reload で nginx 自身の map に入ります。

feed は署名されています (0.1.33+)

すべての feed には分離署名 (bypass-iprange-all.json.sig、ed25519) が併置されます。 署名は配信ホストとは別の署名ホストで生成され、feed を配る側は鍵を一切持ちません。daemon は 取得のたびに署名を検証し、一致しない文書を拒否します。0.1.49 からは、公開 feed に署名が 付いていない場合も拒否します。配信ホストが署名を出さないだけでリストを差し替える、 ということができなくなります。自前のミラーに同じ扱いをさせるには sync_require_signature を指定します。転送してきたファイルは、隣に .sig が あれば検証します。取り込み前に手元で確かめることもできます:

unmask sign-feed -verify /tmp/bypass-iprange-all.json   # 隣に .sig が必要

これが insecure-transport の逃げ道を提供できる理由でもあります: sync_insecure_tls: true (または update-iprange -insecure-tls) を 指定するとトランスポートの証明書検証は免除されますが、その代わり署名が必須に なります — 無署名や署名不一致の文書は即座に拒否されます。切った検証の代わりを内容の検証が 務める、という交換です。可能なら trust store 自体の修正が今でも最善です。

証明書検証を外すと署名が必須になる理由

この feed が運ぶ range は信頼データです: そこに載ったアドレスは challenge を 完全にバイパスします。検証なしの接続で取得し、ほかに何も確かめなければ、経路上の第三者が自分の アドレスをあなたのバイパスリストに注入できてしまいます — 絶対に乗っ取られてはいけない仕組み そのものの乗っ取りです。そのため、証明書検証を外すだけのスイッチはありません。 sync_insecure_tls (上記) は必ず署名の必須化とセットで、接続の代わりに中身を検証します。 可能なら、上記のオフライン取り込み、または trust store 自体の修正 (ISRG Root X1 ルート証明書を システム CA バンドルへ追加する方法。そのホストの他のモダンな TLS 接続も同時に直ります) の方が 今でも良い解です。