過剰なデータ露出は、APIがモデルオブジェクトをそのままJSONにして返すことで、password_digestやAPIトークンなど本来返す必要のない属性まで漏れてしまう脆弱性です。
対策の勘所は、レスポンスに含める属性をサーバ側で明示的に列挙できているかどうかです。
過剰なデータ露出とは
過剰なデータ露出(Excessive Data Exposure):APIのレスポンスに必要以上の属性を含めて返してしまい、機微な情報がクライアントに露出する脆弱性です。 モデルを丸ごとシリアライズし、表示する項目の絞り込みをクライアント任せにする設計が根本原因になります。
画面に表示されていなくても、レスポンス自体には全属性が載っています。
ブラウザの開発者ツールを開くだけで誰でも見えるため、「フロントで使っていないから安全」は成り立ちません。
password_digestが漏れればオフラインでのパスワード総当たりの材料になり、APIトークンやauth_keyの類が漏れればそのまま成りすましに直結します。
OWASP API Security Top 10では、2019年版でAPI3(Excessive Data Exposure)として挙げられていました。 2023年版では書き込み側の対になるマスアサインメントと統合され、API3のBOPLA(オブジェクトプロパティレベル認可の不備)に再編されています。 読み取りの露出がこの脆弱性、書き込みの混入がマスアサインメントで、「オブジェクトの属性単位でアクセス制御していない」という同じ根を持ちます。
過剰なデータ露出が起きるパターン
典型的な混入箇所は次のとおりです。
render json: @userのように、モデルオブジェクトをそのままレスポンスに渡している- 関連モデルを
includeで同梱した際に、関連側の機微な属性まで芋づるで露出している - 本人向けの詳細APIと他人も見る一覧APIで同じシリアライズを使い回し、他人にまで本人用の属性を返している
- 「全属性が既定で返る」設計のまま新しいカラム(内部メモ、フラグ、トークン)を追加し、追加した瞬間から自動的に露出している
- GraphQLや汎用検索APIで、クライアントが要求できるフィールドを制限していない
- エラーレスポンスに、例外オブジェクト経由でモデルの中身がそのまま載っている
共通するのは「返す属性を明示せず、モデルの全属性が既定で出る設計になっている」という点です。 内部ログへの機微情報の記録も情報漏洩につながりますが、API利用者へオブジェクトのプロパティを過剰に返すBOPLAとは別の問題です。
対策法
原則は「レスポンスに含める属性を、サーバ側の許可リストで明示する」ことです。
- シリアライザ層を設けて返す属性を列挙し、モデルの
as_json既定出力には頼らないことが本命の対策になる - 属性の追加が自動で公開につながらない設計にする(新カラムは、シリアライザに書き足さないかぎりレスポンスに出ない状態を保つ)
- 閲覧者によって返す属性を変える。本人と他人、管理者と一般で別のシリアライズを使い、プロパティ単位の認可として扱う
- OpenAPIスキーマやレスポンスのスナップショットテストで、意図しない属性の混入をCIで検知する
- そもそもDBに置く必要のないシークレットは環境変数や専用ストアに逃し、漏れたときの影響を小さくする(補助策)
クライアント側で項目を捨てるのは対策になりません。 本命はサーバ側での列挙であり、「返してよいものを足す」方式に統一するのが費用対効果の高い進め方になります。
Rails での事例
危険な実装は、モデルをそのままrender json:に渡す形です。
# 危険:User の全カラムが JSON になる
def show
@user = User.find(params[:id])
render json: @user
end
has_secure_passwordを使っている場合、このレスポンスにはpassword_digestがそのまま含まれます。
Rails 6以降のfilter_attributesはコンソールやログのinspect表示を伏せるだけで、as_jsonの出力には効きません。
また、Deviseはencrypted_passwordなど自身が管理するカラムをシリアライズから除外しますが、自前で追加したauth_keyやapi_tokenのようなカラムは守ってくれません。
「フレームワークがよしなに伏せてくれる」とは考えず、返す側で列挙するのが原則です。
最小の直し方は、only:で属性を明示することです。
render json: @user.as_json(only: %i[id name avatar_url])
ただしonly:の指定がコントローラごとに散らばると、書き漏れが穴になります。
返す形を一箇所に集めるシリアライザ層を設けるほうが漏れにくくなります。
class UserSerializer
def self.render(user)
{ id: user.id, name: user.name, avatar_url: user.avatar_url }
end
end
render json: UserSerializer.render(@user)
見落とされやすいのが、関連モデルの同梱です。
# 危険:Post に紐づく User の全カラムまで JSON に載る
render json: @post.as_json(include: :user)
include側にもonly:を指定するか、シリアライザで組み立てて、関連の属性も列挙式にします。
本人と他人で返す内容を変えたい場合も、条件分岐で属性を削るのではなく、シリアライズを分けます。
def show
@user = User.find(params[:id])
json = @user == current_user ? UserSerializer.render_private(@user) : UserSerializer.render_public(@user)
render json: json
end
既存コードの点検は、render json:やto_jsonにモデルやリレーションを直接渡している箇所をgrepで洗い出すところから始めます。
as_json(only: ...)の指定がない出力を見つけたら、そこが候補です。
まとめ
過剰なデータ露出対策の要点を整理します。
- 根本原因は「返す属性を明示せず、モデルの全属性が既定で出る設計」。画面に表示していなくてもレスポンスには載っており、開発者ツールを開けば誰でも読める
- 本命の対策はシリアライザ層での列挙。
render json: @userのようにモデルを直接渡さず、返す属性を書き出す方式に統一する - 属性の追加が自動で公開につながらない状態を保つ。新しいカラムは、シリアライザに書き足さないかぎりレスポンスに出ない設計にする
- 閲覧者によって返す属性を変える。本人と他人、管理者と一般で別のシリアライズを使い、プロパティ単位の認可として扱う
- フレームワークの保護を過信しない。Railsの
filter_attributesはログやinspect表示を伏せるだけでas_jsonには効かず、Deviseも自前で足したapi_tokenのようなカラムは守らない
既存コードの点検は、render json:やto_jsonにモデルやリレーションを直接渡している箇所のgrepから始まります。
関連モデルをincludeで同梱している箇所は、芋づるで露出しやすいので優先して見てください。
関連して読める記事
読み取り側の露出がこの脆弱性、書き込み側の混入がマスアサインメントで、「オブジェクトの属性単位でアクセス制御していない」という同じ根を持ちます。
他人のレコードそのものを参照できてしまう場合はIDORの問題になり、レスポンスに載ったapi_tokenやauth_keyが持ち出されれば、シークレット漏洩として被害が確定します。

マスアサインメントとは?起きるパターンと対策法
マスアサインメントの仕組みと起きるパターン、Strong Parameters による許可リスト化を軸にした対策を整理します。permit! で全属性を許可する形と、JSON.parse が返す素の Hash が Strong Parameters の保護対象外になる見落としがちな経路、role のような内部属性を許可リストから外す設計を具体的なコードで示します。
2026年7月25日
IDOR(安全でない直接オブジェクト参照)とは?起きるパターンと対策法
IDOR(安全でない直接オブジェクト参照)の仕組みと起きるパターン、所有者チェックを軸にした対策を整理します。RailsのOrder.find(params[:id])がそのまま穴になる理由と、current_user起点の取得への直し方を具体的なコード例で示します。OWASP API Security Top 10で1位のBOLAに相当する脆弱性です。
2026年7月23日
APIキーとシークレットの漏洩とは?起きるパターンと対策法
APIキーとシークレットが漏洩する三大経路(Gitへのコミット、フロントのバンドル埋め込み、公開ストレージ)と対策を整理します。VITE_ や NEXT_PUBLIC_ の環境変数がビルド成果物に埋め込まれるという誤解、自前バックエンド経由への切り替え、Rails credentials と filter_parameters の設定を具体的なコードで示します。
2026年7月25日