「デバッグ用」のログが、最大のセキュリティホールになる
本番環境で原因不明のエラーが発生。何が起きているか分からず、焦った私はバックエンドのログイン処理にこう書き足しました。
app.post('/api/login', (req, res) => {
console.log('DEBUG: リクエスト内容 →', JSON.stringify(req.body));
// { "email": "user@example.com", "password": "MyS3cretP@ss!" }
// ...認証処理
});
原因が判明したらログを消そう。そう思っていました。
しかし、消し忘れました。
3ヶ月後、セキュリティ監査で指摘されて初めて気づきました。CloudWatch Logsには、3ヶ月分のユーザーのメールアドレスとパスワードの平文が、検索可能なテキストとして永久保存されているのです。
ログ保管期間は「無期限」に設定されていました。
ログはどこまで「見える」のか
「ログなんて自分しか見ないでしょ?」──これは危険な誤解です。
- ログ集約サービス(CloudWatch、Datadog、Splunk等)に送信されたログは、チームの複数のエンジニアや、場合によっては外部のSREベンダーもアクセス可能
- ログにクレジットカード番号やパスワードが含まれていた場合、PCI DSSやGDPR等の法的なコンプライアンス違反に直結する
- ログ基盤自体が侵害された場合、攻撃者はパスワードを復号する必要すらない(平文でそこにある)
ログに出力してはいけないデータ
❌ パスワード、パスワードリセットトークン
❌ セッションID、JWTトークン
❌ クレジットカード番号
❌ マイナンバー、住所、電話番号
❌ APIキー、シークレットキー
防御策
1. リクエストボディを丸ごとログに出さない
console.log(req.body) は、開発中でも本番でも禁止にすべきです。必要なフィールドだけ(email のみ等)を明示的に選択してログに出力しましょう。
// NG: 全フィールドをダンプ
console.log(req.body);
// OK: 必要なフィールドだけ
console.log({ email: req.body.email, timestamp: new Date() });
2. ロギングライブラリにフィルタリング(マスキング)を組み込む
パスワードやトークンのフィールド名を検知し、自動的に *** にマスキングする仕組みをログ基盤のレベルで実装します。
const sensitiveFields = ['password', 'token', 'secret', 'authorization'];
function sanitize(obj) {
const sanitized = { ...obj };
for (const key of Object.keys(sanitized)) {
if (sensitiveFields.some(f => key.toLowerCase().includes(f))) {
sanitized[key] = '***REDACTED***';
}
}
return sanitized;
}
3. ログの保管期間を設定する
CloudWatch Logsのデフォルト保管期間は「無期限」です。明示的に30日や90日に設定し、不要になったログは自動削除されるようにしましょう。
4. CIにログ出力チェックを入れる
console.log や print が本番コード内に残っていないかをLintルールや grep でCIパイプラインに組み込み、マージ前に自動検出します。
console.log は最強のデバッグツールであると同時に、最も手軽に情報を漏洩させる兵器でもあります。「デバッグ用だから大丈夫」──その油断が、ユーザーの個人情報を永久に刻印します。