ログに秘密のトークンが平文で残っていた:呼び忘れようがない唯一の出口で伏せる
トレカダッシュ 開発の記録 / 2026年8月
iOS アプリ「トレカダッシュ」の裏側のログを調べていて、外部サービスの秘密の値(API のトークン)が、そのまま書かれているのを見つけました。2026 年 8 月 24 日のことです。
宿題として「期限なし、任意」に置いていた件でした。サーバーの実際のログを確認すると、実際に漏れていました。この記事は、原因と、「呼び忘れようがない出口で、伏字にする」直し方の記録です。
この記事は、2026 年 8 月 24 日の作業の記録から書いています。本物の値は、書いていません。
ログに、本物のトークンが出ていた
サーバーのログに、こういう行がありました(トークンの部分は、本物でした)。
WARN [再検証] 取得サービス経由での取得に失敗 [https://…]:
request to https://api.scrape.do/?token=<本物のトークン>&url=… failed, reason: read ETIMEDOUT
原因は、通信の部品(node-fetch)のエラーメッセージに、リクエストの URL が、そのまま入ることでした。URL の中のtoken=の部分に、秘密の値が含まれています。
前にも、同じことをして、直したつもりだった
2026 年 8 月 19 日にも、楽天の API の認証情報で、同じことが起きていました。そのとき、ログの文字列から秘密の値を伏せる関数(redactSecretsForLog)を作りました。ところが、この関数は、明示的に呼んだ場所にしか、効きません。取得サービスを呼ぶ場所は、50 か所以上あり、呼び忘れていたのです。
直し方:すべてのログが通る、唯一の出口で、伏せる
同じ直し方を繰り返さないよう、2 段構えにしました。
- ログの出口(
fmt())で伏せる。サーバーの全部のログが、必ず通る、唯一の出口です。ここで伏せれば、呼び忘れようがありません。URL の中の、token、api_key、secret、applicationId、accessKey、affiliateId、setCookiesなどと、X の監視のログイン用クッキーの生の書き方の、両方を伏せます。 - 取得サービスを呼ぶ関数で、エラーを作り直す。呼び出し元に返す前に、伏せた文字列で、エラーを作り直します。関数が、データの変化で動く側(Cloud Functions のトリガー)でも使われるので、そちらのログにも効かせるためです。
実際のログの文字列 4 種で、伏せられることを確かめました。通常のログの行は、変化しないことも確かめました(deadline=2026-08-30 のような、無関係な = を、伏せてしまっては困ります)。
すでに出てしまったログは、どうするか
直しても、すでに出てしまったログには、本物のトークンが残っています。サーバーの管理者の権限でしか読めないので、緊急性は低いのですが、気になる場合は、サービスの管理画面でトークンを再発行して、サーバーの設定の値を差し替えます。この記録の時点では、これは、判断待ちの項目にしていました。
分かったこと
呼び忘れる場所が 50 あるなら、呼び忘れようがない場所で、1 度だけ行う。
- 明示的に呼ぶ対策は、呼び忘れで漏れる。50 か所のうち 1 か所でも忘れれば、漏れます。
- 唯一の出口で、伏せる。全部のログが通る場所に、1 つだけ置きます。
- 伏せすぎないことも、確かめる。通常のログが変化しないことを、試します。
- すでに出たものは、直しても消えない。再発行するかを、判断します。
- 外部の部品のエラーメッセージを、信じない。URL をそのまま含むことがあります。
この話に出てくるもの
- トレカダッシュ — トレカの抽選・予約・先着の情報を集める iPhone アプリ
- Cloud Functions から月 4,300 円のサーバーへ移した — ログが出る場所の話
- 自分で作った仕組みが、自分を傷つけた 4 つの記録 — 自分の道具の失敗
- 本物の抽選を、静かに捨てていた 4 つの実例 — 気づけない失敗
ほかの記録
- 失敗を捨てると、画面は嘘をつく:「○件登録しました」と出て 1 件も入っていなかったカレンダー、固まる文字認識、店名を積み忘れた通知 — try? で失敗を捨てていたため、カレンダーに 0 件なのに「登録しました」と出て二度と登録できなくなった。文字認識の失敗で画面が固まった。通知バッファに店名を積み忘れて「店名なし」になった。
- 候補は見つかっていたのに、答えを読む所で毎回落ちていた:200 トークンで切れた URL と、ハイフンで 400 になる検索 — 公式の告知ページを選ぶ AI の返事が 200 トークンで途中で切れて毎回失敗していた。直しても 0 件だったのは、店の告知が個別の商品名を書かないため、AI が指示に忠実に拒否していたから。楽天の検索は、ハイフンの位置で 400 になった。
- 「iCloud が新しければ取り込む」の関数が、何も比べていなかった:応募の記録が 20 件から 1 件になりうる — iCloud 同期の関数は、名前に反して新しいかを見ず、変更があると丸ごと置き換えていた。新しい端末の空の状態で保存すると、別の端末の 20 件が 1 件に上書きされうる。更新時刻を比べ、空では上書きしないようにした。
コメント
感想や、記事の間違いの指摘は、ここへ。書くには、GitHub のアカウントが要ります。
押すと、giscus(giscus.app)と GitHub に接続します。押さなければ、接続しません。詳しくはあつかいに書いています。