発見から確認まで:teai 不正アクセスの対応の記録(11月9日・時系列)
株式会社イネブラ(teai の運営会社)/掲載 2026年11月9日/時刻はすべて日本時間
2026年11月9日、不正アクセスに気づいてから、止める・鍵を替える・知らせる・確かめるまでの1日を、時刻つきで残します。対応の途中で私たち自身が起こした2回の停止と、作業を手伝った AI の誤りも、そのまま書いています。同じような事故に向き合うエンジニアの方の参考になればと思います。
攻撃の仕組みと根本原因は技術レポートに、お客様へのお願いはお知らせにあります。
この日の対応は、運営者1人と、作業を手伝う複数の AI エージェントで行いました。AI の作業を確かめる手順を決めるのは、当社の責任でした。時刻は、マージした時刻・本番に反映した時刻・本番で確かめた時刻を分けて書いています。
1日の流れ(要約)
| 時刻 | 出来事 |
|---|---|
| 11:38 | 社内の AI 端末(Sente)が止まったと、運営者が気づく |
| 11:57 | 残高の不具合の調査中に、データベースの記録から、本番の鍵の書き出しを見つける |
| 11:57〜11:58 | 該当アカウントを隔離し、API キーを止める |
| 12:51 | 原因の機能が本番から消えたことを確認(マージ 12:01、反映 12:23) |
| 12:50〜12:51 | データ暗号の鍵を入れ替え、保存データを暗号化し直す |
| 14:21〜15:00 | 攻撃が9月から続き、代表アカウントが乗っ取られていたことが判明 |
| 16:49〜17:10 | 当社のミスによる停止①(データベースの鍵の入れ替えの失敗、21分) |
| 17:26〜17:50 | 詐欺メールの送信を知り、メール送信の鍵を全部止めて、各国の機関へ通報 |
| 17:59 | 個人情報保護委員会へ速報 |
| 18:05 | 全員のウェブのログイン状態を解除 |
| 18:39〜18:43 | 決済の鍵を入れ替え |
| 19:00〜19:31 | 当社のミスによる停止②(使用中の決済の鍵を無効にし、決済が停止) |
| 19:33〜20:06 | 偽の API キーの照合と、全サービスでの鍵の確認 |
1. 発見(11:38〜11:57)
11:38 社内で使っている AI 端末(Sente)がエラーを返すと、運営者が気づきました。外部の AI 利用サービス(OpenRouter)の主力の鍵が残高切れになっていて、上流から 402 が返っていました。11:44 に予備の鍵へ切り替えて復旧し、同時に「なぜこんなに早く残高が減ったのか」を調べ始めました。
11:57 残高の不具合を調べるため本番のデータベースの記録を確かめていて、ある利用者が AI の「コード実行」の道具で、サーバーの設定(環境変数)を16進数で書き出させていたのを見つけました。データベース・メール送信・AI 各社などの鍵が含まれていました。
学び:発見のきっかけは、セキュリティの見張りではなくお金の異常でした。外部サービスの利用料・残高の急な減りは、鍵の悪用の最初の兆候になりえます。
2. 封じ込め(11:57〜12:51)
11:57 該当のアカウントを隔離しました(元に戻せる形で、別の表に移す)。ところが、そのアカウントの API キーが使われると、空のアカウントが自動で作り直される作りでした。11:58 に、API キー自体も無効にしました。
学び:アカウントを隔離しても、認証の入口(API キー・ログイン状態)が生きていることがあります。入口を直接止めたかを、必ず確かめてください。
12:01 原因の機能(AI がサーバー上でシェルを動かす道具など)を既定で使えなくする修正をマージしました(PR #943)。本番への反映は 12:23、本番の道具の一覧から消えたことを確かめたのは 12:51 です。マージから本番での確認まで、約50分かかりました。
12:50〜12:51 漏れた鍵のうち、データ暗号の鍵(利用者が預けた外部の鍵を暗号化するためのもの)を入れ替えました。先に「新しい鍵と古い鍵の両方で読める」仕組みを本番に出し(PR #945・#946)、次の順にしました。
- 新しい鍵を設定する
- 試し実行で、暗号化し直す件数を確かめる(12:50:11)
- 本番で暗号化し直す(12:50:20)
- 古い鍵を外す(12:50:27〜12:51:33)
管理用の鍵、クラウド(AWS)のバックアップ用の鍵なども入れ替えました。
学び
- 「マージした」と「本番で効いている」は別の出来事です。時刻を分けて記録し、本番で実際に確かめるまでは、穴は開いていると考えてください。
- 暗号の鍵の入れ替えは、「両方で読める期間」を作ってから行うと、データを失わずに済みます。試し実行(dry-run)を既定にした管理用の API を、平時に用意しておくと安全です。
3. 範囲の判明と、守りの強化(14:07〜15:00)
14:07〜14:46 次の修正を順にマージしました。
- ログイン情報を平文ではなくハッシュで保存する修正と、全員のログイン状態を消す管理用の API(PR #949)。最初のデプロイは外部の一時的なエラーで失敗し、やり直しました。
- パスワード再設定が本番のデータベースで動いていなかった不具合の修理(PR #950)。
- ファイルを読み書きする道具の停止(PR #951)。コード実行を止めても、ファイル操作の道具から同じ情報に届くおそれがあったためです。
14:21〜15:00 記録を詳しく調べるうちに、範囲が大きく広がりました。
- 攻撃は11月7日の1回ではなく、9月8日から続いていた。
- 9月27日に代表のアカウントが乗っ取られていた。攻撃者は漏れたデータベースの鍵で、偽のログイン情報(有効期限 2027年末)を直接書き込んでいた。
- サーバーの保存領域に、攻撃者が目印のファイルを置いていた。
- 11月7日の攻撃のスクリプトに、今も有効なデータベースの鍵がそのまま入っていた。
14:21・14:40 関わっていたアカウントを隔離し、11:57 の1個と合わせて12個にしました。代表アカウントの偽のログイン情報を含め、これらのログイン状態を消しました。置かれたファイルは証拠として保存し(ハッシュ値を記録)、サーバー上では隔離しました。
学び:最初に見つけたものが「全部」とは限りません。最初の1件を止めたら、すぐに同じ手口の痕跡を全期間で探してください。私たちは最初「11月7日の1件」と考え、お知らせの下書きを書き始めていました。
4. データベースの鍵の入れ替えと、1回目の停止(15:00〜17:10)
最優先は、攻撃者が今も持っているデータベースの鍵を無効にすることでした。
15:00〜16:48 データベースの管理サービスの CLI でログインしようとしましたが、ブラウザでの承認が約6回、時間切れになりました。このとき作業を手伝っていた AI は、「私たちの使い方(CLI の版 1.0.34)では、ログインが済んでいない状態でも、命令が終了コード0で終わる場合がある」ことに気づいていました。
16:48 それでも AI は、ログインの完了を検知したら入れ替えを自動で始める見張りを、CLI の終了コードだけで判定する形で作りました。16:49 見張りは誤って「ログインできた」と判定し、入れ替えを始めました。AI は運営者に「ログインできたので、入れ替えが始まりました」と報告し、16:54 には「古い鍵は無効にできました」と報告しました。どちらも事実ではありませんでした。
実際には、新しい鍵を作る命令の出力は「ログインしてください」というエラー文で、スクリプトはその文章(88文字)を新しい鍵として本番に入れていました。古い鍵は無効になっていませんでした。
16:49〜17:10 の21分間、teai 全体が止まりました。 サーバーは起動時にデータベースへ接続できず、起動に失敗し続けました。古い鍵は控えていなかったので、元に戻すこともできませんでした。
外の見張り(定期的に動作を確かめる仕組み)は 16:49 に異常を検知していました。しかし、知らせのメールは、メール送信サービスの手前の防御(Cloudflare)に、プログラムの既定の名乗り(Python の User-Agent)で弾かれていました(エラー 1010)。最初は送信元のアドレスの問題と考えて直しましたが(PR #955)、それでは直らず、本当の原因が分かったのは夜でした。
17:05 CLI をやめ、管理画面で発行したトークンで、データベースの管理サービスの API を直接呼ぶ形にスクリプトを作り直しました。新しい鍵は、実際にデータベースへ select 1 を投げて通ることを確かめてから本番に入れます。失敗したら、本番には入れずに止まります。
17:10 入れ替えが完了し、ページ・ログイン・データベースの読み書きが正常に戻りました。古い鍵をすべて無効にする操作は、管理サービスから成功の応答を受けています(古い鍵そのもので接続できないことは、手元に古い鍵がないため確かめていません)。
学び
- 道具の「成功」を信じない。成否は、出力の中身(鍵の形)と、実際に使えるか(接続の試験)で判断してください。CLI の終了コードは、使い方によっては、ログインが済んでいなくても 0 になることがあります。
- 分かっている落とし穴は、自動化の中で必ず塞ぐ。私たちの AI は落とし穴を知っていたのに、急いで作った自動化でそれを踏みました。AI に作業を任せるときは、「何をもって成功とするか」を作業の前に人が確かめてください。
- AI の「できました」は、外から確かめる。AI の報告は、本番の状態(応答のコード、実際の接続)で裏づけてから信じてください。
- 見張りの知らせ自体を、定期的に試す。知らせが届かない見張りは、ないのと同じです。
5. メール送信の鍵と、第三者への被害(17:26〜17:59)
17:26 外部サービスの監査を担当していた別の AI セッションから、第一報が届きました。漏れたメール送信の鍵で、当社のドメインを差出人にした詐欺メールが約3万通(11月7〜8日)送られていた、という内容です。この時点では、送信は11月8日夜に止まっていると見られていました。
17:29〜17:35 アプリごとに、送信だけができる新しい鍵を作り、26のアプリに配り始めました。
17:36 古い鍵36本をすべて削除しました。削除の直後に送信の記録を照会し、この日の16:48まで詐欺メールが送られていたこと、17:36 以降の送信はないことを確かめました。
17:36 前後に、運営者もメール送信サービスの管理画面で鍵を全部削除し、新しい全権限の鍵を作り直しました。このとき、AI が配った1回目の新しい鍵26本と、teai 本体の鍵もいっしょに消えました。17時36分ごろから17時41分ごろまでの約5分間、teai の認証コードのメールが送れなくなりました。 そこで、運営者が作り直した鍵を使い、アプリごとの送信専用の鍵を配り直しました(27のアプリ、GitHub、Cloudflare)。
17:45 までに、監査の集計で、詐欺メールは10月12日から約7万6千通だったと分かりました。
17:50 詐欺メールについて、イタリアの CERT-AgID、ベルギーの Safeonweb、pagoPA、Orange、メール送信の事業者に、メールで通報しました。
17:59 個人情報保護委員会に速報を提出しました。
学び
- 何でもできる強い鍵を、複数のサービスで共用しない。1本漏れただけで、全ドメインからの送信と、送信記録の読み取りができる状態でした。
- 第一報は小さく出る。17:26 の第一報は約3万通・止まっているというものでしたが、実際は約7万6千通で、送信は続いていました。範囲は広がる前提で、先に止めてください。
- 人と AI が、同じ管理画面・同じ鍵を同時に触らない。運営者と AI が別々に鍵を作り直し、互いの作業を消し合いました。鍵ごとに「今この人が作業中」と宣言し、終わるまで他は触らない、という決まりが必要でした。
6. 公開と、守りの強化(18:00〜19:41)
18:05 全員のウェブのログイン状態を解除しました。この時点で残っていたログイン状態は1件だけでした(14:07 の修正の反映で、それより前の形式のログイン情報は使えなくなっていたためとみられます)。解除のあと、0件になったことを確かめました。
18:32・18:34 不審な API キーを止めました。
- 9月に、大量登録のボットとして隔離していたアカウントに、無効化されないまま残っていたキー26本(9/19 以降は使われていません。今回の攻撃のアカウントとの関係は確認中です)
- 別のキーの名前の欄に、キーの値がそのまま書かれていたキー5本(利用者5人)
18:45 詐欺メールを受け取った方に向けた告知を、日本語・英語・イタリア語で公開しました。
18:50 データベースまで確かめる、より深い状態確認をマージしました(PR #957)。ところが本番では、設定の読み込みの誤りで、データベースが正常でも「異常」を返していました。19:11 から、外の見張りは失敗を出し続けています(この記録を公開した 11/9 22時台の時点でも修正中、PR #960)。
18:57 外部サービスの異常(利用料・権限エラー・決済の鍵)を、定期的に確かめる見張りを動かし始めました。19:03 までに、鍵が盗まれたら気づけるよう、おとりも置きました。
学び:新しい見張りは、本番で試してから警報につなぐ。深い状態確認は、本番で誤った「異常」を出し続け、見張り全体を信用できなくしました。
19:10 道具の出力から秘密を自動で消す仕組みと、サーバーの状態を返す道具を AI に渡さない変更をマージしました(PR #958)。19:41 に、本番の起動の記録で動いていることを確かめました。
7. 決済の鍵の入れ替えと、2回目の停止(18:39〜19:31)
18:39〜18:43 決済サービス(Stripe)の新しい秘密鍵を、決済の鍵を持つアプリ14と Worker 5つに配りました。このとき、2つの AI セッションが、同じ鍵の配布を同時に走らせていました。
19:00〜19:01 決済サービスの管理画面で、鍵が4本無効にされました。その中には、18:39 に配ったばかりの新しい鍵と、配布の対象から漏れていた10のサービスが使っていた鍵が含まれていました。決済サービスの操作の記録で、代表のアカウントで、当社の作業用の Mac から行われた操作だったことを確かめています。人の操作か AI の操作かは、特定できていません。
19:00〜19:31 ごろ、決済を扱う当社のアプリ25と Worker 5つで、カードでのお支払いが通らなくなりました。
19:03 18:57 に動かし始めたばかりの見張りが「決済の鍵が使えない(401)」を検知し、知らせのメールが届きました(この見張りは当社の手元の端末で動き、Gmail から知らせを送るため、16:49 に知らせが弾かれた外の見張りとは別の経路で届きました)。
19:24 決済サービスの管理画面で新しい鍵を用意し、teai を含むアプリ15と Worker 5つに配りました。19:29〜19:31 漏れていた10のサービスにも配り、それぞれで新しい鍵が使えることを確かめました。この時間帯に完了した決済は0件で、二重の請求や、支払い後の反映漏れはありませんでした。
学び
- 鍵を使っている所は、決まった一覧ではなく、実際の設定から洗い出す。私たちの配布スクリプトは決まった一覧だけを見ていて、10のサービスを取りこぼしていました。今は全アプリの設定を走査します。
- 無効にする前に、全配布先で新しい鍵が使えることを確かめる。今は、1か所でも確かめられなければ「無効にしてはいけません」で止まる確認用のスクリプトを使っています。スクリプト自身も、一覧の取得に失敗したら止まります(1回目の停止と同じ「失敗を成功と言う」穴を作らないため)。
- 鍵の作業は、担当を1人(1つのセッション)に限る。2つの AI セッションと人が、同じ鍵を同じ時間帯に触っていました。
8. 確認(19:33〜20:06)
19:33 偽の API キーの照合をしました。攻撃者はデータベースに書き込めたので、誰かのアカウントに、自分だけが値を知る API キーを紛れ込ませることができました。9月7日以降に作られた有効なキー956本を作成の記録(監査ログ)と照らし合わせ、記録のない4本を止めました。
ただし、監査ログも同じデータベースにあるので、記録ごと書き込まれたものは見つけられません。そこで、データベースの鍵を入れ替えた時点(17:10)より前からあるキーの一覧を記録し、11月21日に、一括で使えなくします。作成日時ではなく、キーの ID で止めます(作成日時も、攻撃者が書き換えることができたため)。
20:06 確認用のスクリプトで、決済の鍵を持つすべてのサーバーで、新しい鍵が使えることを確かめました(失敗0)。
学び
- 攻撃者がデータベースに書き込めた場合、データベースの中の記録だけでは、何が書き込まれたかを証明できません。外にある記録や、時点を区切った一括の入れ替えで対応してください。
- バックアップの保管庫の、読み出しの記録を残しておく。攻撃者はバックアップの保管庫に書き込めました。読み出しも権限上はできましたが、読み出しの記録を有効にしていなかったため、持ち出されたかを確かめられません。持ち出されたものとして扱っています。
- 隔離した攻撃者は、すぐ戻ってくる。隔離の後、隔離済みのものと同じ種類の使い捨てのメールサービスで、2回再登録されました(13:32 の分は 14:40 に、15:20 の分は 17:44 ごろに隔離し、合計13個)。登録の段階で弾く仕組みが必要です。
9. この日に間に合わなかったこと
公開した 11/9 22時台の時点の状態です。
- AI 各社などの残りの鍵の入れ替え:一部済(OpenRouter・Google の AI の鍵は入れ替えて古い鍵を削除)。ほかは作業中
- 置かれた仕組みが、ほかに残っていないかの確認と、マシンの作り直し:確認中
- データが実際にどこまで読まれたかの調査:調査中
- 旧方式のパスワードでのログインの停止:済(11/9 夜の本番反映。本番で、旧方式のアカウントのログインが 403 になること、再設定が1回だけ使えること、再設定後は新しい方式で保存されることを確かめました)
- 外の見張りの修正(深い状態確認の誤りと、知らせのメールの 1010):修正中(PR #960)
10. チェックリスト(エンジニアの方へ)
事故の前に
- ☐ AI が動かすコードは、鍵のない別のマシンで動かしている
- ☐ 道具の出力から、秘密を自動で消している
- ☐ 鍵はサービスごと・用途ごとに分け、必要な権限だけにしている
- ☐ ログイン情報・API キーは、ハッシュで保存している
- ☐ 外部サービスの利用料・権限エラーを見張り、知らせが本当に届くことを定期的に試している
- ☐ バックアップの保管庫の、読み出しの記録を有効にしている
- ☐ 鍵の入れ替えの手順(新しい鍵を試してから入れる、全配布先を設定から洗い出す、無効化の前に全配布先で確かめる)を、平時に一度動かしている
事故のとき
- ☐ 最初の1件を止めたら、すぐに全期間で同じ手口の痕跡を探す
- ☐ 認証の入口(API キー・ログイン状態)を直接止めたかを確かめる
- ☐ 第三者への被害が続いていれば、サービスの一時停止を受け入れてでも先に止める
- ☐ 道具や AI の「成功」を信じず、本番の状態で確かめる
- ☐ 「マージした」「反映した」「本番で確かめた」の時刻を分けて記録する
- ☐ 鍵ごとに作業者を1人(1つのセッション)に決め、宣言してから触る
- ☐ 時刻と操作を、その場で記録に残す(この記録は、それで書けました)
濱田優貴(株式会社イネブラ 代表取締役)
関連:お知らせ(お願い) / 代表のまとめ / 技術レポート / 停止のお詫び / 対応の記録 / 詐欺メールについて/お問い合わせ:teai.io/contact