본문 바로가기
빌드노트 검색

빌드노트

Referer가 origin만 보내거나 빠지는 이유

Referer가 없거나 origin만 남아도 요청은 성공할 수 있다. 두 loopback origin에서 네 정책을 비교하고, 수신 헤더와 document.referrer를 나눠 확인해 같은 origin에는 경로·쿼리가 남을 수 있음을 설명한다.

다른 origin으로 보낸 요청은 200인데 Referer에 현재 페이지의 경로가 없거나 헤더 자체가 없을 수 있다. 먼저 출발 페이지의 Referrer-Policy와 요청 목적지의 origin을 확인한다. 이 정책은 요청에 실어 보낼 출발 주소의 범위를 정한다. 헤더가 생략됐다는 사실만으로 네트워크 장애라고 판단할 수는 없다. 반대로 외부 요청에서 경로가 안 보였다고 같은 origin 요청에서도 숨겨진다고 생각하면 안 된다. MDN의 Referrer-Policy 설명 (새 탭에서 열림)

같은 페이지에서 포트만 다른 곳으로 요청했다

합성 주소 http://127.0.0.1:50112/source/demo?marker=FAKE-ONLY#fake-section을 출발 페이지로 사용했다. 같은 포트의 /resource와 다른 포트 50113의 /resource로 브라우저의 fetch() GET 요청을 보냈다. 이어 두 포트의 /landing으로 일반 링크를 눌러 이동했다. 외부 서버에는 실험 요청을 보내지 않았고, 경로·쿼리·fragment는 모두 가짜 값이다.

호스트가 둘 다 127.0.0.1이어도 포트가 다르면 cross-origin이다. HTTP·HTTPS URL의 origin은 scheme, 호스트, 포트로 정해지며 경로는 포함하지 않는다. 여기서 비교하는 것은 같은 사이트처럼 보이는지 여부가 아니라 이 세 값이다. MDN의 origin 정의 (새 탭에서 열림)

Windows의 Chromium 147.0.7727.15에서 출발 페이지 응답의 정책만 바꿨다. 각 조건을 새 페이지에서 시작했고 요청의 Referer를 직접 지정하지 않았다. 수신 서버에 남은 헤더는 자원 요청과 링크 이동에서 다음처럼 같았다. 여기서 ‘경로·쿼리 포함’은 출발 origin에 /source/demo?marker=FAKE-ONLY까지 붙은 값이다.

  • no-referrer: 같은 origin과 다른 origin 모두 Referer가 없었다.
  • origin: 두 목적지 모두 http://127.0.0.1:50112/만 받았다.
  • strict-origin-when-cross-origin: 같은 origin에는 경로·쿼리까지, 다른 origin에는 origin만 전달됐다.
  • same-origin: 같은 origin에는 경로·쿼리까지 전달됐고, 다른 origin에서는 헤더가 없었다.

가령 strict-origin-when-cross-origin에서 받은 실제 값은 아래와 같다. 같은 origin의 헤더에는 가짜 쿼리 값도 그대로 남았다. 두 요청 모두 응답은 200이었다.

같은 origin: Referer: http://127.0.0.1:50112/source/demo?marker=FAKE-ONLY
다른 origin: Referer: http://127.0.0.1:50112/

#fake-section은 어느 헤더에도 붙지 않았다. fragment는 referrer로 쓰기 전에 제거하는 항목이다. 하지만 쿼리는 fragment와 다르다. 이번 같은 origin 관찰처럼 정책에 따라 전달된다. W3C 명세의 URL 정리 절 (새 탭에서 열림)

정책 헤더를 생략한 비교에서도 이 브라우저는 strict-origin-when-cross-origin과 같은 결과를 냈다. 현재 기본값은 2026년 3월 20일 Editor’s Draft (새 탭에서 열림)와 MDN에서 확인했다. W3C의 /TR/ 주소는 2017년 Candidate Recommendation을 보여 주므로, 그 문서의 예전 기본값 설명을 현재 동작으로 옮기면 안 된다.

document.referrer는 다음 요청에 붙을 헤더가 아니다

document.referrer는 현재 문서에 들어올 때의 출발 정보다. 현재 문서가 이후에 보낼 모든 요청의 헤더를 보여 주는 속성이 아니다. 직접 연 문서라면 빈 문자열일 수 있다. HTML 표준의 document.referrer 정의 (새 탭에서 열림)

자신의 격리된 테스트 페이지에서 개발자 도구 Console을 열고 다음 입력을 실행한다. 링크로 이동한 뒤 도착 문서에서도 다시 실행하면 어느 문서의 값을 읽는지 함께 확인할 수 있다. 실제 계정이나 검색어가 든 주소 대신 가짜 경로·쿼리를 사용한다.

console.log(JSON.stringify({page: location.href, incoming: document.referrer}));

이번 실험에서 출발 페이지를 직접 연 뒤와, strict-origin-when-cross-origin 상태에서 다른 포트의 링크를 누른 뒤 얻은 출력은 다음과 같다.

{"page":"http://127.0.0.1:50112/source/demo?marker=FAKE-ONLY#fake-section","incoming":""}
{"page":"http://127.0.0.1:50113/landing?case=nav-cross","incoming":"http://127.0.0.1:50112/"}

첫 줄의 빈 문자열은 출발 페이지에 들어온 정보가 없다는 뜻이다. 그 페이지에서 보낸 같은 origin 자원 요청의 Referer는 앞서 본 것처럼 경로·쿼리를 포함했다. 자원 요청 뒤에도 출발 문서의 document.referrer는 계속 빈 문자열이었다. 따라서 Console 값만으로 자원 요청의 헤더를 추정하지 않는다.

도착 페이지의 응답에는 별도로 Referrer-Policy: no-referrer를 설정했다. 같은 origin 링크로 도착한 문서의 document.referrer에는 여전히 출발 경로·쿼리가 남아 있었지만, 그 도착 문서가 새로 보낸 자원 요청에는 Referer가 없었다. 도착 페이지의 정책을 보고 이미 들어온 요청의 헤더까지 없었을 것이라고 판단해서도 안 된다.

내 요청에서는 출발 응답과 수신 헤더를 대조한다

브라우저 개발자 도구의 Network에서 출발 문서 응답의 Referrer-Policy를 읽고, 그 문서에서 발생시킨 요청의 Request Headers에 있는 Referer를 따로 본다. 링크 이동을 확인할 때는 이동 전에 Preserve log를 켜서 출발 문서 기록을 남긴다. 서버를 관리한다면 격리된 테스트 수신 지점에서도 같은 요청의 헤더를 확인한다. 위 결과는 브라우저 화면의 추정값이 아니라 수신 서버에 도착한 값으로 비교했다.

응답 헤더는 Referrer-Policy, 요청 헤더는 Referer다. 철자가 다르다. 요청을 받은 목적지의 응답 정책만 보고 출발 문서의 정책을 대신 판단하지 않는다. 문서 전체 정책 외에도 HTML의 meta name="referrer", 해당 요소의 referrerpolicy, 링크의 rel="noreferrer" 같은 설정을 확인한다. MDN의 HTML 설정 설명 (새 탭에서 열림)

예를 들어 다른 origin에는 출발 origin만 전하고 같은 origin에는 경로·쿼리를 유지하려는 문서라면, 출발 문서를 제공하는 서버의 응답에 다음 정책을 명시할 수 있다. 이번 실험도 이 헤더를 실제로 내려보내 비교했다.

Referrer-Policy: strict-origin-when-cross-origin

같은 origin에도 경로·쿼리를 전달하지 않으려면 origin, 출발 origin 자체도 전달하지 않으려면 no-referrer가 요구에 맞는지 검토한다. same-origin은 외부에 보내지 않는 정책이지만 내부 요청의 경로·쿼리까지 숨기지는 않았다. 정책을 고른 뒤에는 새로 문서를 로드하고 실제 요청을 다시 발생시켜 확인한다.

결과가 다르면 먼저 출발 문서에 실제 적용된 정책과 요소별 설정, 목적지의 scheme·호스트·포트를 다시 비교한다. 브라우저의 개인정보 보호 설정도 출발 정보를 더 줄일 수 있다. 이때는 정책 문자열만으로 값을 단정하지 말고 수신 결과를 기준으로 좁힌다. 명세의 사용자 제어 범위 (새 탭에서 열림)

이번 측정은 두 loopback HTTP origin에서의 단순 GET 자원 요청과 링크 이동이다. HTTPS에서 일반 HTTP로 내려가는 downgrade, 리다이렉트 체인, iframe·외부 CSS, 다른 브라우저와 확장 기능의 영향은 실행하지 않았다. 특히 strict-origin-when-cross-origin이 HTTPS에서 신뢰할 수 없는 HTTP 목적지로 갈 때 헤더를 생략하는 것은 명세에 적힌 동작 (새 탭에서 열림)이며, 이번 HTTP 실험의 측정값으로 주장하지 않는다.

이 관찰에서 내릴 결정은 페이지 URL에 토큰·개인 검색어 같은 민감값을 넣지 않는 것이다. cross-origin에서 경로가 줄어드는 정책도 같은 origin의 쿼리까지 감추지는 않았다. no-referrer 역시 URL 자체를 지우는 기능은 아니다. 이 글의 정책은 이동과 자원 요청에 붙는 출발 정보를 다루며, 이동 목적지 허용이나 접근 권한 검증은 별도로 필요하다.

← 전체 글 보기