mcpField
E
error-responses

error-responses

github.com/yanko-belov開発1336 回インストール40 週間前に更新
リポジトリ

error-responses について

API のエラー処理に関するコーディングスキルです。内部情報をクライアントへのレスポンスに含めず、一貫した構造化エラーを返す方法を示します。

主な機能

  1. 01構造化エラーレスポンス
  2. 02機械可読なエラーコード
  3. 03HTTP ステータスコードの指針
  4. 04リクエスト ID によるログ照会

活用シーン

  1. 01API エラー処理の実装
  2. 02コントローラーの例外レスポンス確認
  3. 03クライアントに返す生エラーの置き換え

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/yanko-belov/code-craft --skill error-responses

インストール後、Claude Code で/error-responsesを入力して読み込まれたか確認します。

詳細

エラーレスポンス

概要

内部エラーを決して公開しないでください。構造化された安全なエラーレスポンスを返してください。

生のエラーメッセージは実装の詳細を漏らし、攻撃者を助け、ユーザーを混乱させます。エラーは安全で、一貫性があり、対処につながるものである必要があります。

使用する場面

  • API のエラーハンドリングを実装するとき
  • クライアントにエラーレスポンスを返すとき
  • コントローラーで例外を捕捉するとき
  • 「とにかくエラーメッセージを返して」と頼まれたとき

絶対に守るルール

NEVER expose raw error messages or stack traces to clients.

例外はありません:

  • 「デバッグに役立つ」から
  • 「内部 API だけ」だから
  • 「開発中だから」
  • 「フロントエンドに詳細が必要」だから

検知:漏えいの兆候

エラーから内部情報が漏れる場合は、作業を止めてください:

// ❌ VIOLATION: Exposing internals
app.get('/users/:id', async (req, res) => {
  try {
    const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
    res.json(user);
  } catch (error) {
    res.status(500).json({ message: error.message });  // Leaks!
  }
});

漏れる可能性がある情報:

  • "relation \"users\" does not exist" - データベーススキーマ
  • "connect ECONNREFUSED 10.0.1.5:5432" - 内部 IP アドレス
  • ファイルパスを含むスタックトレース
  • テーブル名を含む SQL クエリ

正しいパターン:安全なエラーレスポンス

// ✅ CORRECT: Structured, safe errors

// Custom error classes
class AppError extends Error {
  constructor(
    public statusCode: number,
    public code: string,
    message: string
  ) {
    super(message);
  }
}

class NotFoundError extends AppError {
  constructor(resource: string) {
    super(404, 'NOT_FOUND', `${resource} not found`);
  }
}

class ValidationError extends AppError {
  constructor(public details: Record<string, string[]>) {
    super(400, 'VALIDATION_ERROR', 'Validation failed');
  }
}

// Error handler middleware
app.use((error: Error, req: Request, res: Response, next: NextFunction) => {
  // Log full error internally
  console.error('Error:', {
    message: error.message,
    stack: error.stack,
    path: req.path,
    method: req.method,
  });
  
  // Return safe response
  if (error instanceof ValidationError) {
    return res.status(400).json({
      error: {
        code: error.code,
        message: error.message,
        details: error.details,
      }
    });
  }
  
  if (error instanceof AppError) {
    return res.status(error.statusCode).json({
      error: {
        code: error.code,
        message: error.message,
      }
    });
  }
  
  // Unknown errors - never expose
  res.status(500).json({
    error: {
      code: 'INTERNAL_ERROR',
      message: 'An unexpected error occurred',
    }
  });
});

// Usage in routes
app.get('/users/:id', async (req, res, next) => {
  try {
    const user = await userService.findById(req.params.id);
    if (!user) throw new NotFoundError('User');
    res.json(user);
  } catch (error) {
    next(error);  // Pass to error handler
  }
});

エラーレスポンスの構造

すべてのエラーで一貫した構造にします:

interface ErrorResponse {
  error: {
    code: string;        // Machine-readable: 'VALIDATION_ERROR'
    message: string;     // Human-readable: 'Validation failed'
    details?: unknown;   // Additional info (validation errors, etc.)
    requestId?: string;  // For support/debugging
  }
}

// Examples:
// 400 Bad Request
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": {
      "email": ["Invalid email format"],
      "age": ["Must be at least 18"]
    }
  }
}

// 404 Not Found
{
  "error": {
    "code": "NOT_FOUND",
    "message": "User not found"
  }
}

// 500 Internal Error
{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred",
    "requestId": "req_abc123"
  }
}

HTTP ステータスコード

コード使用する場面
400不正なリクエスト、検証エラー
401認証されていない
403認証済みだが、認可されていない
404リソースが見つからない
409競合(重複、状態の問題)
422処理できないエンティティ
429レート制限に達した
500サーバーエラー(詳細を隠すこと!)
502上流サービスの失敗
503サービスを利用できない

圧力に負けないための手順

1. 「デバッグに役立つ」

圧力:「開発者は完全なエラーを見る必要があります」

**回答:**完全なエラーはサーバー側に記録してください。照合できるよう、リクエスト ID を返してください。

対応:{ requestId: "abc123" } - 開発者はログを検索できます。

2. 「内部 API だけ」

圧力:「この API を呼び出すのは自社のサービスだけです」

**回答:**内部サービスも侵害されます。ログも漏えいします。すべてを保護してください。

**対応:**どこでも同じ安全なエラーハンドリングを行います。

3. 「開発モード」

圧力:「開発環境では詳細を表示し、本番環境では隠します」

**回答:**開発用のコードが本番用コードになります。日頃の習慣が重要です。

**対応:**すべての環境で同じ処理を行います。デバッグにはログを使用してください。

危険な兆候 - 作業を止めて再検討してください

  • res.json({ message: error.message })
  • レスポンス内のスタックトレース
  • エラーメッセージ内の SQL クエリ
  • 内部 IP アドレスやパスの公開
  • エンドポイントごとに異なるエラー形式

これらがあれば、適切なエラーハンドリングを実装してください。

クイックリファレンス

公開される情報(悪い例)安全な情報(良い例)
データベースのエラーメッセージ「エラーが発生しました」
スタックトレースログ検索用のリクエスト ID
内部パス汎用エラーコード
SQL クエリ「検証に失敗しました」
変数のダンプ構造化されたエラーオブジェクト

よくある正当化(すべて誤りです)

言い訳実際のところ
「デバッグに役立つ」サーバー側に記録し、リクエスト ID を返してください。
「内部 API だから」内部のものも侵害されます。
「開発モードだから」どの環境でも同じ処理を行ってください。
「フロントエンドに詳細が必要」安全で構造化された詳細を返してください。
「こちらのほうが速い」エラーハンドリングのコストはわずかです。侵害はそうではありません。

要点

すべてを内部に記録し、外部には何も公開しないでください。

機械可読なコードと人が読めるメッセージを含む、一貫した構造化エラーを返してください。スタックトレース、クエリ、内部の詳細を決して漏らさないでください。デバッグにはリクエスト ID を使用してください。

コメント