외부 기능을 글에 넣는다고 모든 외부 연결이 필요한 것은 아니다. 나는 현재 블록에서 코드와 스타일, 데이터 연결과 프레임을 따로 선언하는 구조를 읽고 그 선언을 확인하는 작은 함수를 분리했다. 등록과 실제 표시까지 서로 다른 단계로 설명한다.
어떤 외부 자원이 필요한지 먼저 적는다
스크립트 파일을 받는 것과 데이터를 조회하는 것, 다른 페이지를 프레임에 띄우는 것은 다른 요청이다. 현재 HTML 블록은 scripts와 styles, connections와 frames, images와 media를 각각 받는다. 한 기능에 필요한 주소를 찾았다면 그 주소가 어느 요청을 담당하는지 먼저 확인하는 구조다.
등록한 서비스가 있다고 모든 목록에 같은 주소를 넣지는 않는다. 실제 예제에 필요한 용도만 선언하면 나중에 기능을 바꾸거나 연결을 회수할 때 무엇을 살펴야 하는지 분명해진다. 새 모듈은 용도를 알 수 없는 입력도 거절한다. 이것은 재사용할 선언의 계약이며 외부 서비스의 모든 요청을 자동으로 찾아주는 분석기는 아니다.
origin과 파일 주소를 같은 값으로 다루지 않는다
origin은 프로토콜과 호스트, 포트의 경계다. 특정 파일의 경로나 검색 조건은 다른 정보다. 현재 등록과 블록 검사는 HTTPS origin에서 계정 정보와 경로, 쿼리와 조각 표식을 제외하도록 한다. 새 함수도 입력을 해석한 뒤 같은 조건을 확인한다. 파일 주소 전체를 등록 목록에 잘못 넣는 실수를 줄이는 방법이다.
호스트가 비슷해 보여도 포트와 프로토콜이 다르면 같은 조건으로 취급하지 않는다. 기본 포트와 대소문자 등은 URL 해석 결과의 정규화 값을 사용한다. 실제 서비스를 선택할 때는 그 경계가 어느 운영 주체와 자료를 가리키는지도 검토해야 한다. 형식이 맞다는 결과는 서비스 내용이나 사용 권한의 승인이 아니다.
사이트의 검토 목록과 개별 선언을 대조한다
개별 블록이 주소를 적었다고 곧 허용하지 않는다. 현재 사이트에는 검토 근거와 목적을 남기는 등록 목록이 있고 블록의 origin이 그 목록에 있는지 확인한다. 새 계획 함수는 등록 목록을 입력으로 받아 각 용도에서 중복 주소를 정리하고 등록되지 않은 항목을 거절한다. 실제 등록 작업은 인증된 관리 API의 별도 동작이다.
등록을 지우면 이후 원고 검토에서 그 조건이 달라질 수 있다. 원고를 내리는 동작은 이런 공개 조건 변경으로 막히지 않도록 유지한다. 실제로 어떤 글이 그 서비스를 사용하는지 읽고 수정이나 철회 범위를 정해야 한다. 새 모듈은 입력 목록을 확인할 뿐 서버 정책을 바꾸거나 이미 발행된 글을 자동으로 수정하지 않는다.
코드와 구현 살펴보기
용도별 등록 origin을 대조하는 함수
const kinds=['scripts','styles','connections','frames','images','media'];
export function exactResourceOrigin(value){
let url;try{url=new URL(value);}catch{throw new TypeError('Absolute HTTPS origin required');}
if(url.protocol!=='https:'||url.username||url.password||url.pathname!=='/'||url.search||url.hash)throw new TypeError('Origin must exclude credentials,path,query and fragment');
return url.origin;
}
export function planResourceOrigins(resources,registered){
if(!resources||typeof resources!=='object'||Array.isArray(resources)||!Array.isArray(registered))throw new TypeError('Explicit resource object and registered origin list required');
if(Object.keys(resources).some(kind=>!kinds.includes(kind)))throw new TypeError('Unknown resource purpose');
const allowed=new Set(registered.map(exactResourceOrigin)),result={};
for(const kind of kinds){
const values=resources[kind]||[];if(!Array.isArray(values)||values.length>12)throw new RangeError('Each purpose supports at most 12 declared origins');
const origins=[...new Set(values.map(exactResourceOrigin))];
if(origins.some(origin=>!allowed.has(origin)))throw new Error('Origin is not registered for this site');
result[kind]=origins;
}
return {resources:result,scope:'Declaration planning only. Registration needs separate actual review; no fetch,CORS,iframe-provider permission or complete policy enforcement is demonstrated.'};
}
등록과 브라우저에서의 사용 가능을 나눈다
등록된 주소여도 외부 서비스가 다른 사이트의 프레임 표시를 막거나 로그인과 연결 조건을 요구할 수 있다. API 응답을 읽는 경우에는 응답을 브라우저가 사용할 수 있는지도 별도 조건이다. 현재 편집실은 그 서비스의 제한을 우회하는 기능으로 설명하지 않는다. 연결 선언과 실제 표시 결과를 이어서 확인해야 한다.
스크립트 실행 토큰과 콘텐츠 보안 정책도 용도 선언과 함께 읽는다. 등록 목록에 코드 주소가 있어도 해당 HTML 블록의 scripts가 꺼져 있으면 같은 실행 조건이 아니다. 무엇을 등록했고 무엇을 요청하며 어떤 실행 기능을 켰는지를 따로 적으면 원인을 찾기 쉽다. 인터넷에서 보이는 모든 화면이 같은 방식으로 삽입된다고 약속하지 않는다.
연결하지 않는 모형으로 선언을 먼저 읽는다
새 로컬 검사에서는 용도별 중복과 빈 목록, 등록하지 않은 origin과 경로·계정·프로토콜·포트 조건을 확인했다. 아래 예제는 입력 주소를 요청하지 않고 등록 상태를 설명용 체크박스로 받는다. 실제 서버 등록이나 외부 서비스 호출은 수행하지 않는다. 반환된 목록은 어떤 용도에 주소가 남는지 읽기 위한 자료다.
새 예제의 브라우저 조작과 외부 공급자의 동작은 미검증이다. 실제 적용에서는 검토한 서비스의 제공 방식과 응답, 스크립트와 프레임을 단계별로 확인해야 한다. 나는 처음부터 모든 연결을 한 묶음으로 열기보다 필요한 선언과 다음 검사 지점을 남겨 확장을 이어가려 한다.