mcpField
S
security

security

github.com/scientiacapitalセキュリティ2878 回インストール4 週間前に更新
リポジトリ

security について

Webアプリ開発者向けのセキュリティスキルで、認証、シークレット管理、入力検証、一般的な脆弱性への対策を案内します。Supabase、Next.js、Pythonバックエンドの実践パターンに加え、トピック別資料とデプロイ前チェックリストを備えています。

主な機能

  1. 01JWTとセッションのセキュリティパターン適用
  2. 02シークレットの管理と検証
  3. 03ユーザー入力の検証とサニタイズ
  4. 04SQLインジェクション、XSS、CSRFの防止
  5. 05Supabase RLSとセキュリティヘッダーの設定
  6. 06デプロイ前のセキュリティチェック

活用シーン

  1. 01Webアプリの認証方式を設計
  2. 02シークレットと環境変数を保護
  3. 03アプリケーション境界でデータを検証
  4. 04デプロイ前にセキュリティ対策を確認

インストール

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

$npx skills add https://github.com/scientiacapital/skills --skill security

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

config.json
reference/
auth-patterns.md
input-validation.md
owasp-top-10.md
rls-policies.md
secrets-management.md

詳細

認証パターン、シークレット管理、入力検証、および一般的な脆弱性の防止を網羅する包括的なセキュリティスキル。Supabase、Next.js、Python バックエンドを用いた Web アプリケーション向けの実践的なパターンに焦点を当てています。

セキュリティは任意ではありません。基本的な要件です。このスキルは、後からセキュリティを付け足すのではなく、最初から安全なアプリケーションを構築するのに役立ちます。

<quick_start> あらゆる新規プロジェクトにおけるセキュリティの要点:

  1. シークレット: 決して git にコミットせず、起動時に検証する

    // .env (gitignored) + envSchema.parse(process.env)
    
  2. 認証: 短命な JWT + httpOnly Cookie

    jwt.sign(payload, secret, { expiresIn: '15m' })
    
  3. 入力: すべてをスキーマで検証する

    const data = z.object({ email: z.string().email() }).parse(input)
    
  4. SQL: 常にパラメーター化クエリを使用する(ORM がこれを処理します)

  5. RLS: すべての Supabase テーブルで有効化し、ユーザースコープのポリシーを設定する </quick_start>

<success_criteria> セキュリティの実装は、次の場合に成功です:

  • すべてのシークレットが環境変数にあり、起動時に検証されている
  • バージョン管理にシークレットがない(gitleaks で確認済み)
  • JWT トークンが短命(15 分以下)で、リフレッシュトークンのローテーションがある
  • すべてのユーザー入力が Zod または同様のスキーマ検証で検証されている
  • すべてのデータベーステーブルで RLS が有効化され、適切なポリシーが設定されている
  • CSP ヘッダーが設定されている(可能な限り unsafe-inline を使用しない)
  • デプロイ前にセキュリティチェックリストが完了している </success_criteria>

<security_mindset>

セキュリティマインドセット

基本原則
  1. 多層防御 - 1 つの壁ではなく、複数のセキュリティ層
  2. 最小権限 - 必要な最小限のアクセス権のみを付与する
  3. 入力を決して信頼しない - ユーザーおよび外部システムからのすべてを検証する
  4. 安全側にフェイルする - エラーはアクセスを許可するのではなく、拒否するべき
  5. シークレットは秘密に保つ - API キーは決してコードやログに含めない
問いかけるべきセキュリティ上の質問

機能をリリースする前に:

  • これはどのようなデータを公開するか?
  • このエンドポイント/ページにアクセスできるのは誰か?
  • ユーザーが悪意のある入力を送信したらどうなるか?
  • シークレットは適切に保護されているか?
  • 機密データがログに記録されているか? </security_mindset>
## 認証パターン
JWT とセッション
観点JWTセッション
保存場所クライアント(localStorage/Cookie)サーバー(DB/Redis)
スケーラビリティステートレスで、スケールしやすい共有セッションストアが必要
失効困難(ブラックリストが必要)容易(ストアから削除)
サイズ大きい(クレームを含む)小さい(セッション ID のみ)
最適な用途API、マイクロサービス従来型の Web アプリ
JWT のベストプラクティス
// DO: Short-lived access tokens + refresh tokens
const accessToken = jwt.sign(
  { userId: user.id, role: user.role },
  process.env.JWT_SECRET,
  { expiresIn: '15m' } // Short-lived!
);

const refreshToken = jwt.sign(
  { userId: user.id },
  process.env.JWT_REFRESH_SECRET,
  { expiresIn: '7d' }
);

// DON'T: Long-lived tokens with sensitive data
// Bad example - never do this:
// { expiresIn: '365d' } // Too long!
// Including PII like SSN in token payload
トークンの保存
方法XSS 安全CSRF 安全推奨
localStorageいいえはい認証には避ける
httpOnly Cookieはいいいえ(CSRF トークンが必要)推奨
メモリ(変数)はいはいSPA に最適
リフレッシュトークンフロー
┌─────────┐                    ┌─────────┐                    ┌─────────┐
│ Client  │                    │  Server │                    │   DB    │
└────┬────┘                    └────┬────┘                    └────┬────┘
     │                              │                              │
     │  Login (email, password)     │                              │
     │─────────────────────────────>│                              │
     │                              │  Verify credentials          │
     │                              │─────────────────────────────>│
     │                              │<─────────────────────────────│
     │  Access token (15m)          │                              │
     │  Refresh token (7d)          │  Store refresh token hash    │
     │<─────────────────────────────│─────────────────────────────>│
     │                              │                              │
     │  API call + access token     │                              │
     │─────────────────────────────>│                              │
     │  Response                    │                              │
     │<─────────────────────────────│                              │
     │                              │                              │
     │  [Access token expired]      │                              │
     │  Refresh token               │                              │
     │─────────────────────────────>│  Verify refresh token        │
     │                              │─────────────────────────────>│
     │  New access token            │                              │
     │<─────────────────────────────│                              │
パスワードの扱い
import bcrypt from 'bcrypt';

// DO: Hash with sufficient rounds
const SALT_ROUNDS = 12; // ~300ms on modern hardware
const hash = await bcrypt.hash(password, SALT_ROUNDS);

// DO: Constant-time comparison
const isValid = await bcrypt.compare(inputPassword, storedHash);

// DON'T: Use weak hashing algorithms like MD5 or SHA1 for passwords
パスワード要件
const passwordSchema = z.string()
  .min(8, 'Minimum 8 characters')
  .max(128, 'Maximum 128 characters')
  .regex(/[a-z]/, 'Must contain lowercase')
  .regex(/[A-Z]/, 'Must contain uppercase')
  .regex(/[0-9]/, 'Must contain number')
  .regex(/[^a-zA-Z0-9]/, 'Must contain special character');

// Check against common passwords (haveibeenpwned API)
async function isPasswordPwned(password: string): Promise<boolean> {
  const sha1 = crypto.createHash('sha1').update(password).digest('hex').toUpperCase();
  const prefix = sha1.slice(0, 5);
  const suffix = sha1.slice(5);

  const response = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`);
  const text = await response.text();

  return text.includes(suffix);
}

<secrets_management>

シークレット管理

黄金律: シークレットを決してバージョン管理にコミットしないでください。.env ファイル(gitignore 対象)を使用し、Zod スキーマですべての環境変数を起動時に検証し、複数の有効なシークレットによるローテーションをサポートし、機密データを決してログに記録しないでください。

gitignore パターン、環境変数テンプレート、Zod 検証、ローテーションパターン、ログのマスキングについては reference/secrets-management.md を参照してください。 </secrets_management>

<input_validation>

入力検証

システム境界での検証
┌─────────────────────────────────────────────────────────┐
│                    Your Application                      │
│                                                          │
│   ┌──────────┐     VALIDATE      ┌──────────────────┐  │
│   │  User    │ ───────────────>  │  Business Logic   │  │
│   │  Input   │                   │  (trusted data)   │  │
│   └──────────┘                   └──────────────────┘  │
│                                                          │
│   ┌──────────┐     VALIDATE      ┌──────────────────┐  │
│   │ External │ ───────────────>  │  Services         │  │
│   │   API    │                   │                   │  │
│   └──────────┘                   └──────────────────┘  │
└─────────────────────────────────────────────────────────┘
スキーマ検証(Zod)
import { z } from 'zod';

// Define schemas
const createUserSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8).max(128),
  name: z.string().min(1).max(100),
  age: z.number().int().min(13).max(120).optional(),
});

// Validate input
export async function createUser(input: unknown) {
  const data = createUserSchema.parse(input); // Throws if invalid
  // data is now typed and validated
  return db.users.create(data);
}

// API handler
export async function POST(req: Request) {
  try {
    const body = await req.json();
    const user = await createUser(body);
    return Response.json(user, { status: 201 });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return Response.json({ errors: error.errors }, { status: 400 });
    }
    throw error;
  }
}
サニタイズ
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';

const window = new JSDOM('').window;
const purify = DOMPurify(window);

// Sanitize HTML (for rich text fields)
const cleanHtml = purify.sanitize(userInput, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
  ALLOWED_ATTR: ['href']
});

// For plain text - escape or strip HTML
const plainText = purify.sanitize(userInput, { ALLOWED_TAGS: [] });

</input_validation>

<sql_injection>

SQL インジェクションの防止

問題点

攻撃者は、サニタイズされていない入力を通じて SQL クエリを操作できます。 攻撃ペイロードの例: '; DROP TABLE users; --

解決策: パラメーター化クエリ
// SAFE: Parameterized query (Prisma)
const user = await prisma.user.findUnique({
  where: { email: email }
});

// SAFE: Parameterized query (raw SQL)
const user = await db.query(
  'SELECT * FROM users WHERE email = $1',
  [email]
);

// SAFE: Supabase
const { data } = await supabase
  .from('users')
  .select()
  .eq('email', email);
Supabase RLS(行レベルセキュリティ)
-- Enable RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Users can only see their own posts
CREATE POLICY "Users see own posts"
  ON posts FOR SELECT
  USING (auth.uid() = user_id);

-- Users can only create posts as themselves
CREATE POLICY "Users create own posts"
  ON posts FOR INSERT
  WITH CHECK (auth.uid() = user_id);

-- Users can only update their own posts
CREATE POLICY "Users update own posts"
  ON posts FOR UPDATE
  USING (auth.uid() = user_id);

-- Users can only delete their own posts
CREATE POLICY "Users delete own posts"
  ON posts FOR DELETE
  USING (auth.uid() = user_id);

</sql_injection>

<xss_prevention>

XSS の防止

問題点

攻撃者は、被害者のブラウザーで実行される悪意のあるスクリプトを注入し、Cookie やデータを窃取します。

解決策: 自動エスケープ + CSP
// React auto-escapes by default - this is safe
return <div>Welcome, {userName}</div>;

// AVOID rendering raw HTML from user input
// If you absolutely must render user HTML, ALWAYS sanitize with DOMPurify first
import DOMPurify from 'dompurify';
const sanitizedContent = DOMPurify.sanitize(userContent);
コンテンツセキュリティポリシー
// next.config.js
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: [
      "default-src 'self'",
      "script-src 'self'", // Avoid 'unsafe-inline' if possible
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: https:",
      "font-src 'self'",
      "connect-src 'self' https://api.supabase.co",
    ].join('; ')
  }
];

</xss_prevention>

<csrf_protection>

CSRF 保護

問題

攻撃者は、認証済みユーザーをだまして、サイトに悪意のあるリクエストを送信させます。

解決策:CSRF トークン + SameSite Cookie
// Server: Generate token
import { randomBytes } from 'crypto';

function generateCsrfToken(): string {
  return randomBytes(32).toString('hex');
}

// Store in session
session.csrfToken = generateCsrfToken();

// Client: Include in forms as hidden field
// Server: Validate token matches session

// Modern approach: SameSite cookies (most effective)
res.cookie('session', sessionId, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict', // or 'lax'
});

</csrf_protection>

詳細なパターンについては、該当するリファレンスを読み込んでください。
トピックリファレンスファイル読み込むタイミング
認証パターンreference/auth-patterns.mdJWT、OAuth、セッション
シークレットreference/secrets-management.mdAPI キー、環境変数
入力検証reference/input-validation.mdサニタイズ、スキーマ
Supabase RLSreference/rls-policies.md行レベルセキュリティ
OWASP Top 10reference/owasp-top-10.md脆弱性チェックリスト

読み込み方法: 特定のトピックを指定するか、コンテキストから必要性を判断してください。

## セキュリティチェックリスト

デプロイ前に確認してください。

認証
  • パスワードは bcrypt でハッシュ化(12 ラウンド以上)
  • JWT トークンの有効期間は短く設定(最大 15 分)
  • リフレッシュトークンを安全に保管
  • セッション Cookie に httpOnly + secure + sameSite を設定
シークレット
  • コードやバージョン管理にシークレットが含まれていない
  • 起動時に環境変数を検証
  • シークレットをログに記録しない
入力
  • すべてのユーザー入力をスキーマで検証
  • SQL ではパラメーター化クエリを使用
  • 描画前に HTML をサニタイズ
  • ファイルアップロードを検証(種類、サイズ、名前)
ヘッダー
  • HTTPS を強制
  • CSP ヘッダーを設定
  • CORS を許可されたオリジンに制限
  • セキュリティヘッダーを設定(HSTS、X-Frame-Options)
認可
  • すべてのテーブルで RLS を有効化
  • API エンドポイントで権限を確認
  • 管理者用ルートを保護
  • 認証エンドポイントにレート制限を設定

結果サイドカーの出力

最後の手順として、~/.claude/skill-analytics/last-outcome-security.json に書き込みます。

{"ts":"[UTC ISO8601]","skill":"security","version":"1.0.0","variant":"default",
 "status":"[success|partial|error]","runtime_ms":[estimated ms from start],
 "metrics":{"vulnerabilities_found":[n],"fixes_applied":[n],"audit_checks_passed":[n]},
 "error":null,"session_id":"[YYYY-MM-DD]"}

一部の段階で失敗したものの結果が生成された場合は、ステータスに "partial" を使用します。出力が生成されなかった場合に限り、"error" を使用します。

コメント