커서 생성 코드에서의 SQL Injection: 놓치는 부분은?
Source: Dev.to

TL;DR
- Cursor와 대부분의 AI 편집기는 사용자 입력을 템플릿 리터럴에 직접 삽입한 SQL 쿼리를 생성합니다.
- 이는 CWE‑89 SQL 인젝션이며, 파라미터화된 쿼리를 사용하면 약 10초 만에 완전히 해결됩니다.
- 자동화된 SAST가 배포 전에 이를 잡아주지만, 대부분의 AI‑생성 코드베이스는 이를 실행하지 않습니다.
The Vulnerable Pattern (CWE‑89)
app.get('/api/users', async (req, res) => {
const { filter } = req.query;
const result = await db.query(
`SELECT * FROM users WHERE name = '${filter}'`
);
res.json(result.rows);
});
${filter} 삽입은 사용자 제어 데이터를 SQL 문자열에 직접 넣습니다. ' OR '1'='1 같은 값을 제공하면 모든 행이 반환됩니다. 이것이 고전적인 SQL 인젝션이며, 백틱 문자열은 데이터베이스에 도달하기 전 단순히 문자열을 연결한 것에 불과합니다.
Why AI Editors Keep Writing This
모델은 수백만 개의 코드 예제로 학습되었으며, 그 중 여전히 문자열 삽입을 사용한 쿼리가 많이 포함돼 있습니다—2014‑2018년 튜토리얼, 오래된 Stack Overflow 답변, 검증되지 않은 GitHub 레포지토리 등. 생성된 코드가 실행되고 테스트를 통과하며 구문적으로 올바르게 보이기 때문에, 모델은 보안 결함에 대한 부정적인 피드백을 받지 못합니다. 문제되는 라인은 쿼리 구성 부분뿐이며, 쉽게 간과될 수 있습니다.
The Fix
파라미터화된 쿼리를 사용하면 SQL 텍스트와 사용자 데이터를 분리할 수 있습니다.
app.get('/api/users', async (req, res) => {
const { filter } = req.query;
const result = await db.query(
'SELECT * FROM users WHERE name = $1',
[filter]
);
res.json(result.rows);
});
- PostgreSQL(
pg드라이버)에서는$1이 플레이스홀더입니다. - MySQL(
mysql2)에서는?를 플레이스홀더로 사용합니다. - Sequelize나 Prisma와 같은 ORM을 사용할 경우, 원시 SQL 문자열 대신 쿼리‑빌더 메서드를 사용하는 것이 좋습니다.
백틱이 포함된 첫 번째 인자를 가진 db.query 호출을 코드베이스 전체에서 검색하면 대부분의 취약한 사례를 찾아낼 수 있습니다. SafeWeave와 같은 도구는 Cursor와 Claude Code와 통합되어 Semgrep를 사용한 SQL‑인젝션 탐지 규칙을 실행합니다. 간단한 pre‑commit 훅도 추가할 수 있습니다:
semgrep --config p/sql-injection
코드가 배포되기 전에 이러한 패턴을 감지하고 수정하면 프로덕션 단계에서 인젝션 사고가 발생할 위험을 없앨 수 있습니다.