빌드노트
같은 서버의 iframe인데 parent.document가 SecurityError를 낸다
같은 서버에서 내려받은 iframe도 sandbox가 불투명 원점을 부여하면 부모 DOM에 접근할 수 없다. 다섯 조건의 실제 브라우저 비교로 location.origin·window.origin·메시지 origin을 구별하고, 권한을 추가하기 전에 필요한 통신 범위를 판단한다.
부모와 iframe을 같은 scheme·호스트·포트에서 제공해도, 자식의 parent.document 접근이 SecurityError로 막힐 수 있다. iframe에 sandbox="allow-scripts"가 있으면 스크립트는 실행되지만 문서에는 별도의 불투명 원점(opaque origin)이 부여된다. URL이 같아 보인다는 사실만으로 부모 DOM에 접근할 권한이 생기지 않는다.
이를 확인하려고 새 loopback 서버에서 합성 부모·자식 문서를 실행했다. 자식의 location.origin은 부모와 같았지만 window.origin과 부모가 받은 메시지의 event.origin은 문자열 "null"이었다. 실제 서비스 장애 기록이 아니라 sandbox 조건만 바꿔 비교한 결과다.
부모에서 먼저 확인할 값
자신의 개발 페이지에서는 Network에서 자식 HTML의 응답과 최종 주소를 확인한 뒤, 부모 페이지 콘솔에서 다음 코드를 실행한다. #widget은 조사할 iframe의 선택자로 바꾼다. 이 코드는 iframe의 설정 URL·sandbox 값과 부모에서 자식 문서를 읽을 수 있는지를 확인한다.
const targetFrame = document.querySelector("#widget");
console.log(JSON.stringify({
sandbox: targetFrame.getAttribute("sandbox"),
parentUrlOrigin: location.origin,
frameUrlOrigin: new URL(targetFrame.src).origin
}));
try {
console.log(targetFrame.contentWindow.document.title);
} catch (error) {
console.log(error.name);
}
이번 실험의 allow-scripts 조건에서 위 코드를 실행한 출력이다. 포트는 실행 때 배정받은 값이다.
{"sandbox":"allow-scripts","parentUrlOrigin":"http://127.0.0.1:53517","frameUrlOrigin":"http://127.0.0.1:53517"}
SecurityError
두 URL의 origin이 같아도 DOM 접근은 거절됐다. 여기서 frameUrlOrigin은 부모가 설정한 src URL에서 계산한 값이다. 리다이렉트나 이후 이동으로 도착한 문서의 원점을 증명하는 값은 아니다. 이번 비교에는 리다이렉트나 프레임 이동이 없었다.
getAttribute("sandbox")가 JavaScript의 null이면 속성이 없고, 빈 문자열 ""이면 sandbox가 적용된 상태다. 빈 값은 제한을 모두 거는 조건이다. allow-scripts는 그중 자식 스크립트 실행 제한만 푼다. MDN의 iframe sandbox 설명 (새 탭에서 열림)
부모에서 contentWindow.document를 읽을 때와 contentDocument를 조회할 때의 결과도 구별한다. 위 실패 조건에서 전자는 SecurityError를 던졌고 후자는 JavaScript의 null을 반환했다. 같은 원점이 아닐 때 contentDocument가 null을 반환하는 동작은 해당 속성의 문서 (새 탭에서 열림)에도 명시돼 있다. 여기에 .body를 이어 붙여 생긴 오류를 보면 원래의 접근 제한을 놓칠 수 있다.
자식 URL의 origin과 문서의 origin을 나눠 본다
자식 페이지를 수정할 수 있고 스크립트 실행이 허용돼 있다면, 자식 소스에 아래 로그를 넣고 새로 로드한다. 부모 콘솔의 전역 값 대신 자식이 실행하는 코드에서 두 origin을 읽는 것이 목적이다.
console.log(JSON.stringify({
urlOrigin: location.origin,
documentOrigin: window.origin
}));
같은 allow-scripts 조건에서 자식이 남긴 원문 로그다.
{"urlOrigin":"http://127.0.0.1:53517","documentOrigin":"null"}
location.origin은 현재 URL의 원점을 반환하고, window.origin은 전역 실행 환경의 원점을 문자열로 보여 준다. 따라서 이 조건에서는 서로 다른 결과가 나올 수 있다. 이 로그는 원인 확인용이며, 자식이 메시지 데이터로 주장하는 origin을 인증 정보로 믿으라는 뜻은 아니다. Location.origin (새 탭에서 열림), Window.origin (새 탭에서 열림)
HTML 표준은 sandbox의 원점 제한이 문서를 불투명 원점으로 만들도록 정한다. 또 불투명 원점은 문자열로 직렬화하면 "null"이 된다. 여러 문서가 같은 문자열을 표시한다고 해서 하나의 원점을 공유하는 것은 아니다. "null"은 JavaScript 값 null도, 신뢰할 사이트의 주소도 아니다. sandbox의 원점 제한 (새 탭에서 열림), 원점의 직렬화와 비교 (새 탭에서 열림)
스크립트 허용과 DOM 접근은 따로 바뀌었다
같은 /child.html을 새 iframe에 로드하면서 다섯 조건을 비교했다. 부모는 자식 문서의 제목을 읽고, 실행 가능한 자식은 부모 문서의 제목을 읽은 결과를 메시지로 보냈다. 부모의 수신 리스너는 iframe을 로드하기 전에 등록했다.
- sandbox 속성 없음: 자식 스크립트가 실행됐다. 양쪽에서 상대 문서 제목을 읽었고, 부모가 받은
event.origin은http://127.0.0.1:53517이었다. sandbox="": 부모의 자식 DOM 접근은SecurityError였다. 브라우저에 자식 스크립트의 실행 차단 로그가 남았고, 로드 후 600ms 관찰 동안 메시지가 없었다. 자식의 부모 DOM 접근은 코드가 실행되지 않았으므로 측정하지 못했다.sandbox="allow-scripts": 자식 스크립트는 실행됐지만 양쪽 DOM 접근은 모두SecurityError였다. 메시지는 도착했고event.origin은"null"이었다.sandbox="allow-same-origin": 부모는 자식의 제목Sandbox child를 읽었다. 자식 스크립트는 차단됐으며 같은 관찰 구간에 메시지가 없었다. DOM을 읽을 수 있다는 사실이 자식 스크립트도 실행된다는 뜻은 아니었다.sandbox="allow-scripts allow-same-origin": 양쪽 DOM 접근과 자식 메시지 전송이 가능했다.event.origin은 서버 주소였고, 브라우저는 이 조합의 sandbox 이탈 가능성을 경고했다.
allow-same-origin만 있는 조건이 두 권한의 차이를 보여 준다. 부모의 DOM 접근은 가능해졌지만 자식 코드는 계속 실행되지 않았다. allow-same-origin은 sandbox가 강제하는 불투명 원점 제한을 풀며, 다른 URL 원점의 문서를 부모와 같은 원점으로 바꾸는 토큰은 아니다. MDN의 두 토큰 설명 (새 탭에서 열림)
DOM을 열어야 하는지부터 결정한다
allow-scripts 조건에서는 DOM 접근이 막힌 채로도 자식의 보고 메시지가 부모에 도착했다. 자식은 부모의 정확한 origin을 targetOrigin으로 지정했다. targetOrigin은 받을 창의 원점을 검사하므로, 발신자인 자식의 원점이 불투명하다는 사실과 구별해야 한다. postMessage의 targetOrigin (새 탭에서 열림)
부모 수신 이벤트의 event.source === iframe.contentWindow도 true였다. 하지만 event.origin === "null"을 새 허용 조건으로 추가하는 것만으로 특정 문서를 신뢰할 수는 없다. 이 문자열로는 서로 다른 불투명 원점을 구별하지 못한다. source는 창 참조를 확인하며 그 창의 이후 이동까지 고정하지 않는다. 수신 origin과 메시지 데이터는 별개이고, 형식 검사는 여전히 필요하다. 메시지 이벤트의 origin·source (새 탭에서 열림), 표준의 수신 검사 지침 (새 탭에서 열림)
이 관찰에서 내려야 할 결정은 필요한 동작의 범위다. 자식이 높이 같은 제한된 값만 알리면 되는 연동이라면, 부모가 메시지를 검증해 정해진 작업만 수행하도록 구성하고 원점 격리를 유지할 수 있다. 이 실험의 수신부는 결과를 기록했을 뿐 권한 있는 작업을 수행하지 않았다. 단순 알림이 온다는 결과를 계정·결제 같은 작업의 승인 근거로 확대하지 않는다.
부모 DOM을 직접 읽는 요구가 있다면 그 권한을 자식 코드에 줘도 되는지 먼저 검토한다. 같은 URL 원점의 자식에 allow-scripts와 allow-same-origin을 함께 주면, 자식이 iframe 요소에 접근해 sandbox 속성을 제거할 수 있다고 MDN이 경고한다. 이번 조합은 차이를 확인하기 위한 비교 조건이다. 접근 오류를 없애는 보편적인 수정안으로 권할 수 없다. 같은 원점에서 두 토큰을 함께 사용할 때의 경고 (새 탭에서 열림)
관찰 환경은 Windows 11, Chrome 156.0.8078.47의 headless 실행, Playwright 1.59.0이었다. 새 브라우저 프로필과 loopback의 합성 문서만 사용했고 종료 후 포트가 닫힌 것을 확인했다. 다른 브라우저, CSP sandbox나 중첩 iframe의 추가 제한, 프레임 이동·교체, 부모에서 불투명 원점 자식으로 보내는 회신, 쿠키·저장소 접근은 실행하지 않았다. sandbox 속성을 제거하는 이탈 동작도 실행하지 않았다. 위 조회에서 속성이 없는데 같은 오류가 난다면 이 실험의 원인으로 단정할 수 없으며 최종 문서의 원점과 추가 sandbox 정책을 따로 확인해야 한다.