티스토리에 이미지를 자동으로 올리고 본문에 넣는 작업을 하다가, 업로드 직후 에디터에 삽입된 이미지 주소를 그대로 HTML에 박아 넣으면 몇 주 뒤에 깨질 수 있다는 것을 알았다. 주소에 만료 시각과 서명이 붙어 있었기 때문이다. 이 글은 그 주소의 정체와, 주소 대신 티스토리가 만든 마크업을 통째로 옮기는 방식으로 우회한 과정을 다룬다.
업로드된 이미지 주소에는 만료 시각과 서명이 붙어 있다
기본모드 에디터에서 사진 첨부로 PNG 하나를 올리자 편집 영역 iframe 안에 이런 마크업이 생겼다.
src의 쿼리스트링을 보면 credential, expires, signature가 있다. expires=1790780399는 유닉스 시각으로 2026년 9월 말이다. 업로드한 날로부터 약 3주 뒤다. 클라우드 스토리지에서 흔히 쓰는 서명된 URL, 즉 특정 기간 동안만 접근을 허용하는 임시 주소다. 편집 중인 글에서 미리보기를 보여주기 위한 것이지, 발행된 글이 영구히 참조할 주소가 아니다.
내 첫 설계는 이 src만 뽑아서, HTML 모드에 넣을 본문의 로컬 이미지 경로와 치환하는 것이었다. 그랬다면 발행 뒤 3주 동안은 멀쩡하다가 어느 날 이미지가 전부 깨졌을 것이다. 발행 시점에는 검증이 통과하고 만료 시점에는 아무도 지켜보지 않는, 가장 나쁜 종류의 지연 폭탄이다.
티스토리는 저장할 때 자기 규칙으로 주소를 바꾼다
발행된 글의 이미지가 어떻게 되는지 확인하려고, 업로드 직후의 글을 임시저장하고 마크다운 모드로 다시 열어봤다. 그러자 이미지가 이런 형태로 바뀌어 있었다.
는 티스토리의 치환자다. 저장 시점에 에디터가 삽입한 마크업을 서버가 자기 규칙으로 바꿔 보관하고, 글을 렌더링할 때 그때그때 유효한 주소로 풀어낸다. 즉 티스토리는 서명 URL을 저장하지 않는다. data-ke-type="image"가 붙은 <figure> 마크업을 보고 어떤 이미지인지 인식한 뒤 자기 식으로 변환한다.
그렇다면 답은 분명하다. 주소를 뽑아 쓰지 말고, 티스토리가 만든 <figure> 요소를 통째로 가져다 쓰면 된다. 서버는 그 마크업을 알아보고 알아서 영구 참조로 바꿔준다.
figure 마크업을 통째로 옮기면 서버가 알아서 처리한다
최종 흐름은 이렇다. HTML 모드에는 업로드 UI가 없으므로 기본모드에서 먼저 이미지를 올린다. 업로드마다 iframe 안의 <figure> 개수가 늘어나는 것을 기다렸다가 마지막 <figure>의 outerHTML을 회수한다. 모든 이미지를 올린 뒤 편집 영역을 비우고 HTML 모드로 전환한 다음, 본문의 로컬 <img>를 회수한 마크업으로 바꿔 넣는다.
def upload_image(page, path):
before = figure_count(page)
page.click("div[aria-label='첨부'] button")
with page.expect_file_chooser() as chooser:
page.click("#attach-image")
chooser.value.set_files(str(path))
deadline = time.monotonic() + 90
while time.monotonic() < deadline:
page.wait_for_timeout(1000)
if figure_count(page) > before:
return last_figure_html(page) # data-mce-selected 속성은 떼고 반환
raise RuntimeError(f"업로드가 끝나지 않았습니다: {path.name}")
회수한 마크업에서 data-mce-selected 속성은 떼어냈다. 에디터가 현재 선택된 요소에 붙이는 편집용 표시라 본문에 남길 이유가 없다. 이렇게 발행한 글을 다시 열자 <figure> 2개(대표 썸네일, 본문 도해)가 모두 살아 있었고, src가 http로 시작하지 않는 로컬 경로는 하나도 남지 않았다.
그래도 만료 주소가 남았는지 발행 직전에 검사한다
이 방식에도 한계는 있다. 티스토리가 <figure> 마크업의 규칙을 바꾸면 서버가 이미지를 인식하지 못할 수 있다. 그 경우 서명 URL이 그대로 저장되고, 3주 뒤에 조용히 깨진다. 지금 동작한다는 사실이 앞으로도 동작한다는 보장은 아니다.
그래서 두 겹의 방어를 두었다. 하나는 발행 직전 게이트에서 본문에 로컬 경로 이미지가 남아 있으면 발행 자체를 막는 규칙이다. 업로드가 누락된 채 나가는 것을 막는다. 다른 하나는 발행 후 저장된 글을 다시 열어 <img src>가 전부 http로 시작하는지, <figure> 개수가 올린 수와 같은지 세는 검증이다.
"주소를 저장하지 말고 서비스가 알아보는 마크업을 저장하라." 티스토리뿐 아니라 서명 URL을 쓰는 어떤 서비스에서든 통하는 원칙이다. 만료 시각이 박힌 주소는 그 시각이 지나면 반드시 깨진다. 발행 시점에 멀쩡해 보이는 것이 가장 위험한 이유다.