첫 글자를 보고 안쪽이라고 했다
주식 앱에서 로그인이 필요한 자리를 누르면 로그인 화면으로 갔다가 원래 보던 곳으로 돌아온다. 어디로 돌아올지는 주소에 실어 보낸다. 그 값을 그대로 믿으면 로그인 직후에 아무 데로나 보낼 수 있으니, 돌아갈 곳이 우리 사이트 안인지 보는 검사가 하나 붙어 있었다.
검사는 두 줄. 슬래시로 시작하지 않으면 버린다. 슬래시 두 개로 시작해도 버린다. 앞 줄은 http로 시작하는 남의 주소를 막고, 뒤 줄은 프로토콜만 생략한 바깥 주소를 막는 것. 남는 건 슬래시 하나로 시작하는 우리 쪽 경로. 읽어보면 말이 된다.
배포 직전 점검에서 걸린 값은 슬래시 뒤에 역슬래시를 붙인 것이었다. 첫 글자는 슬래시고 둘째 글자는 슬래시가 아니니 검사는 통과. 그런데 브라우저의 주소 파서는 http 계열 주소 안에서 역슬래시를 슬래시로 읽는다. 파서 눈에는 슬래시 두 개로 시작하는 바깥 주소가 되는 것. 주소에서 값을 꺼내는 동안 부호가 풀려 진짜 탭 문자가 되는 경우도 마찬가지였다. 파서는 그런 문자를 읽기 전에 먼저 털어내니까, 우리 눈에는 이상한 경로로 보이던 게 파서 눈에서는 깔끔한 외부 주소가 된다.
하필 이 자리가 로그인 직후. 비밀번호를 막 넣은 사람이 다음 화면으로 넘어가는 순간에 우리 도메인 아닌 곳으로 떨어진다. 거기에 우리 로그인 화면과 같은 모양을 세워두면 다시 넣으라고 말할 필요도 없는 것.
틀린 이유가 계산 실수는 아니다. 우리 검사는 그 값을 글자로 읽었고, 실제로 그 값을 쓰는 쪽은 주소로 읽는다. 글자로 보면 슬래시 하나로 시작하는 이상한 경로. 주소로 보면 남의 집 대문. 같은 열 몇 글자를 두 문법으로 읽은 것이고, 먼저 읽은 쪽이 우리인데 실제로 움직이는 건 나중에 읽은 쪽이다.
이 검사를 쓸 때 나는 허용 목록을 쓴다고 생각했다. 상대 경로만 통과시킨다고 주석까지 달아뒀으니까. 적혀 있던 건 거절 목록 두 줄이었다. 내가 떠올린 바깥 주소의 모양이 두 가지였고 그 두 가지를 적어둔 것. 안 떠오른 모양은 목록에 없다.
고친 방식은 판정을 파서한테 맡기는 것. 받은 값을 우리 주소를 기준으로 한 번 파싱하고, 나온 오리진이 우리 것과 다르면 버린다. 여기까지는 예상 가능한 수정이고, 붙은 줄이 하나 더 있다. 돌려주는 값을 받은 원문이 아니라 파서가 다시 조립한 경로로 바꿨다. 앞의 것만 고치면 우리가 승인한 글자와 브라우저가 쓸 글자가 여전히 따로 있게 되니까. 검사는 파서로 하고 넘기는 건 원문이면, 판정과 실행 사이에 한 번 더 읽는 사람이 끼는 셈.
이 건은 누가 밟아서 나온 게 아니라 배포 전 점검에서 나왔다. 로그에는 아무것도 없다. 남을 리가 없다. 검사를 통과한 값이니 우리 쪽 처리는 정상이고, 나간 뒤에 벌어지는 일은 우리 서버를 거치지 않으니까. 안 터진 걸 세는 방법이 없는 종류.
바뀐 줄은 열한 줄. 하는 일은 한 문장으로 줄어든다. 우리 눈으로 읽지 말고, 나중에 이 값을 실제로 읽을 눈으로 한 번 통과시킬 것. 통과한 값은 우리가 받은 그 값이 아니다. 파서가 읽고 다시 쓴 값이다. 대개는 둘이 같고, 다른 날이 오늘이었다.