Skip to main content
Summary

빠르게 만든 앱일수록 천천히 검토해야 한다

Codex로 앱을 만들다 보면 이상한 착각이 생깁니다. 며칠 걸릴 일을 몇 시간 만에 끝냈으니, 출시도 바로 해도 될 것 같습니다. 화면은 잘 뜨고, 로그인도 되고, 데이터도 저장됩니다. 테스트도 통과합니다. 그러면 마음속에서 작은 목소리가 들립니다.

"이 정도면 배포해도 되지 않을까?"

하지만 실제 서비스에서는 "잘 작동한다"와 "안전하다"가 같은 말이 아닙니다. 특히 AI가 만든 코드는 속도가 빠른 만큼 검토해야 할 면도 빠르게 늘어납니다. Codex는 코드를 잘 만들 수 있지만, 여러분의 사업 책임자도 아니고, 개인정보 보호 책임자도 아니며, 법무팀도 아닙니다. Codex가 제안한 코드는 반드시 사람이 최종 판단해야 합니다.

이번 장의 목표는 겁을 주는 것이 아닙니다. 출시 직전에 무엇을 확인해야 하는지, Codex를 어디까지 믿고 어디서 멈춰야 하는지, 초보자도 따라 할 수 있는 기준을 만드는 것입니다.

보안 검토는 거창한 해킹 지식에서 시작하지 않습니다. 가장 먼저 확인해야 할 질문은 단순합니다.

"이 코드가 공개되어도 괜찮은가?"

그다음 질문은 조금 더 날카롭습니다.

"나쁜 사용자가 마음먹고 URL, 요청 값, 파일, 토큰을 바꿔도 우리 앱이 막아낼 수 있는가?"

이 두 질문에 답할 수 있어야 앱을 세상에 내보낼 준비가 됩니다.

보안, 개인정보, 저작권 검토하기

비밀정보부터 코드 밖으로 빼기

보안 검토의 첫 번째 대상은 코드 자체가 아니라 코드 주변에 묻어 있는 비밀정보입니다. API 키, 데이터베이스 주소, JWT 시크릿, OAuth 클라이언트 시크릿, 쿠키 값, 관리자 계정 비밀번호, .env 파일, 운영 DB 덤프 같은 것들입니다.

초보자가 가장 많이 하는 실수는 "Codex가 알아야 고쳐 줄 수 있을 것 같아서" 민감한 값을 프롬프트에 그대로 붙여 넣는 일입니다.

예를 들어 이런 요청은 위험합니다.

우리 앱 로그인이 안 돼.
.env 내용은 아래와 같아.
DATABASE_URL=postgres://prod-user:real-password@...
NEXTAUTH_SECRET=...
GOOGLE_CLIENT_SECRET=...
이걸 보고 고쳐줘.

Codex가 문제를 해결할 수는 있을지 몰라도, 이미 비밀정보가 대화 기록과 작업 맥락에 들어갔습니다. 저장소, 이슈, PR 댓글, 터미널 로그, 스크린샷에도 같은 일이 벌어질 수 있습니다.

안전한 요청은 이렇게 바꿔야 합니다.

로그인 콜백에서 500 오류가 납니다.
실제 .env 값은 공유하지 않겠습니다.
환경변수 이름은 다음과 같습니다.
- DATABASE_URL
- NEXTAUTH_SECRET
- GOOGLE_CLIENT_ID
- GOOGLE_CLIENT_SECRET

.env.example과 인증 설정 파일을 기준으로,
필수 환경변수가 누락되었을 때 명확한 오류를 내도록 수정해 주세요.
실제 비밀값은 코드나 로그에 출력하지 마세요.

핵심은 "값"이 아니라 "이름과 역할"만 알려 주는 것입니다. Codex에게는 OPENAI_API_KEY라는 환경변수가 필요하다는 사실이면 충분합니다. 실제 sk-...로 시작하는 키는 필요하지 않습니다.

GitHub 저장소를 연결했다면 Secret scanning도 반드시 켜야 합니다. GitHub의 secret scanning은 저장소의 전체 Git 히스토리와 브랜치에서 API 키, 비밀번호, 토큰 같은 하드코딩된 자격 증명을 찾아 경고합니다. 이슈와 PR 설명, 댓글, 위키, gist 같은 영역도 스캔 대상이 될 수 있습니다.1

비밀정보 관리는 다음 순서로 처리합니다.

  1. .env, .env.local, .env.production, *.pem, *.key, credentials.json, DB 덤프 파일을 .gitignore에 넣습니다.

  2. 실제 값이 없는 .env.example만 저장소에 커밋합니다.

  3. GitHub Secret scanning과 Push protection을 켭니다.

  4. 이미 노출된 키는 "삭제"가 아니라 "폐기 후 재발급"합니다.

  5. 노출된 키로 접근이 있었는지 서비스 로그를 확인합니다.

여기서 중요한 점은 네 번째입니다. Git 히스토리에서 키를 지웠다고 해서 안전해지는 것이 아닙니다. 한 번 원격 저장소에 올라간 키는 이미 복사되었을 수 있습니다. 그래서 노출된 키는 반드시 해당 서비스에서 폐기하고 새 키를 발급해야 합니다.

Codex에게 시킬 수 있는 안전한 작업은 다음과 같습니다.

저장소에서 실제 비밀값이 커밋될 위험이 있는 파일 패턴을 점검해 주세요.
다음 작업만 수행하세요.
1. .gitignore에 빠진 민감 파일 패턴 제안
2. .env.example에 필요한 환경변수 이름만 정리
3. 코드에서 process.env 사용 여부 점검
4. 로그에 비밀값이 출력될 가능성 찾기

실제 .env 파일은 읽거나 출력하지 마세요.

이 요청은 Codex를 "비밀값을 보는 사람"이 아니라 "비밀값이 새지 않게 구조를 점검하는 도우미"로 사용합니다. 이 차이가 중요합니다.

개인정보는 적게 받고, 짧게 보관하고, 함부로 로그에 남기지 않는다

앱이 사용자 이메일, 이름, 전화번호, 주소, 결제 정보, 위치 정보, 접속 로그를 다루기 시작하면 개인정보 검토가 필요합니다. 초보자는 보통 개인정보를 "회원가입 폼에 있는 정보" 정도로만 생각합니다. 하지만 실제로는 훨씬 넓습니다.

예를 들어 날씨 대시보드 앱에서 사용자의 현재 위치를 저장한다면 그것도 민감한 정보가 될 수 있습니다. 미니 SaaS에서 프로젝트 이름과 메모를 저장한다면, 그 안에 고객사의 내부 정보가 들어갈 수 있습니다. 오류 로그에 요청 본문 전체를 찍으면 사용자가 입력한 개인정보가 로그 서버로 흘러갈 수 있습니다.

개인정보 검토의 기본 원칙은 단순합니다. 필요하지 않으면 받지 말고, 받았다면 오래 보관하지 말고, 보여 줄 때는 꼭 필요한 사람에게만 보여 줍니다.

Codex에게 개인정보 점검을 맡길 때는 이렇게 요청할 수 있습니다.

이 프로젝트에서 개인정보 또는 민감정보로 볼 수 있는 데이터를 찾아 주세요.
다음 관점으로 표를 만들어 주세요.
- 수집되는 데이터
- 수집 위치
- 저장 위치
- API 응답에 포함되는지
- 로그에 남는지
- 꼭 필요한 데이터인지
- 삭제 또는 마스킹 제안

파일을 수정하지 말고 읽기 전용으로 분석만 해 주세요.

예를 들어 Codex가 다음과 같은 결과를 낼 수 있습니다.

email: 회원 식별과 로그인에 사용됨. users.email에 저장됨. 관리자 목록 API에서 전체 노출됨.
제안: 일반 사용자 API에서는 email을 반환하지 않고, 관리자 화면에서도 필요한 경우에만 표시.

passwordHash: users.passwordHash에 저장됨. 일부 /api/me 응답에 포함될 가능성 있음.
제안: 모든 사용자 응답 DTO에서 passwordHash를 명시적으로 제외.

accessToken: OAuth 콜백 디버깅 로그에 출력될 가능성 있음.
제안: 토큰 전체 출력 금지. 앞 4자리만 남기거나 아예 출력하지 않음.

여기서 DTO라는 용어가 나옵니다. DTO는 Data Transfer Object의 줄임말로, 서버가 클라이언트에 보내기 위해 따로 만든 데이터 모양이라고 생각하면 됩니다. 데이터베이스의 User 객체를 그대로 보내는 대신, publicUser처럼 안전한 응답용 객체를 만들어 보내는 방식입니다.

// 위험한 방식: DB 사용자 객체를 그대로 반환
return Response.json(user);

// 안전한 방식: 응답에 필요한 필드만 고름
return Response.json({
  id: user.id,
  name: user.name,
  email: user.email,
});

특히 passwordHash, resetToken, refreshToken, providerAccessToken, twoFactorSecret 같은 필드는 절대 API 응답에 포함하지 않아야 합니다. 이름만 봐도 위험한 필드들은 "혹시 빠졌나?"가 아니라 "명시적으로 제거했나?"를 기준으로 확인해야 합니다.

AI가 만든 앱에서 자주 놓치는 취약점

AI가 만든 코드는 대체로 보기 좋습니다. 함수 이름도 그럴듯하고, 주석도 친절하며, 프레임워크 문법도 맞습니다. 문제는 보안 취약점이 겉모습만 봐서는 잘 보이지 않는다는 점입니다.

OWASP Top 10은 웹 애플리케이션에서 특히 위험한 보안 리스크를 정리한 대표적인 기준입니다. 그중 접근 제어 실패, 즉 사용자가 자기 권한을 넘어 다른 데이터나 기능에 접근하는 문제는 계속해서 가장 중요한 위험으로 다뤄지고 있습니다.2

초보자가 가장 먼저 봐야 할 취약점은 다음입니다.

  • 인증 누락: 로그인하지 않은 사용자도 보호된 API에 접근할 수 있음

  • 권한 검사 부족: 로그인은 했지만 다른 사용자의 데이터를 볼 수 있음

  • 입력값 검증 실패: 예상하지 않은 문자열, 숫자, 객체가 그대로 처리됨

  • SQL/NoSQL 인젝션: 사용자 입력이 쿼리 구조를 바꿔 버림

  • XSS: 사용자가 입력한 HTML이나 스크립트가 다른 사용자 브라우저에서 실행됨

  • SSRF: 서버가 공격자가 지정한 내부 주소나 민감한 URL에 접근함

  • 파일 업로드·경로 조작: 업로드 파일이나 경로 값으로 서버 파일을 덮어쓰거나 읽음

이 목록을 외우는 것보다 더 중요한 습관이 있습니다. 코드를 "파일 단위"가 아니라 "실행 흐름"으로 보는 것입니다.

예를 들어 미니 SaaS 앱에 프로젝트 수정 API가 있다고 해 봅시다.

export async function PATCH(req: Request, { params }: { params: { id: string } }) {
  const body = await req.json();

  const project = await db.project.update({
    where: { id: params.id },
    data: { name: body.name },
  });

  return Response.json(project);
}

이 코드는 잘 작동합니다. 하지만 위험합니다. 누가 요청했는지 확인하지 않고, 그 사용자가 해당 프로젝트의 소유자인지도 확인하지 않습니다. 공격자는 id만 바꿔 다른 사람의 프로젝트를 수정할 수 있습니다.

검토 질문은 이렇게 바뀌어야 합니다.

"이 파일에 인증 코드가 있나?"가 아니라 "사용자가 이 경로로 들어오면 권한 확인이 실제로 실행되는가?"

안전한 흐름은 다음과 비슷합니다.

export async function PATCH(req: Request, { params }: { params: { id: string } }) {
  const session = await requireSession();
  const body = await req.json();

  const parsed = updateProjectSchema.safeParse(body);
  if (!parsed.success) {
    return Response.json({ error: 'Invalid input' }, { status: 400 });
  }

  const project = await db.project.findFirst({
    where: {
      id: params.id,
      ownerId: session.user.id,
    },
  });

  if (!project) {
    return Response.json({ error: 'Not found' }, { status: 404 });
  }

  const updated = await db.project.update({
    where: { id: project.id },
    data: { name: parsed.data.name },
  });

  return Response.json({ id: updated.id, name: updated.name });
}

여기서 중요한 포인트는 세 가지입니다. requireSession()으로 로그인 여부를 확인하고, ownerId로 소유권을 확인하며, updateProjectSchema로 입력값을 검증합니다. 이 셋 중 하나라도 빠지면 실제 서비스에서는 문제가 될 수 있습니다.

Codex에게는 이렇게 리뷰를 요청합니다.

/api 아래의 모든 라우트를 실행 흐름 기준으로 검토해 주세요.
각 라우트마다 다음을 확인해 주세요.
1. 인증이 필요한 라우트인데 requireSession 또는 동등한 검사가 빠졌는가?
2. 사용자 소유 리소스 조회/수정/삭제에서 ownerId 검사가 있는가?
3. 요청 body, query, params를 스키마로 검증하는가?
4. DB 쿼리에 사용자 입력이 안전하게 전달되는가?
5. 응답에 passwordHash, token, secret 같은 민감 필드가 포함되는가?

파일을 수정하지 말고, 위험도와 근거 파일 경로를 표로 정리해 주세요.

이 요청은 초보자에게 특히 좋습니다. Codex가 곧바로 코드를 고치지 않고, 먼저 지도를 그리게 만들기 때문입니다. 보안에서는 "무엇을 고쳤는지"보다 "무엇을 위험하다고 판단했는지"가 먼저입니다.

SSRF와 파일 업로드는 특히 조심한다

AI가 만든 앱에서 요즘 자주 등장하는 기능이 있습니다. URL을 입력하면 본문을 가져오고, 이미지를 업로드하면 미리보기를 만들고, CSV를 올리면 데이터를 가져오고, 외부 API에서 정보를 수집합니다. 기능 자체는 유용하지만 보안 관점에서는 조심해야 합니다.

SSRF는 Server-Side Request Forgery의 줄임말입니다. 쉽게 말해 "사용자가 서버를 속여 서버가 접근하면 안 되는 주소에 요청을 보내게 만드는 공격"입니다.

예를 들어 사용자가 URL을 입력하면 서버가 해당 페이지를 가져오는 코드가 있다고 해 봅시다.

const { url } = await req.json();
const response = await fetch(url);
const html = await response.text();

겉으로는 단순합니다. 하지만 공격자가 http://localhost:3000/admin, http://169.254.169.254/, 사내망 주소, 클라우드 메타데이터 주소 같은 값을 넣으면 서버가 내부 정보에 접근할 수 있습니다.

Codex에게 URL 가져오기 기능을 만들라고 하면 종종 "일단 fetch하면 되는 코드"를 만들어 줍니다. 출시 전에는 반드시 다음 조건을 추가해야 합니다.

URL fetch 기능을 보안 기준으로 수정해 주세요.
요구사항:
1. http와 https만 허용
2. localhost, 127.0.0.1, 사설 IP 대역, link-local 주소 차단
3. 리다이렉트가 발생하면 최종 URL도 다시 검증
4. 응답 크기 제한
5. 타임아웃 설정
6. 허용된 도메인 목록이 있으면 allowlist 우선 사용
7. 실패 시 내부 네트워크 정보가 오류 메시지에 드러나지 않게 처리

파일 업로드도 비슷합니다. "이미지만 업로드 가능"이라고 UI에 써 있어도 공격자는 브라우저 UI를 거치지 않고 직접 요청을 보낼 수 있습니다. 서버에서 파일 크기, 확장자, MIME 타입, 실제 파일 내용, 저장 경로를 검증해야 합니다.

특히 파일 이름을 그대로 경로에 붙이는 코드는 위험합니다.

// 위험한 예
const savePath = `./uploads/${file.name}`;

공격자가 파일 이름을 ../../server.ts처럼 보내면 경로 조작 문제가 생길 수 있습니다. 안전하게 하려면 원본 파일명은 표시용으로만 저장하고, 실제 저장 파일명은 서버가 새로 만든 무작위 ID를 사용해야 합니다.

const safeName = `${crypto.randomUUID()}.png`;
const savePath = path.join(uploadDir, safeName);

보안은 종종 "사용자를 믿지 않는 것"에서 시작합니다. 정확히 말하면 사용자를 미워하라는 뜻이 아니라, 요청은 언제든 조작될 수 있다고 가정하라는 뜻입니다.

Codex Security는 스캐너이자 조수이지, 최종 승인자가 아니다

2026년 현재 Codex Security는 연결된 GitHub 저장소에서 가능성이 높은 보안 문제를 찾고, 검증하고, 수정 제안으로 이어지도록 돕는 보안 전용 워크플로우로 소개되고 있습니다. 공개 자료에 따르면 리서치 프리뷰 기간에 120만 개 이상의 커밋을 스캔했고, Critical 및 High 등급 이슈를 찾아낸 사례가 보고되었습니다.3

이 숫자는 인상적입니다. 하지만 숫자가 크다고 해서 "이제 사람이 안 봐도 된다"는 뜻은 아닙니다. 오히려 반대입니다. Codex Security 같은 도구가 강력해질수록 사람의 역할은 더 분명해집니다.

Codex Security를 사용할 때는 한 명의 만능 보안 담당자로 보지 말고, 세 가지 역할로 나누어 생각하는 것이 좋습니다.

  • 읽기 전용 스캐너: 저장소와 diff를 읽고 위험 후보를 찾음

  • 취약점 재현 도우미: 샌드박스나 테스트 환경에서 문제가 실제로 재현되는지 확인함

  • 패치 제안자: 검토 가능한 작은 수정안을 만들고 PR로 연결함

이 셋을 한 번에 섞으면 위험합니다. "찾아 줘, 고쳐 줘, 테스트해 줘, 커밋해 줘, 배포해 줘"라는 요청은 편해 보이지만 책임 경계가 흐려집니다. 보안 검토에서는 편한 흐름보다 추적 가능한 흐름이 더 중요합니다.

좋은 운영 방식은 다음과 같습니다.

1단계: 읽기 전용으로 취약점 후보를 찾는다.
2단계: 사람이 우선순위를 정한다.
3단계: Codex가 한 이슈만 좁게 수정한다.
4단계: 재현 테스트와 회귀 테스트를 돌린다.
5단계: 사람이 PR diff를 승인한다.

여기서 회귀 테스트란 "고친 뒤에 기존 기능이 망가지지 않았는지 확인하는 테스트"입니다. 보안 패치는 종종 정상 사용자의 흐름까지 막아 버릴 수 있습니다. 예를 들어 권한 검사를 추가했는데 관리자가 자기 팀 데이터를 못 보게 되거나, SSRF 방어를 추가했는데 정상적인 외부 이미지 URL까지 모두 차단될 수 있습니다.

Codex Security가 패치를 제안했다면 바로 병합하지 말고 다음 질문을 던져야 합니다.

이 패치가 막으려는 취약점을 한 문장으로 설명해 주세요.
취약점이 패치 전에는 어떻게 재현되는지 테스트를 작성해 주세요.
패치 후 같은 테스트가 통과하는지 확인해 주세요.
정상 사용자 시나리오가 깨지지 않는지 회귀 테스트를 추가해 주세요.
변경 범위를 최소화하고, 관련 없는 리팩터링은 하지 마세요.

보안 패치에서 가장 위험한 말은 "겸사겸사"입니다. 인증 버그를 고치면서 폴더 구조를 바꾸고, UI를 정리하고, 의존성을 올리고, 타입 이름까지 바꾸면 리뷰가 어려워집니다. 보안 수정은 작고 날카로워야 합니다.

샌드박스와 승인 정책으로 Codex의 손발을 제한하기

Codex에게 보안 검토를 맡길 때는 권한 설정이 중요합니다. Codex의 보안 통제는 크게 샌드박스와 승인 정책으로 나누어 생각할 수 있습니다. 샌드박스는 Codex가 기술적으로 어디까지 접근하고 쓸 수 있는지를 제한하고, 승인 정책은 특정 행동 전에 사람에게 물어보게 만드는 장치입니다.4

보안 검토의 기본값은 보수적으로 잡는 것이 좋습니다.

읽기 전용 리뷰:
- 파일 수정 금지
- 네트워크 접근 금지
- 패키지 설치 금지
- 커밋/푸시 금지
- 결과는 보고서로만 출력

패치 작업:
- 지정된 worktree 또는 브랜치에서만 파일 수정 허용
- 관련 테스트 실행 허용
- 새 의존성 추가는 승인 필요
- 네트워크 접근은 승인 필요
- 커밋/푸시는 사람이 수행

로컬 개발 환경에서는 특히 네트워크 접근을 조심해야 합니다. 네트워크 접근이 켜져 있으면 Codex가 문서를 검색하거나 패키지를 설치할 수 있어 편합니다. 하지만 동시에 의도치 않게 외부 서비스로 데이터를 보낼 가능성도 생깁니다. 보안 검토 중에는 가능한 한 오프라인으로 시작하고, 꼭 필요한 경우에만 승인하는 방식이 좋습니다.

위험한 명령 실행도 마찬가지입니다. 예를 들어 rm -rf, 데이터베이스 마이그레이션, 운영 서버 배포, 패키지 publish, 클라우드 리소스 삭제 같은 작업은 AI가 자동으로 실행해서는 안 됩니다.

Codex에게는 다음처럼 명확히 말합니다.

보안 검토 모드로 작업해 주세요.
다음 작업은 하지 마세요.
- 파일 삭제
- 데이터베이스 마이그레이션 실행
- 외부 네트워크 호출
- 패키지 설치
- git commit, push
- 배포 명령 실행

필요하다고 판단되면 먼저 이유와 명령어를 설명하고 승인을 요청하세요.

초보자에게는 이 문장 하나가 큰 안전장치가 됩니다. Codex를 믿지 말라는 뜻이 아니라, Codex가 실수해도 피해 범위가 작도록 울타리를 세우라는 뜻입니다.

보안 패치는 설명, 재현, 수정, 테스트, 리뷰 순서로 검증한다

보안 이슈를 발견하면 마음이 급해집니다. 빨리 고쳐야 할 것 같습니다. 하지만 급하게 고친 보안 패치는 또 다른 버그를 만들기 쉽습니다.

패치 검증은 항상 같은 순서로 진행합니다.

취약점 설명 → 재현 테스트 → 수정 코드 → 회귀 테스트 → PR 리뷰

예를 들어 "다른 사용자의 프로젝트를 수정할 수 있는 문제"가 발견되었다고 해 봅시다. Codex에게 바로 "고쳐 줘"라고 하지 말고 먼저 설명을 요구합니다.

이 취약점을 초보 개발자도 이해할 수 있게 설명해 주세요.
공격자가 어떤 요청을 보내면 어떤 데이터가 변경되는지 예시를 들어 주세요.
위험도와 영향을 함께 정리해 주세요.

그다음 재현 테스트를 만듭니다.

패치 전에 실패해야 하는 보안 테스트를 먼저 작성해 주세요.
시나리오:
- userA와 userB를 만든다.
- userB의 프로젝트를 만든다.
- userA로 로그인한 상태에서 userB 프로젝트 PATCH 요청을 보낸다.
- 기대 결과는 403 또는 404다.
현재 코드에서는 이 테스트가 실패해야 한다.

그다음에야 수정합니다.

방금 작성한 테스트를 통과하도록 최소 수정만 해 주세요.
프로젝트 조회와 수정 쿼리 모두 ownerId를 포함해야 합니다.
관련 없는 리팩터링은 하지 마세요.

마지막으로 회귀 테스트를 확인합니다.

다음 정상 시나리오도 깨지지 않았는지 확인해 주세요.
- 프로젝트 소유자는 자기 프로젝트를 수정할 수 있다.
- 로그인하지 않은 사용자는 수정할 수 없다.
- 존재하지 않는 프로젝트는 404를 반환한다.
테스트 명령과 결과를 요약해 주세요.

이렇게 하면 Codex가 제안한 패치를 사람이 이해할 수 있습니다. 보안 패치의 목표는 "초록 체크 표시"가 아니라 "왜 안전해졌는지 설명 가능한 상태"입니다.

의존성 보안과 라이선스를 함께 본다

앱은 우리가 직접 쓴 코드만으로 만들어지지 않습니다. React, Next.js, Prisma, Tailwind CSS, date-fns, zod, axios 같은 수많은 오픈소스 위에 올라갑니다. 그래서 출시 전에는 의존성도 확인해야 합니다.

보안 관점에서는 알려진 취약점이 있는 패키지를 확인합니다.

npm audit
npm audit --audit-level=high

하지만 npm audit fix --force를 무작정 실행하면 안 됩니다. 큰 버전이 올라가면서 앱이 깨질 수 있습니다. Codex에게는 이렇게 요청하는 편이 안전합니다.

npm audit 결과를 읽고 출시 전에 반드시 처리해야 할 항목만 우선순위로 정리해 주세요.
각 항목에 대해 다음을 알려 주세요.
- 취약 패키지 이름
- 현재 버전과 권장 버전
- 직접 의존성인지 간접 의존성인지
- 실제 앱에서 영향이 있을 가능성
- 안전한 업그레이드 절차

아직 package.json을 수정하지 마세요.

그다음 라이선스를 확인합니다. 오픈소스는 "무료"라는 뜻이 아니라 "정해진 조건 아래 사용할 수 있다"는 뜻입니다. MIT나 Apache-2.0처럼 비교적 자유로운 라이선스도 저작권 표시나 라이선스 문구 유지 같은 조건이 있습니다. GPL이나 AGPL 계열은 배포 방식에 따라 소스 공개 의무가 문제가 될 수 있어 상업 서비스에서는 반드시 별도 검토가 필요합니다.5

초보자는 다음 네 가지를 기억하면 됩니다.

  • MIT/BSD 계열: 대체로 상업적 사용이 자유롭지만 저작권 표시와 라이선스 문구를 유지해야 함

  • Apache-2.0: 상업적 사용이 가능하고 특허 관련 조항이 있지만 NOTICE와 변경 표시를 확인해야 함

  • GPL 계열: 배포 시 파생물의 소스 공개 의무가 생길 수 있어 주의해야 함

  • AGPL 계열: SaaS처럼 네트워크로 제공하는 경우에도 소스 공개 의무가 문제될 수 있어 더욱 주의해야 함

Codex에게 라이선스 점검을 시킬 수 있습니다.

package.json과 lockfile을 기준으로 의존성 라이선스를 점검해 주세요.
다음 형식으로 표를 만들어 주세요.
- 패키지명
- 버전
- 라이선스
- 상업 서비스 사용 시 주의 여부
- NOTICE 또는 attribution 필요 여부
- GPL/AGPL 등 별도 검토가 필요한 항목 여부

법적 결론을 단정하지 말고, 법무 검토가 필요한 후보를 표시해 주세요.

가능하면 자동 도구도 같이 사용합니다.

npx license-checker --summary
npx license-checker --production --json > third-party-licenses.json

출시용 저장소에는 NOTICE, THIRD-PARTY-LICENSES.md, 또는 third-party-licenses.json 같은 파일로 외부 라이브러리 정보를 정리해 두는 것이 좋습니다. 회사나 팀 프로젝트라면 "허용 라이선스 목록"과 "승인 필요 라이선스 목록"을 미리 정해 두면 더 안전합니다.

AI 생성 코드의 저작권과 유사성도 확인한다

Codex가 만든 코드라고 해서 저작권 문제가 자동으로 사라지는 것은 아닙니다. AI가 외부 예제와 비슷한 구조를 만들 수도 있고, 사용자가 프롬프트에 붙여 넣은 코드 조각이 원래 특정 라이선스를 가진 코드일 수도 있습니다. 또한 Stack Overflow 답변, 블로그 예제, GitHub 코드 조각을 그대로 복사해 넣은 뒤 "Codex가 다듬어 줬으니 괜찮겠지"라고 생각하면 위험합니다.

출시 전에는 다음을 확인합니다.

1. 외부 블로그나 문서에서 복사한 코드가 있는가?
2. 코드 상단의 저작권 표시를 제거하지 않았는가?
3. 예제 코드의 라이선스가 상업적 사용을 허용하는가?
4. GPL/AGPL 코드가 제품 코드에 섞이지 않았는가?
5. 생성된 코드가 특정 오픈소스 파일과 지나치게 유사하지 않은가?
6. UI 아이콘, 이미지, 폰트, 템플릿의 사용 조건을 확인했는가?

Codex에게 유사성 검토를 요청할 때는 이렇게 말할 수 있습니다.

이 프로젝트에서 외부 예제나 오픈소스 코드에서 가져온 것으로 보이는 주석, 출처 URL, 라이선스 문구를 찾아 주세요.
특히 파일 상단 주석, README, package metadata, 복사된 코드 블록을 확인해 주세요.
결과는 법적 결론이 아니라 검토 후보 목록으로 정리해 주세요.

저작권 검토에서 중요한 태도는 "걸리지만 않으면 된다"가 아닙니다. 나중에 투자, 인수, B2B 계약, 보안 심사를 받을 때 오픈소스 사용 내역과 라이선스 표기는 실제로 확인 대상이 됩니다. 작게 시작한 앱이라도 처음부터 정리해 두면 나중에 훨씬 편합니다.

배포 후 인증, 쿠키, CORS를 다시 확인한다

앞 장에서 배포 가능한 앱으로 마무리하면서 OAuth redirect URL, 환경변수, 운영 도메인 설정을 다뤘습니다. 보안 검토에서는 이 부분을 다시 확인해야 합니다. 로컬에서 잘 되던 로그인이 운영 환경에서 깨지는 경우도 많고, 급하게 고치다가 CORS를 너무 넓게 열어 버리는 경우도 많습니다.

운영 환경에서 반드시 확인할 항목은 다음과 같습니다.

인증:
- OAuth redirect URL이 실제 배포 도메인과 일치하는가?
- 콜백 URL에 localhost가 남아 있지 않은가?
- 로그아웃 후 세션이 실제로 무효화되는가?
- 비밀번호 재설정 토큰은 짧은 시간만 유효한가?

쿠키:
- httpOnly가 켜져 있는가?
- production에서 secure가 켜져 있는가?
- sameSite 설정이 의도에 맞는가?
- 세션 만료 시간이 너무 길지 않은가?

CORS:
- origin: '*'를 운영에서 쓰지 않는가?
- credentials: true와 와일드카드 origin을 함께 쓰지 않는가?
- 허용 도메인이 실제 프론트엔드 도메인으로 제한되어 있는가?

CORS는 Cross-Origin Resource Sharing의 줄임말입니다. 쉽게 말해 "어떤 웹사이트가 우리 API를 브라우저에서 호출할 수 있는지 정하는 규칙"입니다. 개발 중에는 귀찮아서 모든 출처를 허용하고 싶어집니다.

// 운영에서 피해야 할 설정
app.use(cors({ origin: '*', credentials: true }));

운영에서는 허용할 도메인을 명시해야 합니다.

app.use(cors({
  origin: ['https://myapp.com', 'https://www.myapp.com'],
  credentials: true,
}));

Codex에게는 실제 배포 주소를 기준으로 점검하게 합니다.

운영 배포 기준으로 인증, 쿠키, CORS 설정을 검토해 주세요.
배포 도메인: https://myapp.com
API 도메인: https://api.myapp.com

다음을 확인해 주세요.
- localhost 또는 preview URL이 운영 설정에 남아 있는지
- 쿠키 secure/httpOnly/sameSite 설정이 적절한지
- CORS origin이 과도하게 열려 있는지
- OAuth redirect URL 문서에 반영해야 할 항목

실제 클라이언트 시크릿 값은 출력하지 마세요.

로그와 오류 메시지는 공격자에게 힌트를 주지 않아야 한다

초보자가 디버깅을 하다 보면 이런 코드를 자주 넣습니다.

console.log('login error', error);
return Response.json({ error }, { status: 500 });

개발 중에는 편합니다. 하지만 운영에서는 위험합니다. error 안에 SQL 쿼리, 테이블 이름, 스택 트레이스, 토큰, 이메일, 내부 파일 경로가 들어 있을 수 있습니다.

운영 오류 응답은 짧고 무심해야 합니다.

return Response.json(
  { error: 'Internal server error' },
  { status: 500 }
);

대신 서버 내부 로그에는 추적 가능한 requestId를 남깁니다.

logger.error({ requestId, err }, 'Unexpected error');
return Response.json(
  { error: 'Internal server error', requestId },
  { status: 500 }
);

이때도 로그에 민감정보가 섞이면 안 됩니다. 요청 본문 전체를 찍는 습관은 특히 위험합니다.

// 위험
logger.info({ body: await req.json() }, 'request body');

Codex에게 로그 점검을 요청합니다.

운영 환경에서 민감정보가 로그나 오류 응답으로 노출될 수 있는 지점을 찾아 주세요.
다음 패턴을 중점적으로 봐 주세요.
- console.log(req.body)
- console.log(headers)
- error.stack을 API 응답으로 반환
- token, password, secret, cookie, authorization 값 출력
- DB 오류 원문을 사용자에게 반환

수정 제안은 requestId 기반 오류 처리와 민감 필드 마스킹 중심으로 해 주세요.

로그는 문제가 생겼을 때 우리 편이 되어야 합니다. 공격자에게 내부 구조를 설명하는 안내문이 되어서는 안 됩니다.

팀에서는 감사 로그와 책임 범위를 남긴다

개인 프로젝트에서는 "내가 했으니까 기억하겠지"라고 생각할 수 있습니다. 하지만 팀이나 기업 환경에서는 누가 어떤 저장소를 Codex에 연결했고, 어떤 작업을 맡겼고, 어떤 제안이 병합되었는지 남겨야 합니다.

특히 Codex Cloud, GitHub 연결, Automations, MCP, 플러그인, Browser Use, Computer Use를 함께 쓰면 데이터 흐름이 복잡해집니다. 소스 코드, 이슈 내용, 로그, 테스트 데이터, 스크린샷, 외부 도구 응답이 Codex 작업 맥락에 들어갈 수 있습니다.

팀에서는 최소한 다음 내용을 문서화해야 합니다.

Codex 운영 기록:
- 연결된 GitHub 조직과 저장소
- Codex가 접근할 수 있는 브랜치와 환경
- 사용한 플러그인, MCP 서버, Skills
- 네트워크 접근 허용 여부와 허용 도메인
- 자동화 작업의 실행 주기와 권한
- Codex가 만든 PR과 병합한 사람
- 보안 이슈 발견, 재현, 패치 승인 기록

데이터 처리 정책:
- Codex에 입력해도 되는 데이터
- 입력하면 안 되는 데이터
- 로그 보관 기간
- 작업 기록 삭제 또는 보관 기준
- 보안 사고 발생 시 키 폐기와 보고 절차
- 벤더 장애 또는 정책 변경 시 대체 절차

벤더 종속성도 생각해야 합니다. 특정 AI 도구의 워크플로우에 모든 코드 리뷰, 보안 검토, 배포 승인을 묶어 두면 나중에 가격, 정책, 기능, 접근 권한이 바뀌었을 때 흔들릴 수 있습니다. 그래서 중요한 기준은 저장소 안에 남겨 두는 것이 좋습니다.

예를 들어 AGENTS.md, SECURITY.md, docs/security-review.md, .github/pull_request_template.md에 팀의 보안 기준을 적어 둡니다.

## 보안 리뷰 기준

모든 PR은 다음을 확인해야 한다.

- 인증이 필요한 API에 인증 검사가 있는가?
- 사용자 소유 리소스에 ownerId 또는 role 검사가 있는가?
- 입력값은 zod 스키마로 검증되는가?
- 비밀정보가 코드, 로그, 테스트 스냅샷에 포함되지 않는가?
- 새 의존성의 라이선스와 취약점을 확인했는가?
- 운영 설정에서 CORS와 쿠키 옵션이 안전한가?

Codex에게 "우리 팀 기준대로 리뷰해 줘"라고 말하려면 기준이 먼저 있어야 합니다. 기준이 없으면 Codex는 그럴듯한 일반론을 따릅니다. 기준이 있으면 팀의 방식에 맞춰 일합니다.

출시 전 최종 체크리스트

마지막으로 실제 출시 직전에 사용할 수 있는 체크리스트를 정리해 보겠습니다. 이 목록은 완벽한 보안 감사를 대신하지는 않습니다. 하지만 초보자가 아무것도 확인하지 않고 배포하는 위험은 크게 줄여 줍니다.

비밀정보:
[ ] .env, .env.local, .env.production이 커밋되지 않았다.
[ ] .env.example에는 실제 값이 없다.
[ ] GitHub Secret scanning과 Push protection을 확인했다.
[ ] 노출된 적 있는 키는 폐기하고 재발급했다.
[ ] 로그와 PR 댓글에 토큰, 쿠키, API 키가 없다.

인증과 권한:
[ ] 보호된 API는 로그인 검사를 한다.
[ ] 사용자 리소스는 ownerId 또는 role 검사를 한다.
[ ] 관리자 API는 관리자 권한을 서버에서 확인한다.
[ ] 로그아웃과 세션 만료가 동작한다.
[ ] 비밀번호 재설정 토큰은 짧게 유효하고 1회만 사용된다.

입력값과 공격 방어:
[ ] body, query, params를 스키마로 검증한다.
[ ] SQL/NoSQL 쿼리에 사용자 입력을 안전하게 전달한다.
[ ] HTML 출력에서 XSS 가능성을 확인했다.
[ ] URL fetch 기능은 SSRF 방어를 한다.
[ ] 파일 업로드는 크기, 타입, 저장 경로를 검증한다.

운영 설정:
[ ] OAuth redirect URL이 운영 도메인으로 설정되어 있다.
[ ] 쿠키에 httpOnly, secure, sameSite가 적절히 설정되어 있다.
[ ] CORS가 허용 도메인으로 제한되어 있다.
[ ] 오류 응답에 스택 트레이스나 내부 정보가 없다.
[ ] 운영 DB와 테스트 DB가 분리되어 있다.

의존성과 라이선스:
[ ] npm audit 또는 동등한 도구로 취약 의존성을 확인했다.
[ ] 새 의존성의 라이선스를 확인했다.
[ ] GPL/AGPL 계열 의존성은 별도 검토했다.
[ ] NOTICE 또는 third-party license 파일을 준비했다.
[ ] 외부 예제 코드의 출처와 사용 조건을 확인했다.

Codex 운영:
[ ] 보안 리뷰는 먼저 읽기 전용으로 실행했다.
[ ] 패치는 worktree 또는 별도 브랜치에서 만들었다.
[ ] 네트워크 접근과 파일 수정 권한을 필요한 만큼만 허용했다.
[ ] Codex가 만든 보안 패치는 사람이 PR에서 검토했다.
[ ] 취약점 설명, 재현 테스트, 수정 코드, 회귀 테스트가 남아 있다.

이 체크리스트를 한 번에 모두 완벽하게 지키기는 어렵습니다. 그래도 괜찮습니다. 중요한 것은 "출시 전 보안 검토"라는 단계를 개발 흐름 안에 넣는 것입니다.

Codex는 앱을 빠르게 만들게 해 줍니다. 그래서 우리는 더 빨리 검토 체계를 만들어야 합니다. AI가 코드를 쓰는 시대에는 보안이 마지막에 붙는 장식이 아니라, 개발 속도를 안전하게 감당하기 위한 브레이크와 안전벨트가 됩니다.

앱을 출시한다는 것은 코드를 공개하는 일이 아닙니다. 사용자에게 신뢰를 요청하는 일입니다. Codex가 만든 코드도 결국 여러분의 이름으로 배포됩니다. 그러니 마지막 승인 버튼은 언제나 사람이 눌러야 합니다.

참고

1About secret scanning - GitHub Docs

2A01 Broken Access Control - OWASP Top 10:2025

3Codex Security: now in research preview - OpenAI

4Agent approvals & security - Codex | OpenAI Developers

5오픈소스 라이선스 완벽 가이드 | TechFeed

책 목록

Codex로 앱 만들기 완벽 가이드: 초보자를 위한 AI 코딩 에이전트 실전 입문

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기