security-and-hardening
コードの脆弱性を強化します。ユーザー入力の処理、認証、データストレージ、外部連携を扱う際に使用してください。信頼されていないデータを受け付ける機能、ユーザーセッション管理、サードパーティサービスとの連携を構築する際に活用できます。
description の原文を見る
Hardens code against vulnerabilities. Use when handling user input, authentication, data storage, or external integrations. Use when building any feature that accepts untrusted data, manages user sessions, or interacts with third-party services.
SKILL.md 本文
セキュリティとハードニング
概要
Webアプリケーション向けのセキュリティ第一の開発実践です。すべての外部入力を敵対的なものとして扱い、すべてのシークレットを神聖なものとして扱い、すべての認可チェックを必須とします。セキュリティはフェーズではなく、ユーザーデータ、認証、外部システムに触れるすべてのコード行に対する制約です。
使用時期
- ユーザー入力を受け付けるもの全般の構築
- 認証または認可の実装
- 機密データの保存または送信
- 外部APIやサービスとの統合
- ファイルアップロード、Webhook、コールバックの追加
- 支払い情報またはPII(個人識別情報)の処理
3段階境界システム
常に実施(例外なし)
- すべての外部入力を検証する — システム境界(APIルート、フォームハンドラ)で
- すべてのデータベースクエリをパラメータ化する — ユーザー入力をSQLに連結してはいけません
- 出力をエンコードする — XSSを防ぐため(フレームワークの自動エスケープを使用し、バイパスしない)
- すべての外部通信にHTTPSを使用する
- パスワードをハッシュ化する — bcrypt/scrypt/argon2で(プレーンテキストで保存しない)
- セキュリティヘッダーを設定する(CSP、HSTS、X-Frame-Options、X-Content-Type-Options)
- httpOnly、secure、sameSiteクッキーをセッションに使用する
- すべてのリリース前に
npm auditを実行する(または同等の手段)
承認を求める(人間の承認が必要)
- 新しい認証フローの追加または認証ロジックの変更
- 新しいカテゴリの機密データ(PII、支払い情報)の保存
- 新しい外部サービス統合の追加
- CORS設定の変更
- ファイルアップロードハンドラの追加
- レート制限またはスロットリングの変更
- 昇格されたパーミッションまたはロールの付与
禁止事項
- シークレットをバージョン管理にコミットしない(APIキー、パスワード、トークン)
- 機密データをログに記録しない(パスワード、トークン、クレジットカード番号の全桁)
- クライアント側の検証をセキュリティ境界として信頼しない
- セキュリティヘッダーを無効化しない(利便性のため)
- ユーザー提供データで
eval()またはinnerHTMLを使用しない - セッションをクライアントアクセス可能なストレージに保存しない(認証トークンのlocalStorage)
- スタックトレースまたは内部エラー詳細をユーザーに公開しない
OWASP Top 10 対策
1. インジェクション(SQL、NoSQL、OSコマンド)
// 悪い例: 文字列連結によるSQLインジェクション
const query = `SELECT * FROM users WHERE id = '${userId}'`;
// 良い例: パラメータ化クエリ
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
// 良い例: パラメータ化入力を使用するORM
const user = await prisma.user.findUnique({ where: { id: userId } });
2. 認証の不備
// パスワードハッシング
import { hash, compare } from 'bcrypt';
const SALT_ROUNDS = 12;
const hashedPassword = await hash(plaintext, SALT_ROUNDS);
const isValid = await compare(plaintext, hashedPassword);
// セッション管理
app.use(session({
secret: process.env.SESSION_SECRET, // コードからではなく環境から
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true, // JavaScriptからアクセス不可
secure: true, // HTTPSのみ
sameSite: 'lax', // CSRF対策
maxAge: 24 * 60 * 60 * 1000, // 24時間
},
}));
3. クロスサイトスクリプティング(XSS)
// 悪い例: ユーザー入力をHTMLとしてレンダリング
element.innerHTML = userInput;
// 良い例: フレームワークの自動エスケープを使用(Reactはデフォルトで実施)
return <div>{userInput}</div>;
// HTMLをレンダリングする必要がある場合は、最初にサニタイズする
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userInput);
4. アクセス制御の不備
// 常に認証だけでなく認可もチェック
app.patch('/api/tasks/:id', authenticate, async (req, res) => {
const task = await taskService.findById(req.params.id);
// 認証されたユーザーがこのリソースを所有していることを確認
if (task.ownerId !== req.user.id) {
return res.status(403).json({
error: { code: 'FORBIDDEN', message: 'Not authorized to modify this task' }
});
}
// 更新を実行
const updated = await taskService.update(req.params.id, req.body);
return res.json(updated);
});
5. セキュリティ設定の不備
// セキュリティヘッダー(Express用のhelmetを使用)
import helmet from 'helmet';
app.use(helmet());
// コンテンツセキュリティポリシー
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"], // 可能であれば厳しくする
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
},
}));
// CORS — 既知のオリジンに制限
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || 'http://localhost:3000',
credentials: true,
}));
6. 機密データの露出
// API応答で機密フィールドを返さない
function sanitizeUser(user: UserRecord): PublicUser {
const { passwordHash, resetToken, ...publicFields } = user;
return publicFields;
}
// シークレット用に環境変数を使用
const API_KEY = process.env.STRIPE_API_KEY;
if (!API_KEY) throw new Error('STRIPE_API_KEY not configured');
入力検証パターン
境界でのスキーマ検証
import { z } from 'zod';
const CreateTaskSchema = z.object({
title: z.string().min(1).max(200).trim(),
description: z.string().max(2000).optional(),
priority: z.enum(['low', 'medium', 'high']).default('medium'),
dueDate: z.string().datetime().optional(),
});
// ルートハンドラで検証
app.post('/api/tasks', async (req, res) => {
const result = CreateTaskSchema.safeParse(req.body);
if (!result.success) {
return res.status(422).json({
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid input',
details: result.error.flatten(),
},
});
}
// result.dataは型付けされ検証済み
const task = await taskService.create(result.data);
return res.status(201).json(task);
});
ファイルアップロードの安全性
// ファイルタイプとサイズを制限
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp'];
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
function validateUpload(file: UploadedFile) {
if (!ALLOWED_TYPES.includes(file.mimetype)) {
throw new ValidationError('File type not allowed');
}
if (file.size > MAX_SIZE) {
throw new ValidationError('File too large (max 5MB)');
}
// ファイル拡張子を信頼しない — 必要に応じてマジックバイトをチェック
}
npm audit 結果の優先順位付け
すべての監査結果が即座に対応が必要なわけではありません。以下の判定フローを使用してください:
npm auditが脆弱性を報告
├── 深刻度: critical または high
│ ├── 脆弱なコードはアプリで到達可能か?
│ │ ├── はい --> すぐに修正(更新、パッチ、または依存関係の置換)
│ │ └── いいえ(開発のみの依存関係、未使用のコードパス) --> 近いうちに修正、ただしブロッカーではない
│ └── 修正が利用可能か?
│ ├── はい --> パッチ版に更新
│ └── いいえ --> 回避策をチェック、依存関係の置換を検討、またはレビュー日付付きで許可リストに追加
├── 深刻度: moderate
│ ├── 本番環境で到達可能? --> 次のリリースサイクルで修正
│ └── 開発のみ? --> 都合がつきましたら修正、バックログで追跡
└── 深刻度: low
└── 定期的な依存関係更新時に追跡して修正
重要な質問:
- 脆弱な機能は実際にあなたのコードパスで呼び出されていますか?
- 依存関係はランタイム依存関係ですか、それとも開発のみですか?
- デプロイメントコンテキストを考慮すると脆弱性は悪用可能ですか(例:クライアントのみのアプリケーションでのサーバー側の脆弱性)?
修正を延期するときは、理由を文書化し、レビュー日付を設定してください。
レート制限
import rateLimit from 'express-rate-limit';
// 一般的なAPI制限
app.use('/api/', rateLimit({
windowMs: 15 * 60 * 1000, // 15分
max: 100, // ウィンドウあたり100リクエスト
standardHeaders: true,
legacyHeaders: false,
}));
// 認証エンドポイント用のより厳しい制限
app.use('/api/auth/', rateLimit({
windowMs: 15 * 60 * 1000,
max: 10, // 15分あたり10回の試行
}));
シークレット管理
.envファイル:
├── .env.example → コミット(プレースホルダー値を含むテンプレート)
├── .env → コミットしない(実際のシークレットを含む)
└── .env.local → コミットしない(ローカルオーバーライド)
.gitignoreに以下を含める必要があります:
.env
.env.local
.env.*.local
*.pem
*.key
コミット前に常にチェック:
# 誤ってステージされたシークレットをチェック
git diff --cached | grep -i "password\|secret\|api_key\|token"
セキュリティレビューチェックリスト
### 認証
- [ ] パスワードはbcrypt/scrypt/argon2でハッシュ化(ソルトラウンド ≥ 12)
- [ ] セッショントークンはhttpOnly、secure、sameSite
- [ ] ログインにレート制限がある
- [ ] パスワードリセットトークンが有効期限切れ
### 認可
- [ ] すべてのエンドポイントがユーザーパーミッションをチェック
- [ ] ユーザーは自分のリソースのみアクセス可能
- [ ] 管理者アクションは管理者ロール検証が必要
### 入力
- [ ] すべてのユーザー入力は境界で検証
- [ ] SQLクエリはパラメータ化
- [ ] HTML出力はエンコード/エスケープ
### データ
- [ ] コードまたはバージョン管理にシークレットなし
- [ ] API応答から機密フィールドを除外
- [ ] PII は保存時に暗号化(該当する場合)
### インフラストラクチャ
- [ ] セキュリティヘッダーが設定(CSP、HSTSなど)
- [ ] CORSが既知のオリジンに制限
- [ ] 脆弱性について依存関係を監査
- [ ] エラーメッセージが内部情報を公開しない
参照
詳細なセキュリティチェックリストおよびコミット前検証手順については、references/security-checklist.md を参照してください。
よくある正当化
| 正当化 | 現実 |
|---|---|
| 「これは内部ツールなので、セキュリティは重要ではない」 | 内部ツールも侵害されます。攻撃者は最も弱いリンクをターゲットにします。 |
| 「後でセキュリティを追加します」 | セキュリティの後付けは10倍難しくなります。今すぐ追加してください。 |
| 「誰もこれを悪用しようとしないでしょう」 | 自動スキャナが見つけます。セキュリティによる隠蔽はセキュリティではありません。 |
| 「フレームワークがセキュリティを処理します」 | フレームワークはツールを提供しますが、保証ではありません。正しく使用する必要があります。 |
| 「これはプロトタイプです」 | プロトタイプは本番環境になります。初日からセキュリティの習慣を。 |
レッドフラグ
- ユーザー入力がデータベースクエリ、シェルコマンド、またはHTMLレンダリングに直接渡される
- ソースコードまたはコミット履歴にシークレット
- 認証または認可チェックのないAPIエンドポイント
- CORS設定がないか、ワイルドカード(
*)オリジン - 認証エンドポイントでレート制限なし
- スタックトレースまたは内部エラーをユーザーに公開
- 既知の重大な脆弱性がある依存関係
検証
セキュリティ関連コードを実装した後:
-
npm auditで重大度critical以上の脆弱性がない - ソースコードまたはgit履歴にシークレットなし
- すべてのユーザー入力がシステム境界で検証
- 保護されたすべてのエンドポイントで認証と認可をチェック
- レスポンスにセキュリティヘッダーが存在(ブラウザDevToolsで確認)
- エラー応答が内部詳細を公開しない
- 認証エンドポイントでレート制限が有効
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- addyosmani
- ライセンス
- MIT
- 最終更新
- 2026/5/10
Source: https://github.com/addyosmani/agent-skills / ライセンス: MIT