人間には易しく、
機械には厳しく。

TaruCaptcha は、ブラウザ環境の整合性と操作のリズムからリスクを算出し、必要なときにだけ課題を出す適応型の CAPTCHA です。大半の利用者はクリックせずに通過し、疑わしいアクセスほど難しい課題に向き合うことになります。

4検知レベル(0〜3)
9課題ファミリ
0Cookie・外部スクリプト
<1s課題生成〜検証の目標
01 — 仕組み

三つの段階で、必要な分だけ確認する

クライアントは判定をしません。生の特徴量だけを送り、サーバーが毎回スコアを再計算します。

観測

SDK がページ上の操作(カーソルの加速度やジッター、タッチの接触面、キー入力の間隔)と、Canvas / WebGL 描画や自動化ツールの痕跡を受動的に集めます。キーの内容や生の IP は送りません。

リスク判定

環境・行動・ネットワーク・履歴の 4 系統を 0〜1 に正規化し、noisy-OR で統合して 0.0〜1.0 のスコアにします。WebDriver のような決定的な信号は最低レベルを引き上げます。

課題または通過

スコアに応じて Level 0〜3 を選び、Level 0 ならそのまま単回使用のトークンを発行します。課題は毎回生成され、解答だけでなく解いている間の操作も検証します。

02 — 検知レベル

リスクに比例した難しさ

既定の閾値は 0.20 / 0.50 / 0.80。ログインや決済など重要なアクションでは閾値を 0.05 下げ、課題に失敗すると次はひとつ上のレベルになります。

Level条件利用者が体験すること目安
L0 Passscore < 0.20、1.5 秒以上の観測または良好な履歴何も表示されない。送信と同時にトークンが付く。0 秒
L1 Low0.20 〜 0.50、または行動の証拠が無いアイコンのタップ、スライダーパズルなど 1 ステップ。2〜3 秒
L2 Medium0.50 〜 0.80、または自動化痕跡敵対的ノイズ付きの画像選択、3D 回転、軌跡なぞり、並べ替え。4〜6 秒
L3 High0.80 以上、TLS 指紋と UA の不一致など2 段の連続課題。各段で操作の人間らしさも検証。10〜15 秒
03 — 課題ファミリ

九つの課題、すべて使い捨て

画像も音声も毎回生成し、配信 URL は一度しか使えません。分類だけできる AI には解けない「向き」や「大小」の関係を問います。

icon_tap

アイコンタップ

ノイズ背景の中から指定した図形をひとつタップ。

L1visual
slider_jigsaw

スライダーパズル

欠けたピースを正しい位置までスライド。ドラッグの軌跡も評価。

L1L2interactive
rotate_3d

3D 回転

立方体をドラッグして参照画像と同じ向きに。角度差で判定。

L2L3spatial
adversarial_grid

敵対的ノイズ画像選択

幾何歪みと周波数領域の摂動を加えた 9 枚から「左を向いた動物」などを選ぶ。

L2L3visual
trace_path

軌跡なぞり

曲線を始点から終点までなぞる。Fréchet 距離で一致を判定。

L2L3interactive
drag_sort

並べ替え

大きさの異なる円を小さい順に並べる。キーボード操作にも対応。

L2interactive
mini_game

ミニゲーム

動く的を 3 回タップ。タップ時刻と位置をサーバーが再計算。

L3interactive
noisy_audio

ノイズ混入音声

背景雑音の中の短いビープ音を数える。視覚課題の代替。

L1L2L3audio / a11y
local_text

ローカル論理問題

画像を配信できない低帯域環境向けの短い設問。

L1L2text / a11y
04 — 検知エンジン

四つの系統を独立に見て、ひとつでも強ければ疑う

環境フィンガープリント

navigator.webdriver、ChromeDriver / Selenium / Playwright のグローバル変数、CDP の Runtime.enable 痕跡、ネイティブ関数の差し替え、SwiftShader などのソフトウェア描画、Canvas と WebGL の描画結果。

行動バイオメトリクス

カーソルの直線性・ジッター・速度変動・イベント間隔の規則性・3 次ベジェ適合残差(滑らかすぎる軌跡は生成器の疑い)、タッチの接触半径と圧力の分散、キー入力の Dwell / Flight time の変動係数。

ネットワーク

データセンター / VPN / Proxy / Tor の判定、JA3 / JA4 の TLS 指紋と申告 UA の整合、HTTP/2 指紋、IP /24・サイト・指紋ハッシュの 3 段レートリミット。

レピュテーション

日次ローテーションのソルト付きハッシュだけで保持する失敗率・超人的な解答速度・近傍 IP の Bot 比率。良好な履歴はスコアを割り引きます。

05 — セキュリティ

再利用できるものを残さない

  • 課題トークン・通過トークンは HMAC-SHA256 の JWT。jti は単回消費、有効期限は 120〜300 秒。
  • IP /24・UA ハッシュ・サイトキーにトークンを束縛。別端末からの提示は拒否。
  • 解答そのものはサーバーに持たず、コミットメントだけを照合。
  • 正答でも、課題中の操作が機械的なら不合格。不可視のハニーポットに触れた場合も同様。
  • 判定理由はクライアントに返さない。攻撃者への勾配情報を与えないため。
— プライバシー

集めない、残さない

  • Cookie・localStorage を使わない。同意バナーは不要。
  • 生の IP アドレスはログにも残さない。/24 に丸めて記録。
  • キー入力はタイミングのみ。文字は収集しない。
  • 操作の軌跡は判定後に破棄。残るのは匿名の集計値のみ。
  • 音声課題の音は端末外へ送らない。
06 — 統合

フロントエンドに 3 行、バックエンドに 1 リクエスト

フロントエンド(ES Module)

import { TaruCaptcha } from "https://captcha.taruta.run/sdk/tc.js";

const tc = await TaruCaptcha.init({ siteKey: "tc_…", action: "login" });
form.addEventListener("submit", async (e) => {
  e.preventDefault();
  form.taru_token.value = await tc.execute();   // Level 0 なら UI なし
  form.submit();
});

バックエンド(サーバー間)

curl -X POST https://captcha.taruta.run/api/v1/siteverify \
  -H "Authorization: Bearer $SITE_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"pass_token":"…","remote_ip":"203.0.113.5","expected_action":"login"}'

{"success":true,"score":0.07,"level_served":0,"action":"login",
 "hostname":"captcha.taruta.run","challenge_ts":"2026-10-03T08:00:12Z","error_codes":[]}

エンドポイント

  • GET/api/v1/session/init — ナンスと鍵素材の発行(10 分、単回)
  • POST/api/v1/challenge/request — メトリクス送信、Level と課題または通過トークン
  • POST/api/v1/challenge/verify — 解答と課題中の操作を検証
  • POST/api/v1/siteverify — サイト側バックエンドが通過トークンを単回消費

エラーは RFC 9457 Problem Details(application/problem+json)で返します。409 token-already-used、410 challenge-expired、422 context-mismatch、429 rate-limited。ブラウザ向けのページは /errors/ にまとめています。