IT女子 アラ美お疲れ様です!IT業界で働くアライグマです!
「MVPだからセキュリティは後で」「まずは動くものを出すのが最優先」。個人開発でこう判断した経験、ありませんか?
実は先日、自分が開発した技術記事キュレーションサービス「DevPick」で、まさにこの判断が致命的なセキュリティ事故を引き起こしました。この記事では、何が起きて、なぜ起きて、どう修正したかを包み隠さず共有します。
個人開発サービスでセキュリティ事故が起きた背景



DevPickは、Zenn・Qiita・Hacker Newsなど10ソースからAIが技術記事をスコアリングし、ユーザーの興味に合った記事だけを毎日届けるサービスです。バックエンドはFastAPI + MySQL、フロントエンドはNext.jsで構成しています。
ユーザーがプロフィール(名前・職種・習熟度・興味ジャンル)を登録すると、AIがそれに基づいて記事のスコアリングを行います。つまり、プロフィール情報はサービスの根幹であり、本来は厳重に保護すべきデータでした。
しかし、MVPリリースの際に「認証は後で入れる。まずは動くものを出す」と判断しました。これが事故の始まりです。AI活用サービスの開発についてはGAS×Claude APIでSlack自動投稿する方法でも触れていますが、機能開発に集中するあまりセキュリティが後回しになるのは個人開発あるあるです。



何が起きたか:全ユーザーのプロフィールが丸見え
リリース直後、致命的な問題が2つ見つかりました。
問題1:ユーザー一覧APIが丸見え
GET /api/users/ を叩くと、登録済みの全ユーザーのプロフィールがリストで返ってくる状態でした。
[
{"id": 1, "name": "あらいぐま", "role": "backend_engineer", ...},
{"id": 2, "name": "YSK", "role": "fullstack_engineer", ...}
]
認証なし、レート制限なし。誰でもアクセス可能でした。
問題2:IDを推測すれば他人のプロフィールを操作できる
GET /api/users/2 で他人のプロフィール詳細が取得できるだけでなく、PUT /api/users/2 で他人のプロフィールを書き換えることすらできました。
フィード、いいね、既読もすべて user_id をクエリパラメータで渡すだけで、本人確認は一切なし。LLMアプリケーションのセキュリティについてはOWASP Top 10ガードレールガイドでも解説していますが、APIの認証不備はOWASP Top 10の1位に挙げられる最も基本的な脆弱性です。



なぜ起きたか:「MVPだから後で」の罠
認証なし ≠ アクセス制御なし
最初の設計で「MVPでは認証を入れない」と決めました。理由は「早くリリースしたいから」。
これ自体は個人開発ではよくある判断です。しかし、認証を入れないことと、他人の情報が見えることは別の問題です。認証がなくても、最低限以下は必要でした。
- ユーザー一覧APIを作らない(または削除する)
- プロフィール作成時にトークンを発行し、以降の操作でトークンを要求する
使わなくなったAPIが残っていた
開発初期に「マルチユーザー切り替え」機能を作り、ドロップダウンでユーザーを選択するために一覧取得APIを実装しました。その後、MVP向けにマルチユーザー機能を削除したのですが、APIだけ消し忘れた。フロントエンドからは呼ばなくなったので気づかず、そのまま本番に出してしまいました。
脆弱性の自動検出についてはセキュリティテスト実践ガイドで解説していますが、使われなくなったエンドポイントの検出もテストに含めるべきでした。



どう修正したか:3ステップの緊急対応
Step 1:ユーザー一覧APIの即時削除
最も危険な一覧APIを即座に削除しました。
# 削除: @router.get("/", response_model=list[UserProfileResponse])
# この API は存在してはいけなかった
Step 2:トークン認証の導入
プロフィール作成時にランダムなトークンを発行し、以降のAPI操作では X-DevPick-Token ヘッダーでの認証を必須にしました。
@router.post("/", status_code=201)
def create_user_profile(payload: UserProfileCreate, db):
profile = UserProfile(
token=secrets.token_hex(32),
name=payload.name,
role=payload.role,
)
取得・更新・削除にはトークンが必須です。secrets.compare_digest でタイミング攻撃を防止しています。
@router.get("/{user_id}")
def get_user_profile(user_id, x_devpick_token: str = Header(...), db):
profile = db.query(UserProfile).filter(UserProfile.id == user_id).first()
if not secrets.compare_digest(profile.token, x_devpick_token):
raise HTTPException(status_code=403, detail="Invalid token")
return profile
Step 3:DBマイグレーション
本番のMySQLに token カラムを追加し、既存ユーザーにもトークンを発行しました。FastAPIの本番運用についてはFastAPI本番運用ガイドでも解説しています。
ALTER TABLE user_profiles ADD COLUMN token VARCHAR(64);
UPDATE user_profiles SET token = MD5(RAND()) WHERE token IS NULL;
ALTER TABLE user_profiles MODIFY token VARCHAR(64) NOT NULL;
CREATE UNIQUE INDEX ix_user_profiles_token ON user_profiles(token);



ケーススタディ:修正前後のセキュリティ比較



ここでは、山田さん(仮名・29歳・バックエンドエンジニア・経験4年)が個人開発サービスで遭遇したセキュリティ事故の修正前後を数値で比較します。
状況(Before)
- 脆弱なエンドポイント数:8箇所(全APIが認証なし)
- 外部アクセス可能なユーザーデータ:全件(名前、職種、興味ジャンル)
- 他人のデータを変更可能か:はい(PUT /api/users/{id} で書き換え可能)
- 心境:「リリースできた嬉しさで、セキュリティのことを完全に忘れていた」
行動(Action)
- 不要APIの削除:ユーザー一覧APIを即座に削除(作業時間5分)
- トークン認証の実装:全APIエンドポイントにトークン認証を追加(作業時間3時間)
- DBマイグレーション:既存ユーザーへのトークン発行+スキーマ変更(作業時間30分)
- curlでの全エンドポイントテスト:認証なしでアクセスできないことを全件確認(作業時間15分)
結果(After)
- 脆弱なエンドポイント数:8箇所 → 0箇所
- 外部アクセス可能なユーザーデータ:全件 → 0件(トークンなしではアクセス不可)
- 修正にかかった総工数:約4時間(最初から入れていれば1時間で済んだ)
振り返ると、「不要APIの削除とトークン認証は、MVPでも初日から入れるべきだった。4時間の作業を後回しにしたことで、リリース後の冷や汗を味わうことになった」。入力値の検証もセキュリティの重要な一環です。Pydantic v2の実装パターンも参考にしてください。



MVPでも最初からやるべきセキュリティ対策チェックリスト
この事故から得た教訓を、チェックリスト形式でまとめます。
リリース前に確認すべき3つのポイント
- 不要なAPIエンドポイントが残っていないか:開発中に作ったが本番では使わないAPIは全て削除する。フロントから呼ばないAPIも、直接叩けば動く
- 他人のデータにアクセスできない設計になっているか:認証を入れなくても、トークンやセッションで「自分のデータだけ操作できる」仕組みは入れられる
- curlで全エンドポイントを叩いたか:ブラウザの画面テストだけでは不十分。5分で終わる作業が、信頼の損失を防ぐ
認証を入れない場合の最低限の設計
- プロフィール作成時にランダムトークンを発行し、localStorageに保存
- 以降のAPI操作ではトークンをヘッダーに付与
- ユーザー一覧APIは作らない(管理画面用にどうしても必要なら、別のパスに分離してBasic認証をかける)
セキュリティ設計のスキルは転職市場でも高く評価されます。社内SE転職エージェント3社比較で紹介しているような社内SEポジションでは、自社アプリのセキュリティ設計を主導する裁量を持てます。



よくある質問
MVPでOAuth2やJWTを実装すべきですか?
MVPの段階ではオーバーキルになることが多いです。まずはランダムトークン + ヘッダー認証で十分です。ユーザー数が増えてきたら、OAuth2やJWTへの移行を検討しましょう。
localStorageにトークンを保存して安全ですか?
XSS攻撃のリスクはありますが、個人開発のMVPレベルでは現実的な選択肢です。本格運用する場合はHttpOnly Cookieへの移行を推奨します。CSRFトークンとの組み合わせも検討してください。
この事故でユーザーに被害はありましたか?
幸い、リリース直後に気づいて即座に修正したため、実被害は確認されていません。ただし「被害がなかった」のは運が良かっただけで、対策が不十分だった事実は変わりません。
本記事で解説したようなAI技術を、基礎から体系的に身につけたい方は、以下のスクールも検討してみてください。
| 比較項目 | Winスクール | Aidemy Premium |
|---|---|---|
| 目的・ゴール | 資格取得・スキルアップ初心者〜社会人向け | エンジニア転身・E資格Python/AI開発 |
| 難易度 | 個人レッスン形式 | コード記述あり |
| 補助金・給付金 | 教育訓練給付金対象 | 教育訓練給付金対象 |
| おすすめ度 | 幅広くITスキルを学ぶなら | AIエンジニアになるなら |
| 公式サイト | 詳細を見る | − |



まとめ
個人開発のMVPだからといって、セキュリティを後回しにしてはいけません。
- 認証なし ≠ アクセス制御なし。認証を入れなくても、他人のデータにアクセスできない設計は必須
- 使わなくなったAPIは必ず削除。フロントから呼ばなくなったエンドポイントも直接叩ける
- リリース前にcurlで全エンドポイントを叩け。5分で終わる作業が信頼の損失を防ぐ
- 最初から入れていれば1時間で済む対策を後回しにすると、4時間の修正工数と冷や汗がかかる
DevPickは現在トークン認証を導入済みです。興味があればぜひ使ってみてください。













