Aikido のダッシュボードを久しぶりに開いたら自分のサイトが Domain not accessible に。通知設定の漏れで cert 切れに気付けなかった反省と、ついでに API DAST を試した結果。Domain と API の違い、認証セットアップでハマった話、検出された 1 件の Issue まで、実体験を整理した記事。
「Aikido の API DAST、まだ触ったことないな」と思って app.aikido.dev を開いた。
数ヶ月前に登録したまま放置していた Domain がいくつか並んでいた。何の気なしにこのサイト(aikido-community.jp)の行を見ると、赤いバッジで "Domain not accessible" と表示されていた。

冷や汗。
詳細を開くと、複数の Findings。中でも目立つのは High: Domain SSL Certificate Expiration。

確認すると、証明書は前日に切れていた。
notBefore=Feb 7 08:01:07 2026 GMT
notAfter=May 8 08:01:06 2026 GMT
調査すると:
certbot コンテナは docker-compose.yml に定義されていたcrontab -l は空っぽ90日経って、当然のように切れた。
ここで本当のダメさ:Aikido の通知(Email Forwarding / Slack / Teams 等)は何も設定していなかった。検知能力があっても、自分に届く経路を組んでいなければ知りようがない。ツール導入 ≠ 運用を地で行く状況だった。
certbot renew を手動で叩いて復旧。同時に cron も追加して自動更新を仕込んだ。
復旧後、Critical で出ていた CSP 抜けや X-Frame、X-Powered-By、Server header 漏洩も対応した。
ただし、全部は潰せていない。CSP 系で High が1件残っている(インライン JavaScript を許容しているため)。これは大量の onclick 属性を data-action に書き換える必要があり、別タスクとして切り出して別記事で取り組む予定。
つまり、Critical は対応したが Findings の総数は思ったほど減っていない。中身は変わったが、見た目の数値は微減。これも現実。
ここまでは Domain(Front-End / Surface Monitoring) のスキャン。Aikido のもう一つの目玉、API Scanning も試してみることにした。
ターゲットは個人で運用している古い検証用 API。Express + PostgreSQL 構成で、20以上のエンドポイントの REST API がある。本番サービスではない。
| Domain (Front-End) | API Scanning | |
|---|---|---|
| やってること | ヘッダ・DNS・証明書・設定の検査 | 実際に攻撃ペイロードをファジング |
| 本番で実行 | OK(非破壊的) | NG(必ず staging) |
| 何を見つける | 設定漏れ、cert 期限、ヘッダ抜けなど | SQLi、NoSQLi、IDOR、コマンド注入、認証バイパスなど |
| 時間 | 数十秒〜数分 | 数分〜数十分 |
| 料金プラン | Free でも一部利用可 | Attack スイート(有料) |
つまり 「Domain は健康診断」「API は実弾の侵入テスト」。並行で使うと Attack Surface の網羅率が上がる。
API DAST は「どこに何を撃つか」を知る必要がある。Aikido の選択肢:
今回はルート定義から手書きで openapi.yaml を作成。
openapi: 3.0.0
info:
title: Sample API
version: 1.0.0
servers:
- url: https://example.com
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
paths:
/api/match/check:
get:
security: [{bearerAuth: []}]
...
20数エンドポイント分で約200行。
Aikido ダッシュボード → Add Domain → Type: REST API → URL 入力 → Upload openAPI spec manually。
「Do NOT run REST API checks on production. Use staging only.」と警告が出る。必ず staging(または無人の検証環境)で実行する。本番でファジングを撃つと DB にゴミレコードが大量に入るし、決済処理を誤発火する可能性もある。
API のほとんどが JWT 認証だったので Auth via Custom Headers を選択。フォームには2つの欄がある:
正解の入力:
| 欄 | 入力値 |
|---|---|
| Header name | Authorization |
| Header value | Bearer eyJhbGc...(実トークン) |
ここが重要。Bearer は value 側に入れる。Header name は単なるヘッダ名で、スキーム(Bearer)と一緒に書かない。
最初これを取り違えてハマった。後述する。
JWT トークンの取得方法:
copy(localStorage.getItem('token')) を実行これを Custom Header の value に貼り付けて、頭に Bearer を付け、Confirm Authentication → Start scan。

最初のスキャンで 1067リクエスト中 832 が 401 Unauthorized。Authentication Failed バナーが出た。

curl で同じトークンを直接叩くと 200 OK で通る。なのに Aikido からは全部 401。
Request Logs から1件開いて確認:
"authorization ": "Bearer eyJh********vG1W0"
↑ 末尾に半角スペース
ヘッダ名 authorization の後ろに見えないスペースが1個入っていた。Express は req.headers['authorization'](スペース無し)を見るので、authorization (スペース付き)は別物として無視される。だから "No token provided" のエラーが返っていた。
原因は私のミス。最初に Header name 欄に Authorization Bearer と書いていた(value 欄に入れるべき Bearer を name 欄に書いてしまった)。あとから「Bearer」を削除したら、間にあったスペースだけ取り残されて、Authorization というヘッダ名で保存されていた。
教訓は単純:Bearer は value 欄、name 欄は Authorization だけ。これが正しい使い方。
地味だけど、curl では再現しない、GUI 越しで初めて引っかかるタイプのバグ。Aikido の Request Logs 機能が無かったら原因特定はかなり困難だっただろう。
ヘッダ名フィールドを再入力して保存 → 再スキャンで認証通過。

スキャン結果として Aikido が「Issue」として正式に上げたのは1件だけ だった。
3エンドポイントが該当:
/api/ai/tts の text フィールド/api/user/profile の nickname フィールド/api/ai/message の message フィールド検出パターンは共通:文字列が来るべきフィールドに、NoSQL 演算子付きオブジェクトを送る。
{
"text": {
"$nonexistentoperator": "function() { return 1; }"
}
}
これに対して全エンドポイントが 500 を返した。Aikido の TL;DR:
A NoSQL injection vulnerability was discovered, which allows an attacker to exfiltrate data from your NoSQL database.
修正方針:
if (typeof text !== 'string') {
return res.status(400).json({ error: 'Invalid text' });
}
23 routes に対して Issue 1件(3 sub-issue)。
そのまま受け取る前に、見ながら考えたことがある。
PostgreSQL バックエンド。Redis も使っているがセッションキャッシュ用途で、ユーザー入力を直接クエリに渡す経路ではない。/api/ai/tts に至っては DB すら触らず、text を OpenAI の Audio API に丸投げするだけ:
const response = await fetch('https://api.openai.com/v1/audio/speech', {
body: JSON.stringify({ input: text, ... }) // ← OpenAI に投げるだけ
});
「NoSQL injection」というラベルはこのアプリの構成と合っていない。
Aikido に渡した openapi.yaml で各フィールドを type: string と宣言した。が、Express は OpenAPI スキーマを自動で強制しない。スキーマはドキュメントとして渡しているだけで、リクエストが実際にスキーマ通りかは別途バリデーションが必要。
DAST が「object を投げたら 500 になる」と検出したのは、要するに OpenAPI で宣言した型契約と、backend の実装が一致していない という話。これは「injection」というより「スキーマ契約違反 / 入力検証欠如」と呼ぶ方が正確。
/api/ai/tts の実際の応答:
{"error": "TTS failed"}
データ漏洩なし、認証バイパスなし、ただ落ちてるだけ。Aikido の TL;DR は「データを exfiltrate される可能性」と書いているが、このアプリで成立する経路は今回見当たらない。
ラベルは違うが、検出している本質は 「入力の型バリデーション欠如」 で、これは事実。修正方針も Aikido の言う通り正しい。型チェック1行で 500 が 400 になり、Issue は消える。
ただ「NoSQL injection の脆弱性が3件あった」と読者が受け取ると、別の場所(DB 周り)を直そうとする可能性がある。Aikido の Findings は中身を読んで自分のスタックに翻訳するプロセスが要る、というのが今回の発見。
3点に集約:
Domain は cert 期限・ヘッダ抜けなどの広く浅い健康診断で、本番 OK。運用ミスを継続的に検知する装置として最適。
API は実弾で深く狭い検証。staging 必須だが、入力検証漏れ・認証バイパス・IDOR など、コードレビューでは見逃しがちな問題を機械的に発見できる。
両方を組み合わせて初めて Attack Surface の網羅率が上がる。
私の cert が切れた最大の原因は「Aikido の通知経路を何も設定していなかった」こと。Slack も Email Forwarding も Teams も、Settings の Chat & Alerts は全部未接続だった。ダッシュボードに行けば High Findings は出ていたが、自分から見にいかない限り気付けない状態。ツールは入れただけでは仕事をしない。導入と通知設定はセット。

Aikido が出した「NoSQL injection」というラベルは、汎用的な検出ロジック(オブジェクトを送って 500 を観測)の名前であって、実際にそのアプリで NoSQL injection が成立するとは限らない。今回がまさにそれで、PostgreSQL バックエンドかつ DB を触らないエンドポイントにまで「NoSQL injection」のラベルが付いていた。
DAST は 自動・継続・広範囲で既知の攻撃パターンを機械的に試す装置。検出するパターン自体は妥当だが、ラベルを自分のスタックに翻訳して、本当の問題が何かを見極めるのは人間の仕事。
ビジネスロジックの欠陥・複雑な認証フロー・複数ステップを連鎖させる攻撃など、DAST には検出できない領域もある。深い脆弱性を本気で探すなら、人間の侵入テスト(Pentest)が必要。Aikido も Pentest は別商品として用意している。
DAST が出した結果は「ここまでは機械的に確認済み」というベースラインであって、「これで安全」を意味しない。今回検証用 API でやったのは「広く浅く」の方だけ。
記事中の Findings は2026年5月時点のもの。Aikido は継続的に新しい検出パターンを追加しているので、最新の情報は公式ドキュメントを参照してください。
Norihisa Ando
公開日: 2026/5/10
最終更新: 2026/5/10
セキュリティの最新情報をお届けします。