FixVibe

// docs / security guides / hardening

AI 코딩 도구로 만든 앱 보안 가이드

Cursor, Claude Code, Lovable, Bolt, v0, Replit 또는 Windsurf로 구축한 앱에 대한 단계별 강화 가이드입니다. 4단계: AI- 생성된 앱이 다르게 실패하는 이유를 이해하고, 즉시 코드베이스 감사를 실행하고, 배포 시 강화하고, 계속 모니터링합니다. 자신의 주장이 있고 서술적이며 실제 내용을 복사할 수 있습니다.

AI- 생성된 앱이 다르게 실패하는 이유

Vibe로 코딩된 앱은 안전할 수 있습니다. 실패 모드는 부주의한 것이 아니라 구조적인 것이기 때문에 추가 감사 통과가 필요합니다.

  • Cursor inlines hardcoded keys. Cursor에게 "인증 오류 수정"을 요청하면 서비스 역할 클라이언트를 가정하는 Supabase 예제를 붙여넣습니다. 키는 페이지 구성 요소의 맨 위에 위치합니다. 익명 클라이언트와 서비스 클라이언트가 모두 공존합니다. 둘 다 배.
  • Claude Code defaults to permissive CORS. 생성된 Express / Fastify 핸들러는 작업 미리보기를 얻는 가장 빠른 방법이기 때문에 cors({ origin: '*' })과 함께 제공됩니다. 미들웨어는 결코 두 번째 패스를 얻지 못합니다.
  • Lovable and v0 skip the rules file. Firestore 지원 프로젝트는 데이터 모델을 생성하지만 firestore.rules을 거의 건드리지 않습니다. 테스트 모드 규칙은 자동으로 만료되며 사용자에게 경고하지 않고 데이터베이스를 잠급니다.
  • Bolt skips RLS migrations. Bolt은 Supabase 스키마와 anon 키를 사용하는 CRUD 표면을 생성합니다. ENABLE ROW LEVEL SECURITY은 마이그레이션에 참여하지 않습니다. 익명 사용자는 모든 행을 읽거나 쓸 수 있습니다.
  • Windsurf trusts unsigned IDs. 생성됨 GET /api/items/[id]은 소유권을 확인하지 않고 매개변수를 읽고 Postgres에 쿼리합니다. 패턴은 활성 active.idor-walking 프로브가 단일 스캔 내에서 패턴을 표면화할 만큼 충분히 일반적입니다.

즉각적인 감사: 위험 패턴에 대한 코드베이스 수집

무엇이든 굳히기 전에 이미 깨진 것이 무엇인지 찾아보세요. 이러한 grep은 각각 1분 미만이 소요됩니다.

비밀 및 공급자 키

bash
grep -RIn 'NEXT_PUBLIC_SUPABASE_SERVICE' src/
grep -RIn 'sk_live_\|pk_live_\|STRIPE_SECRET' src/
grep -RIn 'sk-ant-\|^sk-' src/  # Anthropic / OpenAI
grep -RIn 'AIza\|AKIA' src/        # Google / AWS
grep -RIn 'eyJh[A-Za-z0-9_-]\{20,\}' src/  # JWT-shaped strings

모든 적중에는 삭제와 키 순환이 필요합니다. Provider 대시보드: Supabase → 설정 → API, Stripe → 개발자 → API 키, Anthropic / OpenAI 콘솔.

데이터베이스 액세스 제어

bash
# Supabase migrations
grep -RIn 'CREATE TABLE public\.' supabase/migrations/
grep -RIn 'ENABLE ROW LEVEL SECURITY\|FORCE ROW LEVEL SECURITY' supabase/migrations/

# Firebase / Firestore
cat firestore.rules  # confirm no `if true;` matches

모든 CREATE TABLE public.*에는 일치하는 ENABLE ROW LEVEL SECURITY과 하나 이상의 정책이 필요합니다. Firestore 규칙은 읽기 범위를 request.auth.uid으로 지정해야 합니다.

인증 및 세션 처리

bash
grep -RIn 'getSession()' src/   # should be getUser() server-side
grep -RIn 'localStorage\.\(set\|get\)Item.*token' src/
grep -RIn 'jwt.verify.*\(noVerify\|skipVerify\)' src/

서버 렌더링 경로는 supabase.auth.getUser()을 사용해야 합니다. 이는 백엔드에서 확인됩니다. getSession()은 확인되지 않은 쿠키를 읽습니다. localStorage의 토큰은 페이지에서 실행되는 모든 스크립트에 액세스할 수 있습니다.

헤더 및 미들웨어

bash
# Confirm middleware location for src/ layouts
ls src/middleware.ts middleware.ts 2>&1

# Look for CSP and security headers
grep -RIn 'Content-Security-Policy\|Strict-Transport-Security' src/

src/ 레이아웃에서는 src/middleware.ts만 선택됩니다. 미들웨어 파일이 프로젝트 루트에 있는 경우 Next.js은 이를 자동으로 무시하고 CSP / auth-refresh 논리는 실행되지 않습니다.

배포 시 강화

소스가 정리되면 앱이 프로덕션에 도달하는 방법을 잠급니다.

1단계: 별도의 환경

Vercel: 세 가지 환경 — Production(프로드 도메인), 미리 보기(PR / 스테이징 배포), 개발(로컬) 각각은 자체 env-var 세트를 갖습니다. 라이브 Stripe / Anthropic / Supabase 키는 미리보기에 도달하지 않습니다. 미리보기 키는 Production에 도달하지 않습니다. 분기는 자동으로 미리보기로 푸시됩니다. main에 병합하면 Production에 배포됩니다.

2단계: 미들웨어를 통한 엄격한 CSP

요청별 nonce를 생성한 다음 Content-Security-Policy에 삽입합니다. x-nonce 요청 헤더를 설정할 때 Next.js은 자체 스크립트 태그에 nonce를 자동 적용합니다.

ts
// src/middleware.ts
import { NextResponse, type NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const nonce = crypto.randomUUID().replace(/-/g, '');
  const csp = [
    `script-src 'nonce-${nonce}' 'strict-dynamic'`,
    `style-src 'self' 'unsafe-inline'`,
    `img-src 'self' data: https:`,
    `connect-src 'self' https://*.supabase.co`,
    `object-src 'none'`,
    `base-uri 'self'`,
    `frame-ancestors 'none'`,
  ].join('; ');

  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-nonce', nonce);

  const response = NextResponse.next({ request: { headers: requestHeaders } });
  response.headers.set('Content-Security-Policy', csp);
  response.headers.set('X-Content-Type-Options', 'nosniff');
  response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  return response;
}

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

3단계: 모든 공개 테이블에 RLS 강제 적용

RLS은 기본적으로 활성화되어 있지 않으며 FORCE을 사용하지 않는 한 테이블 소유자에게 적용되지 않습니다. 각 테이블을 역할별 명시적 정책과 연결합니다.

sql
-- supabase/migrations/XXXX_rls.sql
alter table public.profiles enable row level security;
alter table public.profiles force row level security;

create policy "profiles: read own"
  on public.profiles for select
  using (auth.uid() = id);

create policy "profiles: update own"
  on public.profiles for update
  using (auth.uid() = id)
  with check (auth.uid() = id);

4단계: 모든 API 경로에서 서버 전용 인증 확인

모든 상태 변경 API 경로는 supabase.auth.getUser()을 사용하여 호출자 서버 측을 확인합니다. 사용자 개체는 user_id의 정보 소스가 됩니다. 이를 설정하는 요청 본문을 신뢰하지 마십시오.

ts
// src/app/api/items/route.ts
import { NextResponse, type NextRequest } from 'next/server';
import { createClient } from '@/lib/supabase/server';

export async function POST(request: NextRequest) {
  const supabase = await createClient();
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) return NextResponse.json({ error: 'unauthorized' }, { status: 401 });

  const body = await request.json();
  const { data, error } = await supabase
    .from('items')
    .insert({ ...body, user_id: user.id })  // server-supplied, not from body
    .select()
    .single();

  if (error) return NextResponse.json({ error: error.message }, { status: 400 });
  return NextResponse.json(data);
}

5단계: 분석 역방향 프록시

자신의 도메인을 통한 Proxying 분석을 통해 광고 차단기를 방지하고 CSP connect-src 'self'의 범위를 좁힐 수 있습니다. PostHog, Plausible, Umami, 사용자 지정 이벤트 싱크에도 동일한 패턴이 적용됩니다.

ts
// src/app/api/posthog/[...path]/route.ts
import { type NextRequest } from 'next/server';

const UPSTREAM = 'https://us.i.posthog.com';

export async function POST(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
  const { path } = await params;
  const url = `${UPSTREAM}/${path.join('/')}`;
  return fetch(url, {
    method: 'POST',
    headers: { 'content-type': req.headers.get('content-type') ?? 'application/json' },
    body: await req.text(),
  });
}

6단계: 인증 후 바운스에 대한 공개 리디렉션 가드

로그인/가입 흐름은 일반적으로 next 쿼리 매개변수를 허용합니다. 동일한 사이트 경로가 아닌 것은 거부합니다. /으로 시작하고 절대 //(프로토콜 기준, 사용자를 오프사이트로 보냅니다)로 시작하지 마세요.

ts
function safeNext(raw: string | null): string {
  if (!raw) return '/dashboard';
  if (!raw.startsWith('/') || raw.startsWith('//')) return '/dashboard';
  return raw;
}

진행 중: 모니터링 및 재스캔 중

배포할 때마다 드리프트가 발생합니다. 보안을 완료하는 체크리스트가 아닌 루프로 다루십시오.

프로덕션 도메인 확인

Dashboard → Domains → prod 도메인 추가 → DNS TXT 또는 HTTP- 파일 확인(단일 단계). 확인되면 활성 검사를 사용할 수 있게 되며 예약된 재검사를 활성화할 수 있습니다.

수동 재검사 예약

매일 Hobby, 3시간마다 Pro, 매시간 Unlimited. 각 실행에서 새로운 결과가 나타나면 이메일을 보내고 구독한 경우 scan.completed 웹훅을 실행합니다.

bash
# Or from CI, via the REST API:
curl -X POST https://fixvibe.app/api/v1/scans \
  -H "authorization: Bearer $FIXVIBE_TOKEN" \
  -H "content-type: application/json" \
  -d '{"target":"https://your-app.com"}'

API-활성 검색 활성화(선택 사항)

자동화된 활성 프로브(SQLi / XSS / IDOR 걷기 / 등)를 원하는 경우 대시보드 → 도메인 → API 활성에서 도메인별로 활성화하세요. 승인은 지속적이고 90일 만료되며 즉시 취소 가능합니다. 활성화 후 첫 번째 자동 활성 검색이 경고에 도달하도록 scan.active_api.first_used 웹후크와 페어링하세요.

결과를 AI 워크플로에 연결

Mint an API token at Account → API tokens, then configure the MCP server (/docs/mcp) in Claude Desktop / Cursor / Continue. Ask your agent: "Run a scan on staging and show me the highest-severity findings." The agent calls FixVibe, fetches the report, and renders categorized remediation guidance so code/config fixes become prompts and DNS/provider/manual fixes become operator steps.

실시간 위협 감지(Unlimited)

인증서 투명성 로그 차이점은 도메인에 대해 발급된 새로운 TLS 인증서를 표시합니다. DNS-record diff는 승인되지 않은 변경 사항을 포착합니다. JS-bundle 비밀 모니터링은 새 키가 배송된 번들에 도달하는 순간 실행됩니다. 위협 정보 피드(Spamhaus, URLhaus)는 도메인이 목록에 있는 경우 이를 보고합니다.

실제 실패 패턴 및 수정 사항

AI- 생성된 수천 개의 앱에 대한 프로덕션 스캔의 5가지 패턴(각각 실제 수정 사항 포함):

  1. 클라이언트 구성 요소의 서비스 역할 키

    Symptom: baas.supabase-service-key 제작진의 발견 URL. Cause: Cursor 자동 완성을 통해 createClient(URL, SERVICE_ROLE_KEY)을 React 구성 요소에 붙여넣었습니다. Fix: 서비스 클라이언트를 import 'server-only'이 맨 위에 있는 src/lib/supabase/service.ts으로 이동합니다. 클라이언트측 사용을 위해 익명 키를 사용하여 병렬 src/lib/supabase/client.ts을 생성합니다. Supabase Studio를 통해 서비스 역할 키를 순환합니다.

  2. 테스트 모드에 남아 있는 Firestore 규칙

    Symptom: baas.firebase-rules 심각도가 높은 결과입니다. Cause: 생성된 규칙은 allow read, write: if request.time < timestamp.date(2026, 6, 1);을 읽습니다. 시간 제한이 있는 "모두 허용"입니다. Fix: 각 규칙의 범위를 인증된 사용자(match /users/{userId}/posts/{postId} { allow read, write: if request.auth.uid == userId; })로 지정하고 firebase deploy --only firestore:rules을 다시 배포합니다.

  3. 허용적 CORS이 프로덕션까지 살아남음

    Symptom: active.cors 심각도 높음. Cause: 생성된 Express 미들웨어: app.use(cors({ origin: '*' })). Fix: 프런트엔드 출처를 허용 목록에 추가하세요: app.use(cors({ origin: ['https://your-app.com'], credentials: true })). Next.js API 경로의 경우 응답에 Access-Control-Allow-Origin을 명시적으로 설정합니다.

  4. RLS 활성화되었지만 강제되지는 않았습니다.

    Symptom: 활성 baas.supabase-rls은 대시보드에서 RLS이 활성화된 경우에도 anon 역할이 공개 테이블에 쓸 수 있다고 보고합니다. FORCE이 없는 Cause: ENABLE은 테이블 소유자를 제외하고 마이그레이션은 소유자로 실행됩니다. Fix:은 마이그레이션에 alter table public.items force row level security;을 추가합니다. 재배포합니다.

  5. 서명되지 않은 IDOR-walkable ID

    Symptom: active.idor-walking은 익명 사용자가 테넌트 전체에서 /api/items/1, /api/items/2, ...을 읽을 수 있다고 보고합니다. Cause: API 처리기는 경로 매개변수를 신뢰하고 소유권 조건 없이 Postgres를 쿼리합니다. Fix: 모든 읽기 쿼리에 .eq('user_id', user.id)을 추가하거나 /api/users/[uid]/items/[id] 범위의 서명된 URL/UUID로 이동하세요.

바이브 코드 보안 루프

목표는 완벽한 보안이 아닙니다. AI 도구가 지속적으로 놓치는 낮게 매달린 과일을 제거하여 계속 빠르게 배송할 수 있습니다.

  1. Generate fast — Cursor, Claude Code, Lovable, Bolt을 사용합니다. 그게 요점입니다.
  2. Audit immediately — 위의 grep 세트를 실행하고, RLS을 확인하고, CSP을 확인하고, 인증 경계를 검토합니다.
  3. Harden at deploy — 미들웨어, 환경 분리, CSP nonce, HSTS, 서버 전용 인증 확인.
  4. Monitor — FixVibe 매일 수동적으로, 확인된 도메인에서 매주 활성, Slack에 대한 웹후크, Unlimited에서 위협 탐지.
  5. Fix fast — use FixVibe coding-agent prompts for code/config findings and operator steps for DNS, provider, secret-rotation, or manual-review findings. Re-deploy, re-scan, close the loop.

다음 단계

DAST과 SAST의 개념적 배경과 AI- 생성된 앱에 자체 검사가 필요한 이유를 알아보려면 AI-generated code security scanning을 읽어보세요. 빠른 참조 사전 배송 감사는 vibe coding security checklist을 참조하세요.

// scan your app

그만 읽고, 당신 앱의 취약점을 직접 찾아보세요.

URL 추가 — FixVibe은 이 가이드의 모든 수동 검사와 200개 이상의 다른 검사를 1분 이내에 실행합니다. Free, 설치 없음, 카드 없음.

  • Free 계층 — 월 3회 스캔, 카드 없음.
  • URL에 대한 수동 검색 — 도메인 확인이 필요하지 않습니다.
  • Cursor, Claude Code, Lovable, Bolt, v0, Replit에 맞춰 조정되었습니다.
  • Coding-agent prompts for code/config findings, plus operator steps for DNS/provider fixes.
무료 스캔 실행

가입 불필요

AI 코딩 도구로 만든 앱 보안 가이드 — Docs · FixVibe