깔아 두기만 1회차 · Claude Code · Opus 5.5 · 생각 깊이 중간 · 클로드가 쓴 review.md 원문 · 목록

shop.py 코드 리뷰

등급 개수 요약
🔴 Critical 6 시크릿 노출, SQL 인젝션, 남의 주문 조회(IDOR), 경로 탐색, 음수 수량으로 돈·재고 생성, 동시 주문 시 초과 판매
🟠 High 4 잔액 검증 없음, 없는 상품 주문 시 크래시, 쿠폰 오류를 조용히 무시, 페이지네이션 버그
🟡 Medium 4 N+1 쿼리, DB 연결 누수, 상대 경로 의존, 테스트 없음

🔴 Critical — 머지 전 필수 수정

1. 운영 관리자 토큰이 코드에 하드코딩됨 (7행, 83–84행)

ADMIN_TOKEN = "sk_live_9f8e7d6c5b4a3210shopadmin"

수정

import hmac
ADMIN_TOKEN = os.environ["SHOP_ADMIN_TOKEN"]

def is_admin(token):
    return isinstance(token, str) and hmac.compare_digest(token, ADMIN_TOKEN)

2. SQL 인젝션 (19행 search_products)

sql = f"SELECT ... WHERE name LIKE '%{keyword}%'"

수정

db.execute("SELECT id, name, price FROM products WHERE name LIKE ?", (f"%{keyword}%",))

(%, _ 같은 와일드카드까지 막으려면 ESCAPE '\'도 함께 쓰세요.)

3. 다른 사람의 주문을 볼 수 있음, IDOR (40–43행 get_order)

수정

row = db.execute(
    "SELECT id, user_id, created_at FROM orders WHERE id = ? AND user_id = ?",
    (order_id, current_user_id),
).fetchone()

4. 경로 탐색으로 서버 파일 읽기 가능 (77–80행 download_invoice)

수정: 파일명 대신 order_id를 받아 소유권을 확인한 뒤 서버가 경로를 만들도록 바꾸세요. 파일명을 꼭 받아야 한다면 경로를 정규화한 후 디렉터리 안인지 확인해야 합니다.

base = os.path.realpath(INVOICE_DIR)
path = os.path.realpath(os.path.join(base, filename))
if os.path.commonpath([base, path]) != base:
    raise PermissionError("invalid path")

5. 음수 수량으로 재고와 잔액을 늘릴 수 있음 (46–66행 place_order)

수정: 함수 맨 앞에 if not isinstance(qty, int) or qty <= 0: raise ValueError(...)를 넣으세요.

6. 동시 주문 시 초과 판매됨, 경쟁 상태 (48–55행)

수정: 조건부 UPDATE로 확인과 차감을 한 번에 처리하고, 쓰기 트랜잭션을 처음부터 잡으세요.

db.execute("BEGIN IMMEDIATE")
cur = db.execute(
    "UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?",
    (qty, product_id, qty),
)
if cur.rowcount != 1:
    db.rollback()
    raise ValueError("재고 부족")

🟠 High

7. 잔액 검증이 없음 (61–64행)

8. 없는 상품을 주문하면 크래시 (51행)

9. apply_coupon이 오류를 조용히 삼킴 (69–74행)

수정

def apply_coupon(total, coupon):
    percent = coupon["percent"]  # 형식 오류는 그대로 예외로 올림
    if not 0 <= percent <= 100:
        raise ValueError("invalid coupon")
    return total - (total * percent // 100)

10. 페이지네이션 버그 (25행, 27행 list_orders)


🟡 Medium

11. N+1 쿼리 (31–36행)

12. DB 연결을 닫지 않음 (모든 함수)

13. 상대 경로에 의존 (5–6행)

14. 테스트가 없음


머지 전 체크리스트

참고로 이 모듈에는 인증 로직이 없어서 user_id를 호출하는 쪽이 신뢰할 수 있는 세션에서 가져온다는 전제를 깔고 있습니다. API 레이어에서 클라이언트가 보낸 user_id를 그대로 넘기고 있다면 별도의 Critical 이슈가 되니 함께 확인해 보세요.