unmask

docs

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

FAQ

JA4 ってどの構成で取れるの? forward-auth だと取れない?
JA4 を取得できるかどうかは TLS 終端の位置で決まります。nginx + unmask-plugin-nginx (native mode) なら取得できます。gateway コンテナも自分で TLS を終端するため、任意の HTTP server の前段で取得できます。どちらも無い forward-auth mode では取得できません (前段の LB / CDN が header forward する場合を除く)。構成ごとの図解は JA4 取得方法 をご覧ください。
forward-auth で daemon が落ちたときの挙動は? fail-closed にできる?
forward-auth は daemon が落ちると既定で完全に fail-open します。保護パスはゲート (auth_request) が素通しし、challenge 途中の訪問者も / へ 302 で戻ってそのままバックエンドへ流れます (native mode が元リクエストを replay するのと同じ結果)。サイトは落ちません。

逆に fail-closed にしたい場合 — daemon down 時に 503 を返して上位 LB の health check に落とし、その node を drain させる、または元リクエストを自前のバックエンドへ流す — は、@unmask_daemon_downinclude より前に定義します (nginx は named location の先に定義された方を採用するため、include の後に書くと黙って無視されます):
# (a) 503 で上位 LB に drain させる:
location @unmask_daemon_down { add_header Retry-After "10" always; return 503; }
# (b) 元リクエストを自前バックエンドへ流す:
location @unmask_daemon_down {
    proxy_pass http://your-upstream;          # static 配信なら: try_files $uri $uri/index.html =404;
    proxy_set_header Host $host;
}
include /etc/unmask/forward-auth/server.inc;
JA4 verdict が空 ("-") のイベントがあるけど大丈夫?
問題ありません。これは upstream (nginx native module / GCP LB など) が JA4 を取得できなかったリクエスト (TLS resumption や一部の特殊な handshake) で発生します。unmask 側が原因ではありません。
stealth 列が赤く表示される。問題ない?
「JS 検出 (flags) はすべてクリアなのに JA4 では bot 判定」というのは、最新のヘッドレス bot が来ている兆候です。ただしブロック判定では challenge が強制されるので、ダッシュボードの「JA4 bot/suspect 判定 × CAPTCHA 通過」カードが 0 のままであれば、実際には何も通過していません。
既定ではどの訪問者が 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 で KnownBrowserActionpass に設定します。
前段に CDN / リバースプロキシがある。正しいクライアント IP で判定される?
IP ベースの判定 (BAN / rate limit / 国別フィルタ / ホワイトリスト IP) は nginx が見ているクライアント IP を使います。前段に CDN / proxy / LB があると既定ではその前段の IP になるため、nginx 側で set_real_ip_from (信頼する前段の範囲) と real_ip_header (CDN なら CF-Connecting-IP、一般には X-Forwarded-For など) を設定し、転送ヘッダから実 IP を復元してください。これは unmask 固有ではなく nginx 標準の real_ip 機能です。

復元しないと、すべてのアクセスが前段の同一 IP に見えて rate limit や国別判定が意図どおり効きません。JA4 の取得可否はこれとは別で TLS 終端の位置で決まります (前段で TLS を終端する構成では header forward が必要)。詳しくは JA4 取得方法ロードバランサ構成 を参照してください。
正規のユーザーが誤って challenge / ブロックされた。どう対処する?
まず既定の動作は即ブロックではなく PoW / CAPTCHA なので、本物のユーザーは challenge を解けばそのまま通れます (PoW は自動、CAPTCHA も 1 回)。特定の相手や経路を確実に素通しさせたい場合は、狙い撃ちで緩めるのが安全です:
・信頼できる IP (社内 / 監視 / LB の出口) → settings → ホワイトリスト IP
・特定のパス (公開 API など) → settings → ホワイトパス
・特定の JA4 を bot と誤判定している → settings → JA4 verdict tab でその verdict の action を observe / allow に
・全体的に厳しすぎる → 動作モード tab で KnownBrowserActionpass に (普通のブラウザは素通し、curl / scraper だけ challenge)
どのアクセスが何で止まったかは hunt 画面で JA4 / fingerprint / 理由を確認できます。原因を見てから、該当する分だけ緩めてください。
リポジトリ設定を変えずに、特定のバージョンだけ入れたい
パッケージマネージャにファイルを直接指定します。修正の確認を依頼されたとき、そのマシンのリポジトリ設定が必要なバージョンより古いとき、コマンド単位でリポジトリを切り替えられない古いディストリビューションで有効です。
# RHEL / Rocky / Alma / CentOS  (yum でも同じ)
sudo dnf install -y https://unmask.sh/dl/testing/rpm/x86_64/RPMS/unmask-0.1.21-1.x86_64.rpm

# Debian / Ubuntu
curl -fsSLO https://unmask.sh/dl/testing/deb/pool/main/u/unmask/unmask_0.1.21-1_amd64.deb
sudo apt install -y ./unmask_0.1.21-1_amd64.deb

# Alpine
sudo apk add --repository https://unmask.sh/dl/testing/apk/main unmask
最新リリースなら 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-*-1.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 でログインして /admin/setup/ を開くと再設定モードになります。DB ステップで切り替え先の driver (SQLite / MariaDB/MySQL) を選び、接続情報 (MariaDB/MySQL は host・DB・user、SQLite はファイルパス) を入力 → 接続先が空なら最初の管理者を作成 (既に管理者がいれば「スキップ」で流用) → 確認 → 適用。DB を切り替えると background worker を新しい DB に張り直すため daemon が自動で再起動します (手動操作は不要)。

方法 B — CLI で手動: (1) admin 停止 (2) config.ymldb.driver を切り替え先 (mariadb / sqlite) に変更して接続情報を記入 (MariaDB/MySQL は事前に DB と接続用ユーザーを作成、SQLite は sqlite_path を指定) (3) unmask doctor で接続を検証 (不足テーブルも報告) (4) unmask migrate で schema 投入 (5) unmask user create admin -role superadmin で管理者作成 (6) admin 起動。
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 と retention タブが警告します (以前は 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 8 本を持つ大きなファイルで行ごとに削除すると数時間かかるところが、数分で終わります)。-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 されて消えます。
統計タブの「強制 CAPTCHA 判定 (理由別)」カードの reason の意味は?
none=通常の PoW (強制なし) / ja4_bot=JA4 verdict が bot / honeypot=罠を踏んだ / banned=BAN list にヒット (共有バン・手動・honeypot 由来を含む。source 別の内訳は Community Bans / BAN 管理ページで確認できます) / protected=保護パス mode / rate_limit=rate limit 超過 / header=header integrity (Chromium を名乗るのに Sec-CH-UA が無い) / stale=stale-browser 閾値より古いブラウザ版 / geo=国ルール / asn=ASN ルール / test=debug 経路。特定の reason に偏っていれば、その原因を狙い撃ちで設定を見直せます。
「(SSE 接続エラー)」が頻発する
多くは、前段の LB / proxy が idle connection を切ることで起こります (例:GCP LB の backend timeout は既定で 30 秒)。heartbeat は 5 秒間隔で送っています。「(再接続中)」と表示されている間は、EventSource が自動でリトライしています。完全に closed になったときだけ「接続エラー」と表示します。
DB を直接叩けないが、数字を見たい
unmask stats が読み出します。DB を手で開けない場面向けです — sqlite3 が WAL に対応していない古い distro(ファイルごと開けません)、認証情報が 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
-kindtraffic / phase / verdict / ip / ua / ja4 / all-tsv を付けると awk で扱えるタブ区切りになります。読むだけで、DB を書き換えることはありません。
Prometheus メトリクスは出せる?
はい — admin daemon が /unmask/metrics で Prometheus 形式の endpoint を出します。phase / verdict 別の challenge 量、behavioral score の histogram、DB latency など。既定は loopback のみで、リモートから取るなら scraper の IP / CIDR を metrics_allow_from(設定 → Network)に足します。死活監視用に、認証なしで ok を返す /unmask/healthz もあります。詳しくは 監視
admin_allowed_ips を空にすると?
「allow all」 (全 IP を許可) として扱われ、認証のみで防御します。空でなければ、handler 層と nginx render の両方で IP 制限が効きます。
サードパーティ CAPTCHA に変えたら管理画面に入れなくなった
保護パスのプリセット unmask itself が有効だと /unmask/admin/ は CAPTCHA で覆われます。Turnstile / hCaptcha / reCAPTCHA はいずれも許可ドメインの登録が必須なので、管理画面にアクセスしているホスト名をその CAPTCHA の許可ドメインに登録していないと CAPTCHA ボタンが表示されず入室できなくなります (特にサーバ名で直接 admin を開く運用や multi-host 構成)。復旧は /etc/unmask/config.ymlchallenge.default.captcha.providerbuiltin に変更して unmask を再起動してください。unmask 標準 CAPTCHA は全 host で動くので管理画面に入れます。multi-host 環境では builtin が安全な既定です。
IP 位置情報 DB (mmdb) を入れたい / 最新化したい
一番簡単なのは sudo unmask install-ipgeo です。登録不要の DB-IP Lite (CC BY 4.0) を自動でダウンロードし、/var/lib/unmask/ipgeo/ に配置します。cron に登録しておけば月次更新も自動で行えます。手動で行いたい場合は、settings → 国別フィルタ tab の「📥 dl / 更新」ボタンからでも取得できます。mmdb は hot-reload されるので、admin の再起動は不要です。
install 後にユーザーを作成し忘れた
sudo unmask user create <username> -role superadmin -password <pw> で作成できます (コンテナ install では docker compose exec admin unmask user create …)。または /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 の救済は継続します。