Skip to content

Commit 0233c5d

Browse files
junzero741claude
andcommitted
fix: add rate limits for upload/admin and render post content safely in admin
- 이미지 업로드: IP당 시간당 20회 제한 (스토리지 어뷰징 방어) - admin API: IP당 15분당 20회 제한 (brute-force 방어) - admin 페이지: post.content를 DOMPurify로 sanitize 후 렌더링 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e654f35 commit 0233c5d

2 files changed

Lines changed: 26 additions & 0 deletions

File tree

apps/backend/src/main.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,26 @@ const unlockLimiter = rateLimit({
3939
});
4040
app.use('/posts/:slug/unlock', unlockLimiter);
4141

42+
// 이미지 업로드 rate limit: IP당 시간당 20회 (스토리지 어뷰징 방어)
43+
const uploadLimiter = rateLimit({
44+
windowMs: 60 * 60 * 1000,
45+
limit: 20,
46+
standardHeaders: 'draft-8',
47+
legacyHeaders: false,
48+
message: { error: 'Too many requests, please try again later' },
49+
});
50+
app.use('/uploads/image', uploadLimiter);
51+
52+
// admin rate limit: IP당 15분당 20회 (brute-force 방어)
53+
const adminLimiter = rateLimit({
54+
windowMs: 15 * 60 * 1000,
55+
limit: 20,
56+
standardHeaders: 'draft-8',
57+
legacyHeaders: false,
58+
message: { error: 'Too many requests, please try again later' },
59+
});
60+
app.use('/admin', adminLimiter);
61+
4262
// 정적 파일 서빙 (라우터보다 먼저 등록 — 파일 없으면 next()로 통과)
4363
const uploadsDir = path.resolve(__dirname, '../uploads');
4464
app.use('/uploads', express.static(uploadsDir));

apps/frontend/src/app/admin/page.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback } from 'react';
44
import { ReportReason } from '@private-board/shared';
55
import { REPORT_REASON_LABELS } from '@/lib/constants';
66
import { AdminReport, getAdminReports, adminDeletePost, adminDismissReport } from '@/lib/api';
7+
import { sanitizeHtml } from '@/lib/sanitize';
78

89
export default function AdminPage() {
910
const [secret, setSecret] = useState('');
@@ -148,6 +149,11 @@ export default function AdminPage() {
148149
</span>
149150
</div>
150151

152+
<article
153+
className="mb-3 max-h-48 overflow-y-auto rounded-lg bg-surface p-3 prose prose-sm prose-slate max-w-none prose-headings:text-text-primary prose-p:text-text-primary prose-img:rounded"
154+
dangerouslySetInnerHTML={{ __html: sanitizeHtml(report.post.content) }}
155+
/>
156+
151157
{report.description && (
152158
<p className="mb-3 text-sm text-text-secondary">{report.description}</p>
153159
)}

0 commit comments

Comments
 (0)