자바스크립트의 in 연산자는 명단을 확인할 때 쓸데없이 집안 내력까지 뒤진다. 객체에 직접 적어 둔 키만 찾는 게 아니라 프로토타입 체인에 물려 있는 키도 “있다”고 답한다. 평범한 객체를 허용 목록처럼 쓸 때 이 차이는 말장난이 아니라 검증 우회가 된다.
실제 스킬 검사기에는 일부 스킬만 필수 문서 항목 검사를 건너뛰게 하는 목록이 있었다. 의도는 단순했다. 디렉터리 이름이 목록에 직접 등록돼 있을 때만 예외를 주는 것이다. 하지만 코드는 dirName in SECTION_EXEMPT_SKILLS로 확인했다.
그래서 디렉터리 이름을 constructor로 만들면 이상한 일이 벌어졌다. 이 이름은 한 단어짜리 케밥 표기 규칙을 통과했고, 목록에 직접 등록되지 않았는데도 Object.prototype.constructor를 찾아냈다. 검사기는 예외 대상이라고 판정했고, 필수 항목 다섯 개가 하나도 없는 문서에 오류 0개를 돌려줬다.
문제는 자바스크립트 객체가 나빠서가 아니다. 연산자가 답하는 질문과 정책이 묻고 싶은 질문이 달랐다. in은 “이 이름을 상속 계보 어디선가 찾을 수 있는가”를 묻는다. 허용 목록은 “관리자가 이 객체에 직접 등록했는가”를 물어야 한다.
수정은 두 조회를 모두 Object.hasOwn으로 바꾸는 것이었다. 하나는 실제 예외 여부를 정했고, 다른 하나는 문서가 스스로 예외를 주장했을 때 이를 거부하는 경계였다. 한쪽만 고치면 검사 결과와 우회 방지 규칙이 서로 다른 명단을 보게 된다.
회귀 시험도 이름 그대로의 실패를 재현했다. constructor라는 스킬에 필수 항목을 전부 빼고 실행해 exempt: false와 오류 다섯 개를 요구했다. 진짜 허용 대상은 계속 통과시켰고, 주변 동작 여섯 가지도 그대로 유지했다. 새 시험은 수정 전 코드에서만 실패했다.
허용 목록, 기능 플래그, 권한표처럼 키의 존재가 예외나 권한을 주는 곳에서는 “있다”보다 “직접 등록됐다”가 중요하다. 검사기는 거짓말하지 않았다. 조상 중에 같은 이름이 있느냐고 물었고, 아주 성실하게 가계도를 제출했을 뿐이다.