security について
securityは、コードの作成とリリース時に安全な実践を取り入れるチーム向けのスキルガイドです。設定例やテスト手順、脆弱性対策を扱い、既存コードの監査より予防策に重点を置きます。
主な機能
- 01環境変数とシークレットの安全な管理
- 02コミット前のセキュリティチェック
- 03GitHub Actionsのセキュリティワークフロー
- 04入力検証とインジェクション対策
- 05認証と認可の実践方法
- 06リリース前のセキュリティチェックリスト
活用シーン
- 01プロジェクトの環境ファイルとシークレット管理規則の設定
- 02コミットやCIへのセキュリティチェック追加
- 03リリース前の入力処理と認証設定の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ alinaqi/ maggy --skill securityインストール後、Claude Code で/securityを入力して読み込まれたか確認します。
詳細
セキュリティスキル
すべてのプロジェクトを対象とした、セキュリティのベストプラクティスと自動セキュリティテストです。
このスキルは予防的なものです。安全なコードの書き方とリリース方法を扱います。既存のコードベースや PR の脆弱性を監査するには、
security-auditスキル(体系的で、敵対的な視点を取り、証拠に基づく)を使用してください。現在のブランチを手早く確認するには、/security-reviewを実行してください。
基本原則
セキュリティは必須です。 すべてのプロジェクトで、マージ前にセキュリティチェックに合格する必要があります。すべての入力は悪意のあるものとして扱い、コミットするとすべてのシークレットが漏えいし、すべての依存関係に脆弱性があると想定してください。
必須のセキュリティ設定
1. Gitignore(必須)
すべてのプロジェクトで、.gitignore に以下を含める必要があります。
# Environment files - NEVER commit
.env
.env.*
!.env.example
# Secrets
*.pem
*.key
*.p12
*.pfx
credentials.json
secrets.json
*-credentials.json
service-account*.json
# IDE and OS
.idea/
.vscode/settings.json
.DS_Store
Thumbs.db
# Dependencies
node_modules/
__pycache__/
*.pyc
.venv/
venv/
# Build outputs
dist/
build/
*.egg-info/
# Logs that might contain sensitive data
*.log
logs/
2. 環境変数
必須の変数をすべて記載した .env.example を作成します(値は記載しません)。
# .env.example - Copy to .env and fill in values
# Server-side only (NEVER prefix with VITE_ or NEXT_PUBLIC_)
DATABASE_URL=
ANTHROPIC_API_KEY=
SUPABASE_SERVICE_ROLE_KEY=
# Client-side safe (public, non-sensitive)
VITE_SUPABASE_URL=
VITE_SUPABASE_ANON_KEY=
フロントエンドの環境変数(重要!)
クライアントに公開される環境変数にシークレットを絶対に設定しないでください。
| フレームワーク | クライアントに公開されるプレフィックス | サーバー専用 |
|---|---|---|
| Vite | VITE_* | プレフィックスなし |
| Next.js | NEXT_PUBLIC_* | プレフィックスなし |
| Create React App | REACT_APP_* | 該当なし(サーバーなし) |
// WRONG - Secret exposed to browser bundle!
const apiKey = import.meta.env.VITE_ANTHROPIC_API_KEY;
// CORRECT - Only public values client-side
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
// CORRECT - Secrets stay server-side only
// In API route or server function:
const apiKey = process.env.ANTHROPIC_API_KEY;
Vercel の環境変数:
- Vercel ダッシュボードでは、
VITE_プレフィックスのないシークレットはサーバー専用です VITE_*変数のみがクライアントコードにバンドルされます- ブラウザーの開発者ツール → Sources → バンドルを確認し、シークレットが公開されていないことを必ず検証してください
起動時に環境変数を検証します。
// config/env.ts
import { z } from 'zod';
const envSchema = z.object({
DATABASE_URL: z.string().url(),
ANTHROPIC_API_KEY: z.string().min(1),
NODE_ENV: z.enum(['development', 'production', 'test']),
});
export const env = envSchema.parse(process.env);
# config/env.py
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
anthropic_api_key: str
environment: str = "development"
class Config:
env_file = ".env"
settings = Settings()
セキュリティテスト
コミット前のセキュリティチェック
コミット前フックに追加します。
すべてのプロジェクト向け:
# .pre-commit-config.yaml (add to existing)
repos:
# Detect secrets
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
# Check for security issues in dependencies
- repo: local
hooks:
- id: security-check
name: security-check
entry: ./scripts/security-check.sh
language: script
pass_filenames: false
TypeScript/JavaScript:
// package.json scripts
{
"scripts": {
"security:audit": "npm audit --audit-level=high",
"security:secrets": "npx secretlint '**/*'",
"security:deps": "npx better-npm-audit audit"
}
}
Python:
# Add to dev dependencies
pip install safety bandit
# Commands
safety check # Check dependencies for vulnerabilities
bandit -r src/ # Static security analysis
セキュリティチェック用スクリプト
scripts/security-check.sh を作成します。
#!/bin/bash
set -e
echo "Running security checks..."
# Check for secrets in staged files
echo "Checking for secrets..."
if command -v detect-secrets &> /dev/null; then
detect-secrets scan --baseline .secrets.baseline
fi
# Check .env is not staged
if git diff --cached --name-only | grep -E '^\.env$|^\.env\.' | grep -v '\.example$'; then
echo "ERROR: .env file is staged for commit!"
exit 1
fi
# Check for common secret patterns in staged files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
if echo "$STAGED_FILES" | xargs grep -l -E '(password|secret|api_key|apikey|token|private_key)\s*[:=]\s*["\047][^"\047]+["\047]' 2>/dev/null; then
echo "ERROR: Possible secrets found in staged files!"
exit 1
fi
# Language-specific checks
if [ -f "package.json" ]; then
echo "Checking npm dependencies..."
npm audit --audit-level=high || echo "Warning: npm audit found issues"
fi
if [ -f "pyproject.toml" ] || [ -f "requirements.txt" ]; then
echo "Checking Python dependencies..."
if command -v safety &> /dev/null; then
safety check || echo "Warning: safety found issues"
fi
fi
echo "Security checks passed!"
chmod +x scripts/security-check.sh
GitHub Actions のセキュリティワークフロー
.github/workflows/security.yml を作成します。
name: Security
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# Run weekly on Monday at 9am UTC
- cron: '0 9 * * 1'
jobs:
secrets-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect secrets
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.event.pull_request.head.sha }}
dependency-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Node.js projects
- name: Setup Node
if: hashFiles('package.json') != ''
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
if: hashFiles('package.json') != ''
run: npm ci
- name: NPM Audit
if: hashFiles('package.json') != ''
run: npm audit --audit-level=high
# Python projects
- name: Setup Python
if: hashFiles('pyproject.toml') != '' || hashFiles('requirements.txt') != ''
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install safety
if: hashFiles('pyproject.toml') != '' || hashFiles('requirements.txt') != ''
run: pip install safety
- name: Safety check
if: hashFiles('pyproject.toml') != '' || hashFiles('requirements.txt') != ''
run: safety check
codeql:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ hashFiles('package.json') != '' && 'javascript-typescript' || 'python' }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
入力の検証(OWASP Top 10)
1. SQL インジェクションの防止
文字列連結は絶対に使用しないでください。
// BAD - SQL injection vulnerable
const user = await db.query(`SELECT * FROM users WHERE id = ${userId}`);
// GOOD - Parameterized query
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
// GOOD - Using ORM (Kysely, Prisma, Drizzle)
const user = await db.selectFrom('users').where('id', '=', userId).execute();
# BAD - SQL injection vulnerable
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
# GOOD - Parameterized query
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# GOOD - Using ORM (SQLAlchemy)
user = session.query(User).filter(User.id == user_id).first()
2. XSS の防止
// Always sanitize user input before rendering
import DOMPurify from 'dompurify';
// BAD - XSS vulnerable
element.innerHTML = userInput;
// GOOD - Sanitized
element.innerHTML = DOMPurify.sanitize(userInput);
// BEST - Use framework's built-in escaping (React does this by default)
return <div>{userInput}</div>; // Safe in React
// DANGER - Bypasses React's protection
return <div dangerouslySetInnerHTML={{ __html: userInput }} />; // Avoid!
3. 境界での入力検証
// Validate ALL external input with Zod
import { z } from 'zod';
const CreateUserSchema = z.object({
email: z.string().email().max(255),
name: z.string().min(1).max(100).regex(/^[a-zA-Z\s]+$/),
age: z.number().int().min(0).max(150),
});
// In route handler
app.post('/users', async (req, res) => {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: result.error });
}
// result.data is now typed and validated
});
4. パストラバーサルの防止
import path from 'path';
// BAD - Path traversal vulnerable
const filePath = `./uploads/${req.params.filename}`;
// GOOD - Validate and sanitize path
const filename = path.basename(req.params.filename); // Strips ../
const filePath = path.join('./uploads', filename);
// Verify it's still within allowed directory
if (!filePath.startsWith(path.resolve('./uploads'))) {
throw new Error('Invalid path');
}
認証と認可
JWT のベストプラクティス
import jwt from 'jsonwebtoken';
// Token generation
function generateToken(userId: string): string {
return jwt.sign(
{ sub: userId },
process.env.JWT_SECRET!,
{
expiresIn: '15m', // Short-lived access tokens
algorithm: 'HS256',
}
);
}
// Token verification
function verifyToken(token: string): { sub: string } {
return jwt.verify(token, process.env.JWT_SECRET!, {
algorithms: ['HS256'], // Explicitly specify allowed algorithms
}) as { sub: string };
}
パスワードのハッシュ化
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12; // Minimum 10, recommended 12+
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(password: str, hashed: str) -> bool:
return pwd_context.verify(password, hashed)
レート制限
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
standardHeaders: true,
legacyHeaders: false,
});
// Apply to auth routes
app.use('/api/auth', rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 5, // 5 attempts per minute
message: 'Too many login attempts, please try again later',
}));
セキュリティヘッダー
import helmet from 'helmet';
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
},
}));
セキュリティテストのチェックリスト
リリース前に毎回実行します。
## Security Checklist
### Secrets & Environment
- [ ] No secrets in code (run detect-secrets)
- [ ] .env files in .gitignore
- [ ] .env.example exists with all required vars
- [ ] Environment validated at startup
### Dependencies
- [ ] npm audit / safety check passes
- [ ] No known vulnerabilities in dependencies
- [ ] Dependencies up to date (Dependabot enabled)
### Input Validation
- [ ] All API inputs validated with schema (Zod/Pydantic)
- [ ] File uploads restricted by type and size
- [ ] Path traversal prevented
### Authentication
- [ ] Passwords hashed with bcrypt (12+ rounds)
- [ ] JWTs use short expiration
- [ ] Rate limiting on auth endpoints
- [ ] Session tokens rotated on login
### Database
- [ ] Parameterized queries only
- [ ] Least privilege database user
- [ ] Connection strings not logged
### Headers & CORS
- [ ] Security headers enabled (helmet)
- [ ] CORS restricted to known origins
- [ ] HTTPS only in production
### Logging
- [ ] No secrets in logs
- [ ] No PII in logs (or properly masked)
- [ ] Failed auth attempts logged
セキュリティ上のアンチパターン
- ❌
VITE_*、NEXT_PUBLIC_*、またはREACT_APP_*環境変数にシークレットを設定する(クライアントに公開されます!) - ❌ Git にコミットするコードや設定ファイルにシークレットを記載する
- ❌
.gitignoreに登録せずに.envファイルを使用する - ❌ SQL クエリで文字列連結を使用する
- ❌ サニタイズせずに
dangerouslySetInnerHTMLを使用する - ❌ ユーザー入力に対して
eval()またはnew Function()を使用する - ❌ パスワードを平文または脆弱なハッシュ(MD5、SHA1)で保存する
- ❌ 有効期限のない、または有効期限が非常に長い JWT を使用する
- ❌ 認証エンドポイントにレート制限を設定しない
- ❌ 機密データ(パスワード、トークン、個人情報)をログに記録する
- ❌ 本番環境で CORS オリジンに
*を使用する - ❌ npm audit/safety check の警告を無視する
- ❌ 本番環境で root/管理者として実行する
- ❌ どの環境でも認証情報をハードコードする
- ❌ SSL/TLS の検証を無効にする