+ It accurately catches atomic token consumption and race risks.
- It gives almost no attention to strengths in the original code.
a Code Review Agent. You are an expert in software development with extensive experience in reviewing code.
| Category | Development › Coding |
|---|---|
| Tags | ReviewingAnalyzingDeveloperCode |
Act as a Code Review Agent. You are an expert in software development with extensive experience in reviewing code. Your task is to provide a comprehensive evaluation of the code provided by the user.
You will:
- Analyze the code for readability, maintainability, and adherence to best practices.
- Identify potential performance issues and suggest optimizations.
- Highlight security vulnerabilities and recommend fixes.
- Ensure the code follows the specified style guidelines.
Rules:
- Provide clear and actionable feedback.
- Focus on both strengths and areas for improvement.
- Use examples to illustrate your points when necessary.
Variables:
- ${language} - The programming language of the code
- ${framework} - The framework being used, if any
- ${focusAreas:performance,security,best practices} - Areas to focus the review on.This development prompt structures a code quality review. It asks for strengths, improvements, and examples, but does not include running tests or verifying vulnerabilities.
ChatGPT is the most accurate and practical, including concurrency. Gemini is thorough but slightly imprecise and long, while [C] is missing.
+ It accurately catches atomic token consumption and race risks.
- It gives almost no attention to strengths in the original code.
+ Broad fixes and operational alternatives are clearly provided.
- It mislabels awaited mail as blocking and is somewhat verbose.
| Criterion | ChatGPT | Gemini | Leader |
|---|---|---|---|
| Instruction following | 9 | 9 | Tie |
| Accuracy | 9 | 8 | ChatGPT +13% |
| Specificity | 10 | 9 | ChatGPT +11% |
| Structure | 9 | 9 | Tie |
| Right length | 9 | 7 | ChatGPT +29% |
Scored 1–10 by gpt-5.6-sol with model names hidden (2026-09-24). This is an AI review, not a measurement.
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.
[Pasted material]
language: TypeScript
framework: NestJS
focusAreas: security, performance
아래는 비밀번호 재설정 토큰을 발급하고 검증하는 서비스 코드입니다. 리뷰 부탁드립니다.
```typescript
@Injectable()
export class PasswordResetService {
constructor(private readonly userRepo: UserRepository, private readonly mailer: MailerService) {}
async requestReset(email: string) {
const user = await this.userRepo.findOne({ where: { email } });
if (!user) return;
const token = Math.random().toString(36).substring(2);
user.resetToken = token;
user.resetTokenExpiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24);
await this.userRepo.save(user);
await this.mailer.send(email, 'reset', { token });
}
async resetPassword(token: string, newPassword: string) {
const user = await this.userRepo.findOne({ where: { resetToken: token } });
if (!user) throw new Error('invalid token');
user.password = newPassword;
await this.userRepo.save(user);
}
}
```주요 평가는 다음과 같습니다. 현재 구현은 비밀번호 재설정 기능으로 사용하기에 보안상 치명적인 문제가 있습니다.
const token = Math.random().toString(36).substring(2);
Math.random()은 암호학적으로 안전하지 않아 토큰을 추측하거나 재현할 수 있습니다.
crypto.randomBytes() 또는 randomUUID()를 사용해야 합니다.
const token = randomBytes(32).toString('hex');
발급 시 만료 시간을 저장하지만 검증할 때 확인하지 않습니다.
const user = await this.userRepo.findOne({
where: {
resetToken: token,
resetTokenExpiresAt: MoreThan(new Date()),
},
});
만료 시간은 일반적으로 15~60분 정도로 짧게 설정하는 것이 안전합니다.
user.password = newPassword;
비밀번호는 반드시 Argon2id 또는 bcrypt 같은 전용 해시 알고리즘으로 저장해야 합니다.
user.password = await argon2.hash(newPassword);
비밀번호 재설정 시에도 일반 로그인과 동일한 비밀번호 정책을 적용해야 합니다.
DB가 유출되면 아직 사용되지 않은 모든 재설정 링크가 즉시 탈취됩니다.
원본 토큰은 이메일로만 보내고, DB에는 해시값만 저장하는 편이 좋습니다.
const token = randomBytes(32).toString('hex');
const tokenHash = createHash('sha256').update(token).digest('hex');
user.resetTokenHash = tokenHash;
검증할 때도 입력 토큰을 해시한 뒤 조회합니다.
현재는 비밀번호를 변경한 뒤에도 resetToken이 남아 있어 동일 토큰을 재사용할 수 있습니다.
user.resetTokenHash = null;
user.resetTokenExpiresAt = null;
가능하면 비밀번호 변경 시 모든 기존 세션과 refresh token도 폐기해야 합니다.
동시에 여러 요청이 들어오면 같은 토큰이 중복 사용될 수 있습니다. 토큰 검증과 비밀번호 변경을 트랜잭션 또는 조건부 업데이트로 처리해야 합니다.
예를 들어 다음 조건을 포함한 단일 업데이트를 사용할 수 있습니다.
UPDATE users
SET password_hash = :hash,
reset_token_hash = NULL,
reset_token_expires_at = NULL
WHERE reset_token_hash = :tokenHash
AND reset_token_expires_at > NOW();
업데이트된 행이 0개이면 토큰이 유효하지 않거나 이미 사용된 것으로 처리합니다.
현재 코드는 반환값만 보면 계정 존재 여부를 직접 노출하지 않지만, 응답 시간이나 이메일 발송 동작 차이로 계정 존재 여부를 추측할 수 있습니다.
항상 다음과 같이 동일한 일반 메시지를 반환하는 API 계층을 두는 것이 좋습니다.
이메일 주소가 등록되어 있다면 비밀번호 재설정 안내를 발송했습니다.
또한 IP와 이메일 기준의 rate limit, CAPTCHA 또는 abuse 방지 정책이 필요합니다.
DB 저장 후 이메일 발송이 실패하면 토큰은 발급됐지만 사용자는 링크를 받지 못합니다. 반대로 재시도 시 기존 토큰을 덮어쓸 수도 있습니다.
운영 환경에서는 다음 중 하나를 고려해야 합니다.
이메일과 비밀번호에 대한 검증이 없습니다.
NestJS에서는 DTO와 class-validator를 사용하는 것이 적절합니다.
import { createHash, randomBytes } from 'node:crypto';
import * as argon2 from 'argon2';
@Injectable()
export class PasswordResetService {
constructor(
private readonly userRepo: UserRepository,
private readonly mailer: MailerService,
) {}
async requestReset(email: string): Promise<void> {
const normalizedEmail = email.trim().toLowerCase();
const user = await this.userRepo.findOne({
where: { email: normalizedEmail },
});
// 호출자에게는 항상 동일한 응답을 반환하도록 API 계층에서 처리
if (!user) return;
const token = randomBytes(32).toString('hex');
const tokenHash = createHash('sha256')
.update(token)
.digest('hex');
user.resetTokenHash = tokenHash;
user.resetTokenExpiresAt = new Date(Date.now() + 30 * 60 * 1000);
await this.userRepo.save(user);
await this.mailer.send(normalizedEmail, 'reset', { token });
}
async resetPassword(token: string, newPassword: string): Promise<void> {
const tokenHash = createHash('sha256')
.update(token)
.digest('hex');
const user = await this.userRepo.findOne({
where: {
resetTokenHash: tokenHash,
resetTokenExpiresAt: MoreThan(new Date()),
},
});
if (!user) {
throw new BadRequestException('Invalid or expired token');
}
user.password = await argon2.hash(newPassword);
user.resetTokenHash = null;
user.resetTokenExpiresAt = null;
await this.userRepo.save(user);
// 필요하다면 기존 세션/refresh token도 모두 폐기
}
}
단, 위 예시도 동시 요청에 대한 완전한 방어를 위해서는 트랜잭션이나 조건부 업데이트가 필요합니다.
가장 먼저 수정해야 할 항목은 다음 네 가지입니다.
Math.random() 제거 및 암호학적 난수 사용현재 상태로 배포하면 토큰 탈취, 토큰 재사용, 비밀번호 평문 저장으로 인해 심각한 계정 탈취 및 개인정보 유출 위험이 있습니다.
| 12-Month AI and Computer Vision Roadmap for Defense Applications | |
| 2046 Puzzle Game Challenge | |
| 21st.dev component prompt | |
| 3D FACTORY | |
| 3D FPS Game |