「管理手順から外れて誤ってGitHubに保管」— マネーフォワード第二報から考える、本番データ混入を仕組みで止める方法
はじめに
2026 年 5 月 3 日、マネーフォワードが GitHub 不正アクセス事案について第二報を公開しました。第一報(5 月 1 日)の段階では「ソースコードと一部の個人情報が流出した」という事実関係までしか開示されていませんでしたが、第二報では「なぜ GitHub に個人情報が含まれていたか」という、エンジニアにとっていちばん知りたかった部分に踏み込んだ説明が加わっています。
その記述がこちらです。
通常、GitHub 上に保存するソースコードに個人情報の入力はございません。しかしながら、個人情報の取り扱いを伴うサービスの更新作業を行う過程で、個人情報が含まれたファイルが本来の管理手順から外れ誤って GitHub 上に保管されていたことが、下記プレスリリース記載の事象の原因でございます。
— 株式会社マネーフォワード(2026 年 5 月 3 日 13 時 00 分追記)
短い文章ですが、エンジニア視点で読むと「ああ、あれだな」と頭に浮かぶシナリオがいくつか出てきます。本記事では、この一文から読み取れる「正規ルートを通らないまま個人情報がリポジトリに混入する」パターンと、それを「人の注意」ではなく「仕組み」で止めるための設計を整理します。
なお、第一報を含む事案全体の解説は別記事で書いていますので、攻撃経路や認証キーのハードコード問題などはそちらをご参照ください。本記事は 「個人情報が混ざったファイルがどうやって紛れ込んだのか」 という運用観点に絞ります。
第二報の一文を分解する
公式の文章を、エンジニアが読み取りやすい形に分解してみます。
| 公式の表現 | 読み取れること |
|---|---|
| 通常、ソースコードに個人情報の入力はございません | 平常時のリポジトリには個人情報は入らない設計だった |
| サービスの更新作業を行う過程で | リリース・移行・改修などのイレギュラーな作業中に発生 |
| 個人情報が含まれたファイル | CSV / SQL ダンプ / ログのような実データ系のファイル |
| 本来の管理手順から外れ | 手順自体は決まっていたが、それを通さずに作業が進んだ |
| 誤って GitHub 上に保管されていた | 故意ではなく、意図せずリポジトリに残ってしまった |
ポイントは「手順自体は決まっていた」「その手順を通さずに作業が進んだ」という構造です。逆に言えば、フローが整備されていない組織で起きた事故ではなく、フローはあったが守りきれなかった事案、というのが公式の主張です。これは多くの開発組織にとって他人事ではない構図です。
ありがちな「手順を通らずに混入する」三パターン
「更新作業の過程で個人情報ファイルが混入する」という現象は、現場で起きるとだいたい以下のいずれかのパターンに収束します。いずれも本人は手順を破っているつもりがなく、「ちょっとした近道」のつもりで起きるのが特徴です。
パターン 1: デバッグ・障害調査のために本番データを手元に落とす
サービスの更新作業に伴う動作確認や、リリース後に発生したエラーの調査で、本番 DB のスナップショットや、特定ユーザーのレコードを CSV で抜き出してくることがあります。
- 「ステージング環境では再現しないバグなので本番データで試す」
- 「マイグレーション結果の検証のため、移行前後の差分を CSV で出力した」
- 「インシデント対応で対象ユーザーの一覧を
SELECT ... INTO OUTFILEで吐かせた」
意図としては正当で、現場では珍しくありません。問題は その作業ディレクトリがリポジトリの中だった という一点です。多くの開発者が ~/work/myrepo/ のような場所で作業しているため、tmp/ や scripts/migration/ のサブディレクトリに置いた CSV が、後の git add . で巻き込まれる事故が発生します。
パターン 2: マイグレーション・データパッチのスクリプト化
「特定の条件にマッチするレコードに対してこの処理を流す」というデータパッチスクリプトを書く際、対象ユーザーのリストを SQL 内に直接埋め込んでしまうケースです。
-- ❌ 本番ユーザーの ID をハードコード
UPDATE users
SET status = 'migrated'
WHERE id IN (10342, 10891, 11023, ...);
このスクリプトをリポジトリにコミットすると、ID リスト自体は個人情報ではなくても、同じファイル内のコメントに「対象顧客一覧」として氏名やメールアドレスが書かれていることがあります。「あとで消す」と思いながら消し忘れる、というのも本当によくあるパターンです。
パターン 3: テストフィクスチャ/E2E シードに本番由来データを流用
「ダミーデータでは再現できない仕様」をテストするため、本番の一部レコードを fixtures/ 配下に置いてしまうケース。サービスが大きく成熟してくるほど、合成データでは表現しきれないエッジケース(特定文字種、長すぎる氏名、外字、絵文字を含むレコードなど)が増え、本番データを使いたくなる動機が積み上がっていきます。
これは ライクアカデミー「ナナポケ」事件(2020 年)のように、過去にも複数の事故が起きている王道パターンです。同事件では、開発委託先のテスト環境に園児・保護者の個人情報が匿名化不十分な状態で置かれており、外部アクセス可能だったため流出に至りました。「テストだから安全」が成立しないのは、業界として何度も学ばされてきた話です。
なぜ「気づかない」のか
混入してしまうこと自体は、上記のように動線がいくつもあるので避けられない瞬間が生まれます。問題は 混入してから流出するまでに気づけない ことの方です。理由を技術的に分解するとこうなります。
理由 1: シークレットスキャンは個人情報を見ない
GitHub の標準機能である Secret Scanning や、定番の gitleaks、TruffleHog といったシークレットスキャンツールは、API キー・トークン・パスワードのような 構造化されたシークレット を検出します。アクセスキーには AKIA で始まる、というような明確なパターンがあるからです。
一方、氏名・住所・カード番号下 4 桁のような業務系の個人情報は、形式に決まりがありません。「山田太郎」は氏名であり同時にテストデータでも使われる文字列です。「1234」は下 4 桁でもあり単なる数字でもあります。シークレットスキャナーの守備範囲外 で、ここをすり抜ける混入は技術的に検出が難しい領域です。
理由 2: PR レビューで全行を見るのは現実的でない
サービス更新の PR には、コード変更に加えてマイグレーションファイルやドキュメント修正、設定変更など、雑多なファイルが含まれます。レビュアーがすべての差分行を読み込むのは現実には難しく、特に CSV や SQL ダンプのような 大きなテキストファイル は、変更行数が膨大なためレビューツール側で「Large diff」として畳まれていることが多いです。レビューが甘くなる構造が初期設定で組み込まれています。
理由 3: プライベートリポジトリの安心感
「外には公開していないから多少の個人情報が残っても被害は限定的」という暗黙の前提が、社内に染み付いている組織は少なくありません。今回のマネーフォワード事案は、まさにその前提が崩れた瞬間です。GitHub の認証情報が漏えいすれば、private 設定はなんの守りにもなりません。プライベートリポジトリは「公開していない」だけであって、「絶対に外に出ない」ことを保証する仕組みではない、という事実が改めて突き付けられた格好です。
仕組みで止める:4 層の防御を組む
人為ミスは必ず起きます。だからこそ「人が間違えても止まる」レイヤーを複数重ねるのが、現代的なアプローチです。本番データの混入については、以下の 4 層を組み合わせるのが現実的です。
第 1 層: そもそも本番データを開発側に渡さない
最強の防御は「持ち込ませない」です。ステージング DB や開発 DB にデータを供給する経路で、必ずマスキングを通すように設計します。
- 本番 → 開発のレプリケーションパイプラインに 動的マスキング を挟む
- ダンプ取得スクリプトに、個人情報カラムを置換する
UPDATEを必ずパイプ - PostgreSQL Anonymizer や Greenmask のような OSS の活用
GDPR Article 5(1)(b) の「目的外利用の禁止」の観点でも、本番 PII を開発に流用すること自体に法的リスクがあります。仕組みとしてマスキングを必須化する設計は、コンプライアンス的にも合理的です。
第 2 層: pre-commit でファイル種別とパターンを止める
仮に手元に本番データのコピーが存在しても、「リポジトリにコミットさせない」段階で止めます。pre-commit フックの典型例はこんな感じです。
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.0
hooks:
- id: gitleaks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: check-added-large-files
args: ["--maxkb=500"]
- repo: local
hooks:
- id: block-data-files
name: Block CSV/SQL/dump files
entry: bash -c 'git diff --cached --name-only | grep -E "\.(csv|sql|dump|sqlite|sqlite3)$" && exit 1 || exit 0'
language: system
ポイントは以下の三つです。
-
拡張子ブロック:
.csv.sql.dump等を原則コミット禁止に。必要があれば個別に--no-verifyではなく allowlist で例外管理 - 大きすぎるファイルを止める: 500KB を超えるテキストファイルは、まず疑う
- gitleaks: 認証情報の混入も同時に検査
加えて、日本固有の PII(マイナンバー、電話番号、郵便番号)を検出したい場合は、gitleaks のカスタムルールを追加するのが手軽です。.gitleaks.toml に独自の正規表現を書き足せば、標準ルールと一緒に走ってくれます。
# .gitleaks.toml
[extend]
useDefault = true
[[rules]]
id = "jp-mynumber"
description = "Japan My Number (12 digits)"
regex = '''(?<![0-9])[0-9]{12}(?![0-9])'''
[[rules]]
id = "jp-phone-mobile"
description = "Japan mobile phone number"
regex = '''0[789]0-?[0-9]{4}-?[0-9]{4}'''
[[rules]]
id = "jp-postal"
description = "Japan postal code with 〒"
regex = '''〒\s*[0-9]{3}-?[0-9]{4}'''
過去に同種の用途では uktrade/pii-secret-check-hooks という pre-commit が使われていましたが、現在はリポジトリがアーカイブされており新規メンテはありません。日本の業務で使う PII パターンは欧米向けツールでカバーしきれないことも多いので、自社のドメインに合わせて gitleaks のカスタムルールを少しずつ育てる運用が現実的です。
第 3 層: CI でリポジトリ全体を再スキャン
pre-commit はあくまでローカルでの最初の防衛線で、git commit --no-verify で簡単にバイパスできます。CI 側で同じスキャンを再実行する二重化が必要です。
# .github/workflows/secret-scan.yml
name: secret-scan
on: [pull_request]
jobs:
gitleaks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
加えて、GitHub の Secret Scanning と Push Protection を有効化しておくと、push 時点でサーバ側が蹴ってくれるので、ローカル設定が崩れているメンバーがいても最後の砦になります。
第 4 層: 過去履歴の定期スイープ
第 1〜3 層を導入しても、それ以前にコミットされた履歴は残ったまま です。Git 履歴は持ち去られると過去にコミットされた .env や CSV まで丸ごと盗まれるため、定期的な棚卸しが必要です。
# 履歴全体を gitleaks でスキャン
gitleaks detect --source . --log-opts="--all"
# TruffleHog で履歴を検証付きスキャン
trufflehog git file://. --only-verified
ヒットした場合の対応順序は重要です。
- まず 鍵の rotate / データの無効化 を最優先で実施
- その上で必要に応じて git-filter-repo で履歴から削除
- 共同作業者に再クローンを促す
順序を間違えると、攻撃者がすでにコピー済みのリポジトリで漏えいを継続します。「履歴を消す」のは「もう外に出ていないと断言できる」場合に限定すべき作業です。
「本来の管理手順」が機能するために必要なもの
第二報は「本来の管理手順から外れ」という言葉を使っていますが、エンジニア視点で見たとき、人が守るしかない手順は必ずどこかで飛ばされるのが前提 です。マネーフォワードに限らず、どんな組織でも以下のような瞬間に手順は破られます。
- 障害対応で時間がない
- 期限の決まったリリース直前
- 委託先・新人など、手順に習熟していない作業者
- 普段とは異なる例外的な作業
- 手順書が長く読まれていない
これらすべてのケースを、手順遵守の徹底 で解決しようとすると無理が出ます。「人は手順を守れない瞬間が必ずある」という前提に立ち、その瞬間に技術的なゲートが自動で発動するように設計する。これが「仕組みで止める」の本質です。
たとえば、
- pre-commit が
.csvをブロックする → 「テストデータだから例外で」と説得しても物理的にコミットできない - CI が gitleaks で停止する → 強行マージしようにも main にマージできない
- データの取得経路にマスキング層がある → そもそも個人情報の生データを手元に持てない
このように、「手順を飛ばしたくても物理的にできない」状態を作るのがゴールです。規律ではなく構造で守る という発想に切り替えると、属人性に頼らない安全運用が可能になります。
エンジニアが今日からできるチェック
長くなったので、すぐに使えるチェックリストにまとめておきます。
-
自分のリポジトリで、過去半年に追加された
.csv.sql.dumpファイルを棚卸ししたか -
fixtures/seed/tmp/配下に本番由来のデータが残っていないか確認したか - pre-commit で gitleaks と大きなファイルブロックを設定しているか
- CI で同じスキャンを再実行しているか
- GitHub の Secret Scanning と Push Protection を有効化しているか
- 本番 DB から開発環境へのコピー経路にマスキング処理が挟まっているか
- 過去 1 年以内に、リポジトリ全履歴をスキャンしたか
-
チームに「
.csvをリポジトリにコミットしたい」という相談が来たときの正規ルートが定義されているか
最後の項目が意外と重要です。正規の逃げ道が用意されていないと、人はかならず非正規の道に逃げます。「本番データを使いたい」というニーズ自体は否定せず、合成データジェネレータ(Faker など)の活用や、マスキング済みデータの取得 API といった代替経路を整備しておくのが運用設計のポイントです。
まとめ
第二報の一文「個人情報の取り扱いを伴うサービスの更新作業を行う過程で、個人情報が含まれたファイルが本来の管理手順から外れ誤って GitHub 上に保管されていた」を、エンジニアの目線で読み解くと、こうなります。
- 平常時の手順は存在したが、更新作業という非定常タイミングで「手順を通さない近道」が選ばれてしまった
- 混入動線は「デバッグ用の本番データ持ち込み」「マイグレーションへの埋め込み」「テストフィクスチャ流用」のいずれかが濃厚
- 個人情報はシークレットスキャナーで検出しづらく、PR レビューもすり抜けやすい
- プライベートリポジトリは「公開していない」だけであって、流出時の防壁にはならない
- 「人の注意」ではなく、4 層(マスキング・pre-commit・CI・履歴監査)の自動ゲートで仕組み化するのが現代的な対応
本番データを開発に持ち込みたくなる瞬間は、どんな組織でも必ず訪れます。そのときに「手順を飛ばしたくても物理的にできない」状態を作っておけたかどうかが、自分の組織で同じ事故が起きるかを分けます。マネーフォワードの第二報を読み終えたら、自分のリポジトリの .gitignore と pre-commit を一度棚卸ししてみるのがよいと思います。
参考記事・データ
- 『GitHub』への不正アクセス発生に関するお知らせとお詫び(第一報)|株式会社マネーフォワード
- マネーフォワードの GitHub 不正アクセス事件をエンジニア視点で読み解く — なぜソースコードに本番カード情報と認証キーが入っていたのか(本サイト関連記事)
- マネーフォワード GitHub 不正アクセス事案 2026 年 5 月公表の全体像|株式会社一創
- マネーフォワード、GitHub への不正アクセスでビジネスカード情報 370 件・ソースコード流出の可能性|セキュリティ対策 Lab
- 開発委託先テスト環境に不正アクセス、園児・保護者の個人情報流出の可能性(ライクアカデミー)|ScanNetSecurity
- 本番データをテストに使ってはいけない|Fox on Security
- 「本番データでシステム開発」に潜む個人情報漏洩の危険性|ITmedia エンタープライズ
- Data Masking for Testing Environments: Privacy-Safe Testing Guide (2026)|Total Shift Left
- Data Masking Best Practices: In-Place vs In-Flight [2026]|Synthesized
- PostgreSQL Anonymizer Documentation
- Greenmask - Open Source Data Anonymization Tool
- gitleaks/gitleaks|GitHub
- trufflesecurity/trufflehog|GitHub
- uktrade/pii-secret-check-hooks|GitHub(リポジトリはアーカイブ済み)
- privateai/pai-pre-commit-hook|GitHub
- About secret scanning|GitHub Docs
- Art. 5 GDPR — Principles relating to processing of personal data
Discussion