llms.txt

  • WPCode로 보안 헤더 추가하다 코드가 깨진 이유

    WPCode로 보안 헤더 추가하다 코드가 깨진 이유

    SEO 감사 결과를 받아들고 액션 아이템 목록을 훑었다. 생각보다 할 게 많았다. 그중에서 오늘 WPCode로 건드린 건 두 가지다.

    SEO 감사에서 나온 보안 헤더 추가, 왜 하려고 했나?

    감사 리포트에 보안 헤더(HTTP Security Header, 브라우저에게 보안 정책을 알려주는 응답값)가 빠져 있다고 나왔다. 구체적으로는 세 가지였다. HSTS(HTTP Strict Transport Security, HTTPS 연결만 허용하도록 강제하는 헤더), X-Content-Type-Options(브라우저가 파일 형식을 멋대로 해석하지 못하게 막는 헤더), Referrer-Policy(다른 사이트로 이동할 때 내 사이트 주소를 얼마나 보낼지 제어하는 헤더). 검색엔진 점수에 직접 영향을 주는 항목은 아니지만, 빠져 있으면 감사 점수가 깎인다.

    여기에 하나 더 추가하고 싶은 게 있었다. llms.txt다. llms.txt는 AI 크롤러(ChatGPT, Claude 같은 AI가 사이트 정보를 수집할 때 읽어가는 파일)가 내 사이트 구조를 파악할 수 있도록 만들어두는 텍스트 파일이다. robots.txt(검색엔진 크롤러에게 크롤링 규칙을 알려주는 파일)의 AI 버전이라고 보면 된다.

    문제는 서버 접근 방법이었다. 예전에 랭크매스 SEO 점수를 16점에서 76점까지 올릴 때도 비슷하게 REST API만으로는 한계가 있었는데, 이번에도 그랬다. FTP(File Transfer Protocol, 서버에 파일을 올리고 내리는 방식)도 없고 SSH(Secure Shell, 서버에 원격으로 접속하는 방식)도 없었다. 서버에 직접 파일을 올릴 수 있는 방법이 아예 없는 환경이었다. 그래서 선택한 게 WPCode다. WPCode는 워드프레스 관리자 화면에서 PHP 스니펫(snippet, 짧은 코드 조각)을 직접 추가하고 실행할 수 있는 플러그인(plugin, 워드프레스에 기능을 추가하는 확장 프로그램)이다.

    WPCode로 보안 헤더·llms.txt 추가하다 겪은 삽질기 스크린샷 1

    위는 WPCode 플러그인 편집 화면이다. PHP 코드를 여기에 붙여넣고 활성화하면 워드프레스가 코드를 실행한다.

    WPCode 에디터의 CodeMirror 자동완성이 PHP 코드를 깨뜨린 문제

    WPCode의 코드 에디터(code editor, 코드를 입력하고 수정하는 편집 창)는 CodeMirror라는 라이브러리(library, 특정 기능을 묶어놓은 코드 모음)로 만들어져 있다. 편집기 기능이 꽤 풍부한데, 그중에 자동 괄호 완성 기능이 있다. 괄호를 하나 열면 닫는 괄호를 자동으로 붙여주는 기능이다.

    이게 문제였다. Claude Code로 에디터에 타이핑을 시켰더니, 자동완성이 끼어들면서 코드 중간에 괄호가 두 개씩 들어가거나 엉뚱한 위치에 삽입되는 일이 계속 생겼다. PHP 코드는 괄호 하나만 틀려도 에러가 난다. 별로였다.

    그러다가 예상치 못한 확인창이 떴다. 뭘 클릭한 건지도 모르겠는 상태에서 페이지가 about:blank로 넘어갔다. about:blank는 아무것도 없는 빈 페이지다. 입력하던 코드가 전부 날아갔다. 저장도 안 된 상태로.

    타이핑 대신 setValue()로 코드를 통째로 밀어넣다

    다시 페이지를 열었다. 이번엔 방식을 바꿨다.

    CodeMirror는 자바스크립트(JavaScript, 웹 브라우저에서 실행되는 프로그래밍 언어)로 제어할 수 있다. 에디터 인스턴스(instance, 실행 중인 특정 객체)에 setValue()라는 메서드(method, 객체가 수행할 수 있는 기능)를 호출하면, 타이핑 없이 코드를 통째로 집어넣을 수 있다. 자동완성이 개입할 여지가 없다. 한 번에 전체 내용이 들어가니까.

    브라우저 콘솔(console, 개발자 도구에서 자바스크립트를 직접 실행할 수 있는 창)에서 아래처럼 실행했다.

    document.querySelector('.CodeMirror').CodeMirror.setValue(`여기에 PHP 코드 전체`)

    코드가 깨끗하게 들어갔다. 자동완성 간섭 없이. 저장하고 활성화한 뒤 curl(curl, 터미널에서 HTTP 요청을 보내는 명령어 도구)로 확인했더니 세 헤더가 전부 응답에 포함돼 있었다. HSTS, X-Content-Type-Options, Referrer-Policy. 다 떴다.

    WPCode로 보안 헤더·llms.txt 추가하다 겪은 삽질기 스크린샷 2

    curl 응답에서 세 보안 헤더가 실제로 찍힌 결과다. 헤더가 응답에 없으면 아무리 코드를 넣어도 동작하지 않은 것이니, 이 확인 단계는 건너뛰면 안 된다.

    보안 헤더는 성공했는데 llms.txt는 404가 뜬 이유

    같은 방식으로 llms.txt도 만들었다. WPCode에 스니펫을 하나 더 추가해서, 특정 URL로 접근하면 llms.txt 내용을 텍스트로 응답하도록 했다. setValue()로 코드를 넣었으니 입력 과정은 문제없었다.

    그런데 curl로 확인하니 이상했다. 본문 내용은 정확하게 나왔다. llms.txt에 넣으려던 텍스트가 그대로 출력됐다. 그런데 HTTP 상태 코드(HTTP Status Code, 서버가 요청에 대해 응답할 때 같이 보내는 숫자 코드)가 404였다.

    404는 “없는 페이지”라는 뜻이다. 내용은 있는데 없는 페이지라고 응답하는 이상한 상태였다.

    상태 코드를 명시적으로 지정해야 했던 워드프레스의 특성

    워드프레스는 URL을 처리하는 방식이 독특하다. 등록된 페이지나 포스트가 아닌 URL로 요청이 들어오면, 워드프레스는 그 URL을 404로 먼저 처리해버린다. llms.txt는 워드프레스에 등록된 페이지가 아니다. 그러니까 워드프레스 입장에서는 없는 URL이다.

    PHP 코드로 본문 내용을 echo(echo, PHP에서 텍스트를 출력하는 명령어)해도, 워드프레스가 이미 404로 결정한 상태 코드는 바뀌지 않는다. 내용은 출력되지만 상태 코드는 404 그대로 남는 거다. 검색엔진이나 AI 크롤러는 상태 코드를 보고 판단한다. 404면 없는 페이지다. 내용이 아무리 정확해도 무시된다.

    해결 방법은 status_header(200)을 코드에 추가하는 거였다. status_header()는 워드프레스에서 HTTP 상태 코드를 강제로 지정하는 함수(function, 특정 작업을 수행하는 코드 묶음)다. 이걸 echo 앞에 넣어서 200으로 바꿔줬다.

    그래도 처음엔 안 됐다. 라이트스피드 캐시(LiteSpeed Cache, 웹사이트 속도를 높이기 위해 페이지를 미리 저장해두는 플러그인)가 이전 404 응답을 저장해두고 있었다. 캐시 퍼지(purge, 저장된 캐시를 강제로 삭제하는 작업)를 하고 나서야 curl에서 200이 확인됐다.

    WPCode로 보안 헤더·llms.txt 추가하다 겪은 삽질기 스크린샷 3

    캐시 퍼지 후 curl로 다시 확인한 결과다. 상태 코드가 200으로 바뀐 걸 확인할 수 있다. 이 단계 없이는 제대로 됐는지 알 수 없다.

    코드 에디터 자동화와 HTTP 헤더를 다루면서 배운 두 가지

    이번 작업에서 정리된 게 두 가지다.

    첫 번째. 자동화 도구로 코드 에디터를 다룰 땐 타이핑을 시키면 안 된다. 자동완성, 자동 들여쓰기, 단축키 처리 같은 에디터 기능이 전부 방해 요소가 된다. 에디터 인스턴스에 직접 값을 주입하는 방식을 써야 한다. CodeMirror라면 setValue()가 그 방법이다. 타이핑은 사람이 할 때만 자연스럽다.

    두 번째. 워드프레스에서 커스텀 URL(custom URL, 워드프레스에 등록되지 않은 주소)을 서빙할 땐 본문 내용만 맞춰선 끝이 아니다. HTTP 상태 코드를 직접 200으로 지정해야 한다. 안 하면 워드프레스가 404를 내보낸다. 크롤러는 그 페이지를 없는 페이지로 처리한다. llms.txt를 만들어도 아무도 읽지 않는 상황이 된다.

    다음엔 llms.txt 내용을 실제로 AI 크롤러가 제대로 읽어가는지 확인해볼 생각이다. 200이 뜨는 건 확인했는데, 실제로 색인(index, 검색엔진이나 AI가 페이지 내용을 자신의 데이터베이스에 등록하는 것)이 되는지는 별개 문제다. 아직 모른다.