절대 실행되지 않은 쿼리의 신비한 사건
Source: Dev.to
Background
지난 주말에 개인 웹사이트 코드를 리팩터링하던 중 이상한 버그를 발견했습니다. 저는 가벼운 러너이며, 제 모든 달리기 기록을 모아 놓은 페이지를 운영하고 있습니다. 데이터는 Neon 데이터베이스에 저장돼 있으며, 직접 만든 nuxt‑neon 모듈을 통해 읽어오고 있습니다.
모듈의 최신 변경 사항에 맞게 코드를 업데이트하던 중, 특히 날짜(연도 및/또는 월) 필터링 방식을 바꾸고 있었습니다. 원래는 문자열 리터럴 WHERE 조건을 사용했었습니다:
r.date BETWEEN '${fromDate}' AND '${toDate}'
새로운 객체 기반 구문을 시험해 보고 싶었습니다:
where.push({
column: 'r.date',
condition: '>=',
value: fromDate,
});
where.push({
column: 'r.date',
condition: '`** – I wondered if the literal characters were being stripped or escaped somewhere.
하루 동안 막다른 길에 부딪힌 뒤, Copilot에게 도움을 청했습니다. Copilot은 프론트엔드의 잡음을 피하기 위해 curl로 요청을 재현해 보라고 제안했습니다. 그렇게 하니 실제 원인이 드러났습니다.
Root Cause: Nuxt Security’s XSS Protection
요청은 Nuxt Security 모듈의 XSS 공격 방어 기능에 의해 차단되었습니다. 문제를 일으킨 코드는 다음 위치에 있었습니다:
...\node_modules\.pnpm\nuxt-security@2.4.0_magicast@0.3.5_rollup@4.52.5\
node_modules\nuxt-security\dist\runtime\server\middleware\xssValidator.js:38:18
Nuxt Security는 “ 문자들을 잠재적인 악성 스크립트 삽입으로 간주하여 요청을 차단하고, 그 결과 모호한 500 오류가 발생했습니다.
Mitigation
문제를 우회하기 위해 클라이언트 측에서 각괄호를 텍스트 약어로 변환하고, 서버에서 실제 SQL 쿼리를 구성할 때 다시 원래 문자로 되돌리는 내부 매핑을 추가했습니다. 사용자 편의를 위해 원래 기호도 그대로 사용할 수 있게 하면서 GT와 LT를 대안으로 제공했습니다.
Lessons Learned
1. AI에게 도움을 청하는 것을 주저하지 말 것
정확한 질문을 떠올리지 못하더라도 상황을 설명하면 (“왜 이런 오류가 발생하나요?”) 유용한 실마리를 얻을 수 있습니다.
2. 문제를 격리하라
백엔드가 응답하지 않는 것처럼 보일 때는 엔드포인트를 직접 호출해 보세요(예: curl 사용). 이렇게 하면 프론트엔드 스택의 간섭을 배제할 수 있습니다.
3. 서드파티 소프트웨어를 고려하라
버그는 직접 작성하지 않은 라이브러리나 미들웨어에서 발생할 수 있습니다. 이번 경우에는 Nuxt Security의 XSS 검증기가 숨은 장애물이었다는 점을 기억하세요.
Closing Thoughts
저는 20년 넘게 소프트웨어를 개발해 왔지만 여전히 사소하고 답답한 버그에 부딪히곤 합니다. 이런 경험을 공유함으로써 시니어 개발자라도 문제에 걸릴 수 있다는 점을 상기시키고, 체계적인 디버깅과 약간의 AI 도움을 통해 다시 정상 궤도로 돌아올 수 있음을 전하고 싶습니다.
읽어 주셔서 감사합니다. 비슷한 문제를 겪는 분들에게 도움이 되길 바랍니다.