☰ Categories

Review code for security issues

Traces the paths external input takes, and states which areas it could not assess.

CategoryDevelopment › Coding
TagsReviewingCodeDeveloper
Prompt
Review this code for security issues.

Work by following untrusted input, not by scanning for patterns:
1. **Entry points** — every place external input enters. Request bodies, query strings, headers, file uploads, webhooks, message queues, environment.
2. For each, trace where it flows: into queries, into commands, into file paths, into templates, into deserialization, into logs, back into a response.
3. At each destination, say whether it is neutralized and how. Name the specific mechanism, not "it is validated".
4. **Authorization** — for every operation, is the check present, and is it checking the right subject against the right object. Missing object-level checks are the most common real finding and the least likely to be caught by scanners.
5. Secrets — hardcoded, logged, returned in errors, committed.
6. What errors reveal to the caller.

Report per finding: the path from entry to impact, what an attacker gets, and the fix.

Rules:
- *Never conclude that the code is secure.* Report what you checked and what you could not. Absence of a finding is not evidence of safety.
- **State explicitly what the code alone cannot tell you** — framework defaults, middleware applied elsewhere, infrastructure controls, what the caller already authenticated.
- Separate confirmed issues from things that depend on context you cannot see.
- Do not pad with generic advice. If there is nothing at a given step, say so.
After pasting, fill in the fields at the bottom (Code · Runtime and trust boundaries · Data handled)

What this prompt does

In a security review, "this looks safe" is the most dangerous output. This follows the input paths and is explicit about what the code alone cannot determine.

Model comparison

ChatGPT best traces data flows while preserving uncertainty. Gemini is concrete but overclaims and runs long; [C] is absent.

ChatGPTTop overall
44/ 50

+ Best separates confirmed flaws from context-dependent risks.

- Grouping authorization absence as confirmed is slightly ambiguous.

Gemini
36/ 50

+ Clearly traces input into SQL and responses with concrete fixes.

- Overstates contextual risks as confirmed flaws and outcomes.

CriterionChatGPTGeminiLeader
Instruction following97ChatGPT +29%
Accuracy96ChatGPT +50%
Specificity99Tie
Structure98ChatGPT +13%
Right length86ChatGPT +33%

Scored 1–10 by gpt-5.6-sol with model names hidden (2026-09-24). This is an AI review, not a measurement.

Read full answers

We gave three models the same input and copied their answers unedited. Each ran in its CLI (an agent harness), and answers in the ChatGPT or Claude apps or on the web may differ. Outputs are in Korean.

Input used
Code: // routes/admin.js
router.get('/api/admin/users/:id/orders', (req, res) => {
  const userId = req.params.id;
  const query = `SELECT * FROM orders WHERE user_id = ${userId}`;
  db.query(query, (err, rows) => {
    if (err) return res.status(500).json({ error: err.message });
    res.json(rows);
  });
});

router.post('/api/admin/refund', (req, res) => {
  const { orderId, amount } = req.body;
  refundService.refund(orderId, amount);
  res.json({ ok: true });
});

Runtime and trust boundaries: 내부 관리자용 Node.js/Express API, AWS EC2 단일 인스턴스에 배포. 프론트는 React 어드민 페이지에서 호출. 로그인 시 발급한 JWT를 헤더에 실어 보내고 미들웨어에서 검증은 하지만, role(super_admin/staff) 체크는 일부 라우트에만 걸려 있습니다.

Data handled: 고객 이름, 전화번호, 배송지 주소, 주문 금액, 환불 내역 등 결제·개인정보를 다룹니다.
gpt-5.6-luna (codex CLI · 에이전트 하네스) · Run on 2026-09-24

확인된 문제

1. SQL 인젝션 + 관리자 API의 객체 수준 권한 누락

경로:

req.params.id → 문자열 템플릿 삽입 → SQL 쿼리 → 주문 목록 반환

const query = `SELECT * FROM orders WHERE user_id = ${userId}`;

userId를 숫자로 검증하거나 이스케이프하지 않고 SQL 문에 직접 삽입합니다. 공격자는 다음처럼 쿼리 구조를 변경할 수 있습니다.

/api/admin/users/1 OR 1=1/orders

영향:

  • 전체 고객의 주문 및 개인정보 조회 가능
  • DB 권한이 넓으면 추가 SQL 조작 가능성
  • SELECT *로 주문에 포함된 불필요한 민감 필드까지 노출 가능

또한 JWT 검증만으로는 충분하지 않습니다. 이 코드에는 현재 요청자가 super_admin 또는 허용된 staff인지, 그리고 해당 userId 객체에 접근할 권한이 있는지 확인하는 코드가 없습니다. 내부 관리자 API라도 직원 계정 간 접근 범위가 다르다면 IDOR/BOLA입니다.

수정:

router.get('/api/admin/users/:id/orders', requireJwt, requireAdminRole,
  async (req, res) => {
    const userId = Number(req.params.id);
    if (!Number.isSafeInteger(userId) || userId <= 0) {
      return res.status(400).json({ error: 'invalid user id' });
    }

    // 정책에 따라 객체 접근 권한 확인
    if (!canViewUser(req.user, userId)) {
      return res.status(403).json({ error: 'forbidden' });
    }

    const [rows] = await db.query(
      'SELECT id, user_id, amount, status, created_at FROM orders WHERE user_id = ?',
      [userId]
    );
    res.json(rows);
  }
);

숫자 변환만으로 SQL 인젝션 방어를 대체하지 말고, 반드시 DB 드라이버의 파라미터 바인딩을 사용해야 합니다.

2. DB 오류 메시지의 외부 반환

경로:

DB 오류 → err.message → HTTP 응답

res.status(500).json({ error: err.message });

공격자는 SQL 문, 테이블명, 컬럼명, DB 호스트 정보 또는 드라이버 내부 세부사항을 얻을 수 있습니다. 특히 위 SQL 인젝션을 시도할 때 오류 기반 탐색에도 사용됩니다.

수정:

  • 외부에는 고정된 일반 오류만 반환
  • 상세 오류는 접근 제한된 서버 로그에 기록
  • 로그에도 요청 입력값이나 개인정보를 무제한 남기지 않기
logger.error({ err, requestId: req.id }, 'order lookup failed');
return res.status(500).json({ error: 'internal server error' });

문맥에 따라 확인이 필요한 문제

3. 환불 API의 권한 및 객체 검증이 코드상 없음

경로:

req.body.orderId, req.body.amount → refundService.refund(orderId, amount) → 환불 처리

이 라우트에는 다음 검사가 보이지 않습니다.

  • 요청자가 super_admin인지, 환불 가능한 staff인지
  • 해당 요청자가 이 주문을 처리할 권한이 있는지
  • orderId가 실제 주문인지
  • 주문 상태가 환불 가능한 상태인지
  • 환불 금액이 주문 금액 이하인지
  • 이미 환불된 금액을 초과하지 않는지
  • 통화와 소수점 정밀도가 올바른지
  • 중복 요청을 막는 멱등성 처리

refundService 내부에서 모두 처리한다면 일부 문제는 없을 수 있지만, 제공된 코드만으로는 확인할 수 없습니다. 서비스에 검사가 없다면 공격자는 유효한 JWT를 가진 낮은 권한 직원으로 다른 고객의 주문을 환불하거나, 금액 조작·중복 환불을 발생시킬 수 있습니다.

수정 방향:

  • 라우트 또는 서비스 경계에서 역할 검사
  • DB에서 주문을 조회해 요청자의 접근 범위와 주문 소유자를 확인
  • 환불 가능 잔액을 서버 측 주문 데이터로 계산
  • 클라이언트가 보낸 금액을 신뢰하지 않기
  • 트랜잭션과 행 잠금 또는 원자적 조건 업데이트 사용
  • 결제 제공업체 호출에 멱등성 키 사용
  • 허용된 스키마로 orderId, amount 타입·범위 검증

4. 환불 성공 응답이 실제 결과보다 먼저 반환됨

refundService.refund(orderId, amount);
res.json({ ok: true });

refund()가 비동기 함수라면 완료를 기다리지 않습니다.

영향:

  • 실제 환불 실패 후에도 클라이언트는 성공으로 인식
  • Promise rejection이 별도 처리되지 않으면 프로세스 오류나 관찰되지 않는 실패 가능
  • 프론트엔드가 성공 응답을 근거로 주문 상태를 변경할 수 있음

수정:

try {
  const result = await refundService.refund(orderId, amount);
  return res.json({ ok: true, refundId: result.id });
} catch (err) {
  logger.error({ err, requestId: req.id }, 'refund failed');
  return res.status(502).json({ error: 'refund failed' });
}

단, 서비스가 동기 함수인지와 내부 트랜잭션 처리는 코드 밖의 정보입니다.

입력·출력별 확인 결과

  • 경로 파라미터 id: SQL에 직접 삽입되므로 확인된 SQL 인젝션입니다.
  • JSON body의 orderId, amount: 서비스 내부 구현과 검증이 보이지 않아 잠재적 권한 우회·금액 조작 문제입니다.
  • JWT 헤더: 미들웨어에서 서명 검증은 한다고 했지만, 이 코드에는 역할 및 객체 권한 검사가 없습니다.
  • 응답: 주문 조회 결과 전체를 반환하므로 DB 행의 민감 필드가 그대로 노출될 수 있습니다.
  • 로그: 이 코드에서 명시적 비밀정보 로깅은 없습니다.
  • 하드코딩된 비밀: 제시된 코드에는 보이지 않습니다.
  • 환불 라우트의 오류 반환: 직접적인 오류 노출은 보이지 않지만, 전역 Express 오류 처리기와 서비스 예외 처리 방식은 확인할 수 없습니다.

코드만으로 알 수 없는 사항

  • JWT가 어떤 알고리즘·키·issuer·audience 정책으로 검증되는지
  • 이 두 라우트에 실제로 인증·역할 미들웨어가 연결되어 있는지
  • refundService가 권한, 금액, 상태, 중복 환불을 검증하는지
  • DB 계정 권한과 SQL 드라이버의 파라미터 처리 방식
  • 프록시/WAF, 네트워크 보안 그룹, EC2 접근 제한
  • 전역 오류 처리기와 요청 로깅 설정
  • 브라우저 인증 방식 및 CSRF 방어 여부

따라서 이 코드만으로 가장 확정적인 취약점은 SQL 인젝션과 DB 오류 정보 노출입니다. 환불 API의 권한 우회 및 금액 조작은 refundService와 실제 라우터 미들웨어를 추가로 확인해야 확정할 수 있습니다.

More in this category

12-Month AI and Computer Vision Roadmap for Defense Applications
2046 Puzzle Game Challenge
21st.dev component prompt
3D FACTORY
3D FPS Game