fix: harden unicode safety checks

This commit is contained in:
Affaan Mustafa
2026-03-29 08:59:06 -04:00
parent dd675d4258
commit 866d9ebb53
239 changed files with 3780 additions and 3962 deletions

View File

@@ -23,7 +23,7 @@ origin: ECC
### RESTful API 구조
```typescript
// Resource-based URLs
// PASS: Resource-based URLs
GET /api/markets # List resources
GET /api/markets/:id # Get single resource
POST /api/markets # Create resource
@@ -31,7 +31,7 @@ PUT /api/markets/:id # Replace resource
PATCH /api/markets/:id # Update resource
DELETE /api/markets/:id # Delete resource
// Query parameters for filtering, sorting, pagination
// PASS: Query parameters for filtering, sorting, pagination
GET /api/markets?status=active&sort=volume&limit=20&offset=0
```
@@ -132,7 +132,7 @@ export default withAuth(async (req, res) => {
### 쿼리 최적화
```typescript
// GOOD: Select only needed columns
// PASS: GOOD: Select only needed columns
const { data } = await supabase
.from('markets')
.select('id, name, status, volume')
@@ -140,7 +140,7 @@ const { data } = await supabase
.order('volume', { ascending: false })
.limit(10)
// BAD: Select everything
// FAIL: BAD: Select everything
const { data } = await supabase
.from('markets')
.select('*')
@@ -149,13 +149,13 @@ const { data } = await supabase
### N+1 쿼리 방지
```typescript
// BAD: N+1 query problem
// FAIL: BAD: N+1 query problem
const markets = await getMarkets()
for (const market of markets) {
market.creator = await getUser(market.creator_id) // N queries
}
// GOOD: Batch fetch
// PASS: GOOD: Batch fetch
const markets = await getMarkets()
const creatorIds = markets.map(m => m.creator_id)
const creators = await getUsers(creatorIds) // 1 query

View File

@@ -96,7 +96,7 @@ ORDER BY hour DESC;
### 효율적인 필터링
```sql
-- 좋음: 인덱스된 컬럼을 먼저 사용
-- PASS: 좋음: 인덱스된 컬럼을 먼저 사용
SELECT *
FROM markets_analytics
WHERE date >= '2025-01-01'
@@ -105,7 +105,7 @@ WHERE date >= '2025-01-01'
ORDER BY date DESC
LIMIT 100;
-- 나쁨: 비인덱스 컬럼을 먼저 필터링
-- FAIL: 나쁨: 비인덱스 컬럼을 먼저 필터링
SELECT *
FROM markets_analytics
WHERE volume > 1000
@@ -116,7 +116,7 @@ WHERE volume > 1000
### 집계
```sql
-- 좋음: ClickHouse 전용 집계 함수를 사용
-- PASS: 좋음: ClickHouse 전용 집계 함수를 사용
SELECT
toStartOfDay(created_at) AS day,
market_id,
@@ -129,7 +129,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,
@@ -172,7 +172,7 @@ const clickhouse = new ClickHouse({
}
})
// 배치 삽입 (효율적)
// PASS: 배치 삽입 (효율적)
async function bulkInsertTrades(trades: Trade[]) {
const rows = trades.map(trade => ({
id: trade.id,
@@ -185,7 +185,7 @@ async function bulkInsertTrades(trades: Trade[]) {
await clickhouse.insert('trades', rows)
}
// 개별 삽입 (느림)
// FAIL: 개별 삽입 (느림)
async function insertTrade(trade: Trade) {
// 루프 안에서 이렇게 하지 마세요!
await clickhouse.query(`

View File

@@ -48,12 +48,12 @@ origin: ECC
### 변수 네이밍
```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
@@ -62,12 +62,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) { }
@@ -76,7 +76,7 @@ function email(e) { }
### 불변성 패턴 (필수)
```typescript
// ALWAYS use spread operator
// PASS: ALWAYS use spread operator
const updatedUser = {
...user,
name: 'New Name'
@@ -84,7 +84,7 @@ const updatedUser = {
const updatedArray = [...items, newItem]
// NEVER mutate directly
// FAIL: NEVER mutate directly
user.name = 'New Name' // BAD
items.push(newItem) // BAD
```
@@ -92,7 +92,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)
@@ -108,7 +108,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()
@@ -118,14 +118,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()
@@ -134,7 +134,7 @@ const stats = await fetchStats()
### 타입 안전성
```typescript
// GOOD: Proper types
// PASS: GOOD: Proper types
interface Market {
id: string
name: string
@@ -146,7 +146,7 @@ function getMarket(id: string): Promise<Market> {
// Implementation
}
// BAD: Using 'any'
// FAIL: BAD: Using 'any'
function getMarket(id: any): Promise<any> {
// Implementation
}
@@ -157,7 +157,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
@@ -182,7 +182,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>
}
@@ -191,7 +191,7 @@ export function Button(props) {
### 커스텀 Hook
```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)
@@ -213,25 +213,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}
```
@@ -254,7 +254,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
@@ -285,7 +285,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),
@@ -348,14 +348,14 @@ types/market.types.ts # camelCase with .types suffix
### 주석을 작성해야 하는 경우
```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++
@@ -395,12 +395,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)
}, [])
@@ -411,7 +411,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() {
@@ -426,13 +426,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('*')
@@ -459,12 +459,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', () => { })
```
@@ -475,12 +475,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)
@@ -490,7 +490,7 @@ function processMarketData() {
### 2. 깊은 중첩
```typescript
// BAD: 5+ levels of nesting
// FAIL: BAD: 5+ levels of nesting
if (user) {
if (user.isAdmin) {
if (market) {
@@ -503,7 +503,7 @@ if (user) {
}
}
// GOOD: Early returns
// PASS: GOOD: Early returns
if (!user) return
if (!user.isAdmin) return
if (!market) return
@@ -515,11 +515,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

View File

@@ -23,7 +23,7 @@ React, Next.js 및 고성능 사용자 인터페이스를 위한 모던 프론
### 상속보다 합성
```typescript
// GOOD: Component composition
// PASS: GOOD: Component composition
interface CardProps {
children: React.ReactNode
variant?: 'default' | 'outlined'
@@ -304,17 +304,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">
@@ -330,7 +330,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'))
@@ -525,7 +525,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>
@@ -544,7 +544,7 @@ export function AnimatedMarketList({ markets }: { markets: Market[] }) {
)
}
// Modal animations
// PASS: Modal animations
export function Modal({ isOpen, onClose, children }: ModalProps) {
return (
<AnimatePresence>

View File

@@ -36,12 +36,12 @@ origin: ECC
┌─────────────────────────────────────────────┐
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ DISPATCH │─────│ EVALUATE │ │
│ │ DISPATCH │─────│ EVALUATE │ │
│ └──────────┘ └──────────┘ │
│ ▲ │ │
│ │ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ LOOP │─────│ REFINE │ │
│ │ LOOP │─────│ REFINE │ │
│ └──────────┘ └──────────┘ │
│ │
│ Max 3 cycles, then proceed │

View File

@@ -140,10 +140,10 @@ await db.query(
#### JWT 토큰 처리
```typescript
// WRONG: localStorage (vulnerable to XSS)
// FAIL: WRONG: localStorage (vulnerable to XSS)
localStorage.setItem('token', token)
// CORRECT: httpOnly cookies
// PASS: CORRECT: httpOnly cookies
res.setHeader('Set-Cookie',
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
```
@@ -302,18 +302,18 @@ app.use('/api/search', searchLimiter)
#### 로깅
```typescript
// WRONG: Logging sensitive data
// FAIL: WRONG: Logging sensitive data
console.log('User login:', { email, password })
console.log('Payment:', { cardNumber, cvv })
// CORRECT: Redact sensitive data
// PASS: CORRECT: Redact sensitive data
console.log('User login:', { email, userId })
console.log('Payment:', { last4: card.last4, userId })
```
#### 에러 메시지
```typescript
// WRONG: Exposing internal details
// FAIL: WRONG: Exposing internal details
catch (error) {
return NextResponse.json(
{ error: error.message, stack: error.stack },
@@ -321,7 +321,7 @@ catch (error) {
)
}
// CORRECT: Generic error messages
// PASS: CORRECT: Generic error messages
catch (error) {
console.error('Internal error:', error)
return NextResponse.json(

View File

@@ -24,7 +24,7 @@
#### 최소 권한 원칙
```yaml
# CORRECT: Minimal permissions
# PASS: CORRECT: Minimal permissions
iam_role:
permissions:
- s3:GetObject # Only read access
@@ -32,7 +32,7 @@ iam_role:
resources:
- arn:aws:s3:::my-bucket/* # Specific bucket only
# WRONG: Overly broad permissions
# FAIL: WRONG: Overly broad permissions
iam_role:
permissions:
- s3:* # All S3 actions
@@ -65,14 +65,14 @@ aws iam enable-mfa-device \
#### 클라우드 시크릿 매니저
```typescript
// CORRECT: Use cloud secrets manager
// PASS: CORRECT: Use cloud secrets manager
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;
// WRONG: Hardcoded or in environment variables only
// FAIL: WRONG: Hardcoded or in environment variables only
const apiKey = process.env.API_KEY; // Not rotated, not audited
```
@@ -99,7 +99,7 @@ aws secretsmanager rotate-secret \
#### VPC 및 방화벽 구성
```terraform
# CORRECT: Restricted security group
# PASS: CORRECT: Restricted security group
resource "aws_security_group" "app" {
name = "app-sg"
@@ -118,7 +118,7 @@ resource "aws_security_group" "app" {
}
}
# WRONG: Open to the internet
# FAIL: WRONG: Open to the internet
resource "aws_security_group" "bad" {
ingress {
from_port = 0
@@ -142,7 +142,7 @@ resource "aws_security_group" "bad" {
#### CloudWatch/로깅 구성
```typescript
// CORRECT: Comprehensive logging
// PASS: CORRECT: Comprehensive logging
import { CloudWatchLogsClient, CreateLogStreamCommand } from '@aws-sdk/client-cloudwatch-logs';
const logSecurityEvent = async (event: SecurityEvent) => {
@@ -177,7 +177,7 @@ const logSecurityEvent = async (event: SecurityEvent) => {
#### 보안 파이프라인 구성
```yaml
# CORRECT: Secure GitHub Actions workflow
# PASS: CORRECT: Secure GitHub Actions workflow
name: Deploy
on:
@@ -237,7 +237,7 @@ jobs:
#### Cloudflare 보안 구성
```typescript
// CORRECT: Cloudflare Workers with security headers
// PASS: CORRECT: Cloudflare Workers with security headers
export default {
async fetch(request: Request): Promise<Response> {
const response = await fetch(request);
@@ -281,7 +281,7 @@ export default {
#### 자동 백업
```terraform
# CORRECT: Automated RDS backups
# PASS: CORRECT: Automated RDS backups
resource "aws_db_instance" "main" {
allocated_storage = 20
engine = "postgres"
@@ -327,10 +327,10 @@ resource "aws_db_instance" "main" {
### S3 버킷 노출
```bash
# WRONG: Public bucket
# FAIL: WRONG: Public bucket
aws s3api put-bucket-acl --bucket my-bucket --acl public-read
# CORRECT: Private bucket with specific access
# PASS: CORRECT: Private bucket with specific access
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
# WRONG
# FAIL: WRONG
resource "aws_db_instance" "bad" {
publicly_accessible = true # NEVER do this!
}
# CORRECT
# PASS: CORRECT
resource "aws_db_instance" "good" {
publicly_accessible = false
vpc_security_group_ids = [aws_security_group.db.id]