mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-09 19:03:28 +08:00
fix: harden unicode safety checks
This commit is contained in:
@@ -12,7 +12,7 @@ description: Backend architecture patterns, API design, database optimization, a
|
||||
### RESTful API構造
|
||||
|
||||
```typescript
|
||||
// ✅ リソースベースのURL
|
||||
// PASS: リソースベースのURL
|
||||
GET /api/markets # リソースのリスト
|
||||
GET /api/markets/:id # 単一リソースの取得
|
||||
POST /api/markets # リソースの作成
|
||||
@@ -20,7 +20,7 @@ PUT /api/markets/:id # リソースの置換
|
||||
PATCH /api/markets/:id # リソースの更新
|
||||
DELETE /api/markets/:id # リソースの削除
|
||||
|
||||
// ✅ フィルタリング、ソート、ページネーション用のクエリパラメータ
|
||||
// PASS: フィルタリング、ソート、ページネーション用のクエリパラメータ
|
||||
GET /api/markets?status=active&sort=volume&limit=20&offset=0
|
||||
```
|
||||
|
||||
@@ -120,7 +120,7 @@ export default withAuth(async (req, res) => {
|
||||
### クエリ最適化
|
||||
|
||||
```typescript
|
||||
// ✅ 良い: 必要な列のみを選択
|
||||
// PASS: 良い: 必要な列のみを選択
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('id, name, status, volume')
|
||||
@@ -128,7 +128,7 @@ const { data } = await supabase
|
||||
.order('volume', { ascending: false })
|
||||
.limit(10)
|
||||
|
||||
// ❌ 悪い: すべてを選択
|
||||
// FAIL: 悪い: すべてを選択
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('*')
|
||||
@@ -137,13 +137,13 @@ const { data } = await supabase
|
||||
### N+1クエリ防止
|
||||
|
||||
```typescript
|
||||
// ❌ 悪い: N+1クエリ問題
|
||||
// FAIL: 悪い: N+1クエリ問題
|
||||
const markets = await getMarkets()
|
||||
for (const market of markets) {
|
||||
market.creator = await getUser(market.creator_id) // Nクエリ
|
||||
}
|
||||
|
||||
// ✅ 良い: バッチフェッチ
|
||||
// PASS: 良い: バッチフェッチ
|
||||
const markets = await getMarkets()
|
||||
const creatorIds = markets.map(m => m.creator_id)
|
||||
const creators = await getUsers(creatorIds) // 1クエリ
|
||||
|
||||
@@ -86,7 +86,7 @@ ORDER BY hour DESC;
|
||||
### 効率的なフィルタリング
|
||||
|
||||
```sql
|
||||
-- ✅ 良い: インデックス列を最初に使用
|
||||
-- PASS: 良い: インデックス列を最初に使用
|
||||
SELECT *
|
||||
FROM markets_analytics
|
||||
WHERE date >= '2025-01-01'
|
||||
@@ -95,7 +95,7 @@ WHERE date >= '2025-01-01'
|
||||
ORDER BY date DESC
|
||||
LIMIT 100;
|
||||
|
||||
-- ❌ 悪い: インデックスのない列を最初にフィルタリング
|
||||
-- FAIL: 悪い: インデックスのない列を最初にフィルタリング
|
||||
SELECT *
|
||||
FROM markets_analytics
|
||||
WHERE volume > 1000
|
||||
@@ -106,7 +106,7 @@ WHERE volume > 1000
|
||||
### 集計
|
||||
|
||||
```sql
|
||||
-- ✅ 良い: ClickHouse固有の集計関数を使用
|
||||
-- PASS: 良い: ClickHouse固有の集計関数を使用
|
||||
SELECT
|
||||
toStartOfDay(created_at) AS day,
|
||||
market_id,
|
||||
@@ -119,7 +119,7 @@ WHERE created_at >= today() - INTERVAL 7 DAY
|
||||
GROUP BY day, market_id
|
||||
ORDER BY day DESC, total_volume DESC;
|
||||
|
||||
-- ✅ パーセンタイルにはquantileを使用(percentileより効率的)
|
||||
-- PASS: パーセンタイルにはquantileを使用(percentileより効率的)
|
||||
SELECT
|
||||
quantile(0.50)(trade_size) AS median,
|
||||
quantile(0.95)(trade_size) AS p95,
|
||||
@@ -162,7 +162,7 @@ const clickhouse = new ClickHouse({
|
||||
}
|
||||
})
|
||||
|
||||
// ✅ バッチ挿入(効率的)
|
||||
// PASS: バッチ挿入(効率的)
|
||||
async function bulkInsertTrades(trades: Trade[]) {
|
||||
const values = trades.map(trade => `(
|
||||
'${trade.id}',
|
||||
@@ -178,7 +178,7 @@ async function bulkInsertTrades(trades: Trade[]) {
|
||||
`).toPromise()
|
||||
}
|
||||
|
||||
// ❌ 個別挿入(低速)
|
||||
// FAIL: 個別挿入(低速)
|
||||
async function insertTrade(trade: Trade) {
|
||||
// ループ内でこれをしないでください!
|
||||
await clickhouse.query(`
|
||||
|
||||
@@ -42,12 +42,12 @@ description: TypeScript、JavaScript、React、Node.js開発のための汎用
|
||||
### 変数の命名
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Descriptive names
|
||||
// PASS: GOOD: Descriptive names
|
||||
const marketSearchQuery = 'election'
|
||||
const isUserAuthenticated = true
|
||||
const totalRevenue = 1000
|
||||
|
||||
// ❌ BAD: Unclear names
|
||||
// FAIL: BAD: Unclear names
|
||||
const q = 'election'
|
||||
const flag = true
|
||||
const x = 1000
|
||||
@@ -56,12 +56,12 @@ const x = 1000
|
||||
### 関数の命名
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Verb-noun pattern
|
||||
// PASS: GOOD: Verb-noun pattern
|
||||
async function fetchMarketData(marketId: string) { }
|
||||
function calculateSimilarity(a: number[], b: number[]) { }
|
||||
function isValidEmail(email: string): boolean { }
|
||||
|
||||
// ❌ BAD: Unclear or noun-only
|
||||
// FAIL: BAD: Unclear or noun-only
|
||||
async function market(id: string) { }
|
||||
function similarity(a, b) { }
|
||||
function email(e) { }
|
||||
@@ -70,7 +70,7 @@ function email(e) { }
|
||||
### 不変性パターン(重要)
|
||||
|
||||
```typescript
|
||||
// ✅ ALWAYS use spread operator
|
||||
// PASS: ALWAYS use spread operator
|
||||
const updatedUser = {
|
||||
...user,
|
||||
name: 'New Name'
|
||||
@@ -78,7 +78,7 @@ const updatedUser = {
|
||||
|
||||
const updatedArray = [...items, newItem]
|
||||
|
||||
// ❌ NEVER mutate directly
|
||||
// FAIL: NEVER mutate directly
|
||||
user.name = 'New Name' // BAD
|
||||
items.push(newItem) // BAD
|
||||
```
|
||||
@@ -86,7 +86,7 @@ items.push(newItem) // BAD
|
||||
### エラーハンドリング
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Comprehensive error handling
|
||||
// PASS: GOOD: Comprehensive error handling
|
||||
async function fetchData(url: string) {
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
@@ -102,7 +102,7 @@ async function fetchData(url: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ BAD: No error handling
|
||||
// FAIL: BAD: No error handling
|
||||
async function fetchData(url) {
|
||||
const response = await fetch(url)
|
||||
return response.json()
|
||||
@@ -112,14 +112,14 @@ async function fetchData(url) {
|
||||
### Async/Awaitベストプラクティス
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Parallel execution when possible
|
||||
// PASS: GOOD: Parallel execution when possible
|
||||
const [users, markets, stats] = await Promise.all([
|
||||
fetchUsers(),
|
||||
fetchMarkets(),
|
||||
fetchStats()
|
||||
])
|
||||
|
||||
// ❌ BAD: Sequential when unnecessary
|
||||
// FAIL: BAD: Sequential when unnecessary
|
||||
const users = await fetchUsers()
|
||||
const markets = await fetchMarkets()
|
||||
const stats = await fetchStats()
|
||||
@@ -128,7 +128,7 @@ const stats = await fetchStats()
|
||||
### 型安全性
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Proper types
|
||||
// PASS: GOOD: Proper types
|
||||
interface Market {
|
||||
id: string
|
||||
name: string
|
||||
@@ -140,7 +140,7 @@ function getMarket(id: string): Promise<Market> {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
// ❌ BAD: Using 'any'
|
||||
// FAIL: BAD: Using 'any'
|
||||
function getMarket(id: any): Promise<any> {
|
||||
// Implementation
|
||||
}
|
||||
@@ -151,7 +151,7 @@ function getMarket(id: any): Promise<any> {
|
||||
### コンポーネント構造
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Functional component with types
|
||||
// PASS: GOOD: Functional component with types
|
||||
interface ButtonProps {
|
||||
children: React.ReactNode
|
||||
onClick: () => void
|
||||
@@ -176,7 +176,7 @@ export function Button({
|
||||
)
|
||||
}
|
||||
|
||||
// ❌ BAD: No types, unclear structure
|
||||
// FAIL: BAD: No types, unclear structure
|
||||
export function Button(props) {
|
||||
return <button onClick={props.onClick}>{props.children}</button>
|
||||
}
|
||||
@@ -185,7 +185,7 @@ export function Button(props) {
|
||||
### カスタムフック
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Reusable custom hook
|
||||
// PASS: GOOD: Reusable custom hook
|
||||
export function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
||||
|
||||
@@ -207,25 +207,25 @@ const debouncedQuery = useDebounce(searchQuery, 500)
|
||||
### 状態管理
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Proper state updates
|
||||
// PASS: GOOD: Proper state updates
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
// Functional update for state based on previous state
|
||||
setCount(prev => prev + 1)
|
||||
|
||||
// ❌ BAD: Direct state reference
|
||||
// FAIL: BAD: Direct state reference
|
||||
setCount(count + 1) // Can be stale in async scenarios
|
||||
```
|
||||
|
||||
### 条件付きレンダリング
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Clear conditional rendering
|
||||
// PASS: GOOD: Clear conditional rendering
|
||||
{isLoading && <Spinner />}
|
||||
{error && <ErrorMessage error={error} />}
|
||||
{data && <DataDisplay data={data} />}
|
||||
|
||||
// ❌ BAD: Ternary hell
|
||||
// FAIL: BAD: Ternary hell
|
||||
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
|
||||
```
|
||||
|
||||
@@ -248,7 +248,7 @@ GET /api/markets?status=active&limit=10&offset=0
|
||||
### レスポンス形式
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Consistent response structure
|
||||
// PASS: GOOD: Consistent response structure
|
||||
interface ApiResponse<T> {
|
||||
success: boolean
|
||||
data?: T
|
||||
@@ -279,7 +279,7 @@ return NextResponse.json({
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
|
||||
// ✅ GOOD: Schema validation
|
||||
// PASS: GOOD: Schema validation
|
||||
const CreateMarketSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
description: z.string().min(1).max(2000),
|
||||
@@ -342,14 +342,14 @@ types/market.types.ts # 型定義は .types サフィックス付き cam
|
||||
### コメントを追加するタイミング
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Explain WHY, not WHAT
|
||||
// PASS: GOOD: Explain WHY, not WHAT
|
||||
// Use exponential backoff to avoid overwhelming the API during outages
|
||||
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
|
||||
|
||||
// Deliberately using mutation here for performance with large arrays
|
||||
items.push(newItem)
|
||||
|
||||
// ❌ BAD: Stating the obvious
|
||||
// FAIL: BAD: Stating the obvious
|
||||
// Increment counter by 1
|
||||
count++
|
||||
|
||||
@@ -389,12 +389,12 @@ export async function searchMarkets(
|
||||
```typescript
|
||||
import { useMemo, useCallback } from 'react'
|
||||
|
||||
// ✅ GOOD: Memoize expensive computations
|
||||
// PASS: GOOD: Memoize expensive computations
|
||||
const sortedMarkets = useMemo(() => {
|
||||
return markets.sort((a, b) => b.volume - a.volume)
|
||||
}, [markets])
|
||||
|
||||
// ✅ GOOD: Memoize callbacks
|
||||
// PASS: GOOD: Memoize callbacks
|
||||
const handleSearch = useCallback((query: string) => {
|
||||
setSearchQuery(query)
|
||||
}, [])
|
||||
@@ -405,7 +405,7 @@ const handleSearch = useCallback((query: string) => {
|
||||
```typescript
|
||||
import { lazy, Suspense } from 'react'
|
||||
|
||||
// ✅ GOOD: Lazy load heavy components
|
||||
// PASS: GOOD: Lazy load heavy components
|
||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||
|
||||
export function Dashboard() {
|
||||
@@ -420,13 +420,13 @@ export function Dashboard() {
|
||||
### データベースクエリ
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Select only needed columns
|
||||
// PASS: GOOD: Select only needed columns
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('id, name, status')
|
||||
.limit(10)
|
||||
|
||||
// ❌ BAD: Select everything
|
||||
// FAIL: BAD: Select everything
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('*')
|
||||
@@ -453,12 +453,12 @@ test('calculates similarity correctly', () => {
|
||||
### テストの命名
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Descriptive test names
|
||||
// PASS: GOOD: Descriptive test names
|
||||
test('returns empty array when no markets match query', () => { })
|
||||
test('throws error when OpenAI API key is missing', () => { })
|
||||
test('falls back to substring search when Redis unavailable', () => { })
|
||||
|
||||
// ❌ BAD: Vague test names
|
||||
// FAIL: BAD: Vague test names
|
||||
test('works', () => { })
|
||||
test('test search', () => { })
|
||||
```
|
||||
@@ -470,12 +470,12 @@ test('test search', () => { })
|
||||
### 1. 長い関数
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: Function > 50 lines
|
||||
// FAIL: BAD: Function > 50 lines
|
||||
function processMarketData() {
|
||||
// 100 lines of code
|
||||
}
|
||||
|
||||
// ✅ GOOD: Split into smaller functions
|
||||
// PASS: GOOD: Split into smaller functions
|
||||
function processMarketData() {
|
||||
const validated = validateData()
|
||||
const transformed = transformData(validated)
|
||||
@@ -486,7 +486,7 @@ function processMarketData() {
|
||||
### 2. 深いネスト
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: 5+ levels of nesting
|
||||
// FAIL: BAD: 5+ levels of nesting
|
||||
if (user) {
|
||||
if (user.isAdmin) {
|
||||
if (market) {
|
||||
@@ -499,7 +499,7 @@ if (user) {
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ GOOD: Early returns
|
||||
// PASS: GOOD: Early returns
|
||||
if (!user) return
|
||||
if (!user.isAdmin) return
|
||||
if (!market) return
|
||||
@@ -512,11 +512,11 @@ if (!hasPermission) return
|
||||
### 3. マジックナンバー
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: Unexplained numbers
|
||||
// FAIL: BAD: Unexplained numbers
|
||||
if (retryCount > 3) { }
|
||||
setTimeout(callback, 500)
|
||||
|
||||
// ✅ GOOD: Named constants
|
||||
// PASS: GOOD: Named constants
|
||||
const MAX_RETRIES = 3
|
||||
const DEBOUNCE_DELAY_MS = 500
|
||||
|
||||
|
||||
@@ -348,7 +348,7 @@ DJANGO 検証レポート
|
||||
✓ ハードコードされたシークレットなし
|
||||
✓ マイグレーションが含まれる
|
||||
|
||||
推奨: ⚠️ デプロイ前にpip-auditの脆弱性を修正してください
|
||||
推奨: WARNING: デプロイ前にpip-auditの脆弱性を修正してください
|
||||
|
||||
次のステップ:
|
||||
1. 脆弱な依存関係を更新
|
||||
|
||||
@@ -12,7 +12,7 @@ React、Next.js、高性能ユーザーインターフェースのためのモ
|
||||
### 継承よりコンポジション
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Component composition
|
||||
// PASS: GOOD: Component composition
|
||||
interface CardProps {
|
||||
children: React.ReactNode
|
||||
variant?: 'default' | 'outlined'
|
||||
@@ -283,17 +283,17 @@ export function useMarkets() {
|
||||
### メモ化
|
||||
|
||||
```typescript
|
||||
// ✅ useMemo for expensive computations
|
||||
// PASS: useMemo for expensive computations
|
||||
const sortedMarkets = useMemo(() => {
|
||||
return markets.sort((a, b) => b.volume - a.volume)
|
||||
}, [markets])
|
||||
|
||||
// ✅ useCallback for functions passed to children
|
||||
// PASS: useCallback for functions passed to children
|
||||
const handleSearch = useCallback((query: string) => {
|
||||
setSearchQuery(query)
|
||||
}, [])
|
||||
|
||||
// ✅ React.memo for pure components
|
||||
// PASS: React.memo for pure components
|
||||
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
||||
return (
|
||||
<div className="market-card">
|
||||
@@ -309,7 +309,7 @@ export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
||||
```typescript
|
||||
import { lazy, Suspense } from 'react'
|
||||
|
||||
// ✅ Lazy load heavy components
|
||||
// PASS: Lazy load heavy components
|
||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
|
||||
|
||||
@@ -504,7 +504,7 @@ export class ErrorBoundary extends React.Component<
|
||||
```typescript
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
|
||||
// ✅ List animations
|
||||
// PASS: List animations
|
||||
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
@@ -523,7 +523,7 @@ export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
||||
)
|
||||
}
|
||||
|
||||
// ✅ Modal animations
|
||||
// PASS: Modal animations
|
||||
export function Modal({ isOpen, onClose, children }: ModalProps) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
|
||||
@@ -27,12 +27,12 @@ description: サブエージェントのコンテキスト問題を解決する
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ DISPATCH │─────▶│ EVALUATE │ │
|
||||
│ │ DISPATCH │─────│ EVALUATE │ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ ▲ │ │
|
||||
│ │ ▼ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ LOOP │◀─────│ REFINE │ │
|
||||
│ │ LOOP │─────│ REFINE │ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ │
|
||||
│ 最大3サイクル、その後続行 │
|
||||
|
||||
@@ -17,22 +17,22 @@ Spring Bootサービスにおける読みやすく保守可能なJava(17+)コー
|
||||
## 命名
|
||||
|
||||
```java
|
||||
// ✅ クラス/レコード: PascalCase
|
||||
// PASS: クラス/レコード: PascalCase
|
||||
public class MarketService {}
|
||||
public record Money(BigDecimal amount, Currency currency) {}
|
||||
|
||||
// ✅ メソッド/フィールド: camelCase
|
||||
// PASS: メソッド/フィールド: camelCase
|
||||
private final MarketRepository marketRepository;
|
||||
public Market findBySlug(String slug) {}
|
||||
|
||||
// ✅ 定数: UPPER_SNAKE_CASE
|
||||
// PASS: 定数: UPPER_SNAKE_CASE
|
||||
private static final int MAX_PAGE_SIZE = 100;
|
||||
```
|
||||
|
||||
## 不変性
|
||||
|
||||
```java
|
||||
// ✅ recordとfinalフィールドを優先
|
||||
// PASS: recordとfinalフィールドを優先
|
||||
public record MarketDto(Long id, String name, MarketStatus status) {}
|
||||
|
||||
public class Market {
|
||||
@@ -45,10 +45,10 @@ public class Market {
|
||||
## Optionalの使用
|
||||
|
||||
```java
|
||||
// ✅ find*メソッドからOptionalを返す
|
||||
// PASS: find*メソッドからOptionalを返す
|
||||
Optional<Market> market = marketRepository.findBySlug(slug);
|
||||
|
||||
// ✅ get()の代わりにmap/flatMapを使用
|
||||
// PASS: get()の代わりにmap/flatMapを使用
|
||||
return market
|
||||
.map(MarketResponse::from)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Market not found"));
|
||||
@@ -57,13 +57,13 @@ return market
|
||||
## ストリームのベストプラクティス
|
||||
|
||||
```java
|
||||
// ✅ 変換にストリームを使用し、パイプラインを短く保つ
|
||||
// PASS: 変換にストリームを使用し、パイプラインを短く保つ
|
||||
List<String> names = markets.stream()
|
||||
.map(Market::name)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
|
||||
// ❌ 複雑なネストされたストリームを避ける; 明確性のためにループを優先
|
||||
// FAIL: 複雑なネストされたストリームを避ける; 明確性のためにループを優先
|
||||
```
|
||||
|
||||
## 例外
|
||||
|
||||
@@ -21,13 +21,13 @@ description: 認証の追加、ユーザー入力の処理、シークレット
|
||||
|
||||
### 1. シークレット管理
|
||||
|
||||
#### ❌ 絶対にしないこと
|
||||
#### FAIL: 絶対にしないこと
|
||||
```typescript
|
||||
const apiKey = "sk-proj-xxxxx" // ハードコードされたシークレット
|
||||
const dbPassword = "password123" // ソースコード内
|
||||
```
|
||||
|
||||
#### ✅ 常にすること
|
||||
#### PASS: 常にすること
|
||||
```typescript
|
||||
const apiKey = process.env.OPENAI_API_KEY
|
||||
const dbUrl = process.env.DATABASE_URL
|
||||
@@ -107,14 +107,14 @@ function validateFileUpload(file: File) {
|
||||
|
||||
### 3. SQLインジェクション防止
|
||||
|
||||
#### ❌ 絶対にSQLを連結しない
|
||||
#### FAIL: 絶対にSQLを連結しない
|
||||
```typescript
|
||||
// 危険 - SQLインジェクションの脆弱性
|
||||
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
|
||||
await db.query(query)
|
||||
```
|
||||
|
||||
#### ✅ 常にパラメータ化されたクエリを使用
|
||||
#### PASS: 常にパラメータ化されたクエリを使用
|
||||
```typescript
|
||||
// 安全 - パラメータ化されたクエリ
|
||||
const { data } = await supabase
|
||||
@@ -139,10 +139,10 @@ await db.query(
|
||||
|
||||
#### JWTトークン処理
|
||||
```typescript
|
||||
// ❌ 誤り:localStorage(XSSに脆弱)
|
||||
// FAIL: 誤り:localStorage(XSSに脆弱)
|
||||
localStorage.setItem('token', token)
|
||||
|
||||
// ✅ 正解:httpOnly Cookie
|
||||
// PASS: 正解:httpOnly Cookie
|
||||
res.setHeader('Set-Cookie',
|
||||
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
|
||||
```
|
||||
@@ -299,18 +299,18 @@ app.use('/api/search', searchLimiter)
|
||||
|
||||
#### ロギング
|
||||
```typescript
|
||||
// ❌ 誤り:機密データをログに記録
|
||||
// FAIL: 誤り:機密データをログに記録
|
||||
console.log('User login:', { email, password })
|
||||
console.log('Payment:', { cardNumber, cvv })
|
||||
|
||||
// ✅ 正解:機密データを編集
|
||||
// PASS: 正解:機密データを編集
|
||||
console.log('User login:', { email, userId })
|
||||
console.log('Payment:', { last4: card.last4, userId })
|
||||
```
|
||||
|
||||
#### エラーメッセージ
|
||||
```typescript
|
||||
// ❌ 誤り:内部詳細を露出
|
||||
// FAIL: 誤り:内部詳細を露出
|
||||
catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: error.message, stack: error.stack },
|
||||
@@ -318,7 +318,7 @@ catch (error) {
|
||||
)
|
||||
}
|
||||
|
||||
// ✅ 正解:一般的なエラーメッセージ
|
||||
// PASS: 正解:一般的なエラーメッセージ
|
||||
catch (error) {
|
||||
console.error('Internal error:', error)
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#### 最小権限の原則
|
||||
|
||||
```yaml
|
||||
# ✅ 正解:最小限の権限
|
||||
# PASS: 正解:最小限の権限
|
||||
iam_role:
|
||||
permissions:
|
||||
- s3:GetObject # 読み取りアクセスのみ
|
||||
@@ -32,7 +32,7 @@ iam_role:
|
||||
resources:
|
||||
- arn:aws:s3:::my-bucket/* # 特定のバケットのみ
|
||||
|
||||
# ❌ 誤り:過度に広範な権限
|
||||
# FAIL: 誤り:過度に広範な権限
|
||||
iam_role:
|
||||
permissions:
|
||||
- s3:* # すべてのS3アクション
|
||||
@@ -65,14 +65,14 @@ aws iam enable-mfa-device \
|
||||
#### クラウドシークレットマネージャー
|
||||
|
||||
```typescript
|
||||
// ✅ 正解:クラウドシークレットマネージャーを使用
|
||||
// PASS: 正解:クラウドシークレットマネージャーを使用
|
||||
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
|
||||
|
||||
const client = new SecretsManager({ region: 'us-east-1' });
|
||||
const secret = await client.getSecretValue({ SecretId: 'prod/api-key' });
|
||||
const apiKey = JSON.parse(secret.SecretString).key;
|
||||
|
||||
// ❌ 誤り:ハードコードまたは環境変数のみ
|
||||
// FAIL: 誤り:ハードコードまたは環境変数のみ
|
||||
const apiKey = process.env.API_KEY; // ローテーションされず、監査されない
|
||||
```
|
||||
|
||||
@@ -99,7 +99,7 @@ aws secretsmanager rotate-secret \
|
||||
#### VPCとファイアウォール設定
|
||||
|
||||
```terraform
|
||||
# ✅ 正解:制限されたセキュリティグループ
|
||||
# PASS: 正解:制限されたセキュリティグループ
|
||||
resource "aws_security_group" "app" {
|
||||
name = "app-sg"
|
||||
|
||||
@@ -118,7 +118,7 @@ resource "aws_security_group" "app" {
|
||||
}
|
||||
}
|
||||
|
||||
# ❌ 誤り:インターネットに公開
|
||||
# FAIL: 誤り:インターネットに公開
|
||||
resource "aws_security_group" "bad" {
|
||||
ingress {
|
||||
from_port = 0
|
||||
@@ -142,7 +142,7 @@ resource "aws_security_group" "bad" {
|
||||
#### CloudWatch/ロギング設定
|
||||
|
||||
```typescript
|
||||
// ✅ 正解:包括的なロギング
|
||||
// PASS: 正解:包括的なロギング
|
||||
import { CloudWatchLogsClient, CreateLogStreamCommand } from '@aws-sdk/client-cloudwatch-logs';
|
||||
|
||||
const logSecurityEvent = async (event: SecurityEvent) => {
|
||||
@@ -177,7 +177,7 @@ const logSecurityEvent = async (event: SecurityEvent) => {
|
||||
#### 安全なパイプライン設定
|
||||
|
||||
```yaml
|
||||
# ✅ 正解:安全なGitHub Actionsワークフロー
|
||||
# PASS: 正解:安全なGitHub Actionsワークフロー
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
@@ -237,7 +237,7 @@ jobs:
|
||||
#### Cloudflareセキュリティ設定
|
||||
|
||||
```typescript
|
||||
// ✅ 正解:セキュリティヘッダー付きCloudflare Workers
|
||||
// PASS: 正解:セキュリティヘッダー付きCloudflare Workers
|
||||
export default {
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const response = await fetch(request);
|
||||
@@ -281,7 +281,7 @@ export default {
|
||||
#### 自動バックアップ
|
||||
|
||||
```terraform
|
||||
# ✅ 正解:自動RDSバックアップ
|
||||
# PASS: 正解:自動RDSバックアップ
|
||||
resource "aws_db_instance" "main" {
|
||||
allocated_storage = 20
|
||||
engine = "postgres"
|
||||
@@ -327,10 +327,10 @@ resource "aws_db_instance" "main" {
|
||||
### S3バケットの露出
|
||||
|
||||
```bash
|
||||
# ❌ 誤り:公開バケット
|
||||
# FAIL: 誤り:公開バケット
|
||||
aws s3api put-bucket-acl --bucket my-bucket --acl public-read
|
||||
|
||||
# ✅ 正解:特定のアクセス付きプライベートバケット
|
||||
# PASS: 正解:特定のアクセス付きプライベートバケット
|
||||
aws s3api put-bucket-acl --bucket my-bucket --acl private
|
||||
aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
||||
```
|
||||
@@ -338,12 +338,12 @@ aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
||||
### RDS公開アクセス
|
||||
|
||||
```terraform
|
||||
# ❌ 誤り
|
||||
# FAIL: 誤り
|
||||
resource "aws_db_instance" "bad" {
|
||||
publicly_accessible = true # 絶対にこれをしない!
|
||||
}
|
||||
|
||||
# ✅ 正解
|
||||
# PASS: 正解
|
||||
resource "aws_db_instance" "good" {
|
||||
publicly_accessible = false
|
||||
vpc_security_group_ids = [aws_security_group.db.id]
|
||||
|
||||
@@ -313,39 +313,39 @@ npm run test:coverage
|
||||
|
||||
## 避けるべき一般的なテストの誤り
|
||||
|
||||
### ❌ 誤り:実装の詳細をテスト
|
||||
### FAIL: 誤り:実装の詳細をテスト
|
||||
```typescript
|
||||
// 内部状態をテストしない
|
||||
expect(component.state.count).toBe(5)
|
||||
```
|
||||
|
||||
### ✅ 正解:ユーザーに見える動作をテスト
|
||||
### PASS: 正解:ユーザーに見える動作をテスト
|
||||
```typescript
|
||||
// ユーザーが見るものをテスト
|
||||
expect(screen.getByText('Count: 5')).toBeInTheDocument()
|
||||
```
|
||||
|
||||
### ❌ 誤り:脆弱なセレクタ
|
||||
### FAIL: 誤り:脆弱なセレクタ
|
||||
```typescript
|
||||
// 簡単に壊れる
|
||||
await page.click('.css-class-xyz')
|
||||
```
|
||||
|
||||
### ✅ 正解:セマンティックセレクタ
|
||||
### PASS: 正解:セマンティックセレクタ
|
||||
```typescript
|
||||
// 変更に強い
|
||||
await page.click('button:has-text("Submit")')
|
||||
await page.click('[data-testid="submit-button"]')
|
||||
```
|
||||
|
||||
### ❌ 誤り:テストの分離なし
|
||||
### FAIL: 誤り:テストの分離なし
|
||||
```typescript
|
||||
// テストが互いに依存
|
||||
test('creates user', () => { /* ... */ })
|
||||
test('updates same user', () => { /* 前のテストに依存 */ })
|
||||
```
|
||||
|
||||
### ✅ 正解:独立したテスト
|
||||
### PASS: 正解:独立したテスト
|
||||
```typescript
|
||||
// 各テストが独自のデータをセットアップ
|
||||
test('creates user', () => {
|
||||
|
||||
Reference in New Issue
Block a user