티스토리 뷰

카테고리 없음

무인 발행, 승인서에 숫자 하나 없었을 뿐인데..그 글은 못 고쳤다?(이중 해시)

ststst1255 2026. 9. 3. 04:14

목차


    결론부터 말씀드리면, 이 파이프라인이 발행 전에 검사를 세 번이나 겹쳐 두는 이유는 단순히 꼼꼼해서가 아닙니다. 검사 하나가 빠졌던 어느 날, 글 한 편이 영영 고칠 수 없는 상태로 남았기 때문입니다.
    그 하루의 기록이 지금 이 세 겹의 구조를 만들었습니다.

    무인 발행 3중 안전장치_1

    발행 전에 이 세 가지를 통과해야 합니다

    이 블로그는 사람이 매번 화면을 지켜보며 발행 버튼을 누르지 않습니다. 대신 발행 직전에 코드가 세 단계를 순서대로 통과시킵니다.

    첫 번째는 preflight.py의 기계검사입니다. 원고 HTML을 열어 민감정보가 새지 않았는지부터 봅니다.
    텔레그램 봇 토큰처럼 생긴 문자열, Anthropic API 키처럼 생긴 문자열, 구글 API 키처럼 생긴 문자열, 애드센스 pub ID, 이메일 주소, IP 주소 형태, 윈도우 계정 경로 형태 — 이런 형태의 패턴을 정규식으로 찾습니다. 실제 값이 아니라 "이런 모양이면 의심"이라는 규칙만 코드에 있습니다.

    같은 검사가 쿠팡 링크 규칙도 봅니다. 고지 문구가 본문에 있는지, 링크 개수가 상한을 넘지 않는지, rel="nofollow sponsored"가 붙어 있는지까지 확인합니다.
    이미지 자리표시자가 남아 있으면 막고, 내부링크가 2개 미만이면 그것도 막습니다.

    두 번째는 승인서입니다. write_approval.py가 검수를 통과한 원고에 대해 .review.json 파일을 하나 발급합니다.
    이 파일 안에는 원고 전체를 해시한 값(html_sha256)과, 쿠팡 링크가 들어갈 자리를 뺀 본문만 따로 해시한 값(content_sha256)이 함께 들어갑니다.

    세 번째는 발행 이후의 확인입니다. publish_draft.py가 발행 버튼을 누른 뒤에도 일을 끝내지 않습니다.
    공개 설정을 직접 클릭해 놓고, RSS 피드에서 방금 쓴 제목을 찾아 실제 주소를 확인하고, 로그인 쿠키 없이 그 주소에 GET 요청을 보내 진짜로 200이 뜨는지까지 봅니다.

    단계 무엇을 잡는가 담당 코드
    ① 발행 전 기계검사 민감정보 패턴, 쿠팡 링크 규칙, 이미지 자리표시자, 내부링크 개수 preflight.check_html
    ② 승인서 이중 해시 사람 검수를 거쳤는지, 승인 이후 본문이 몰래 바뀌지 않았는지 write_approval.py / preflight.check_approval
    ③ 발행 후 공개 검증 발행 버튼을 눌러도 실제로 남들에게 열리는지 publish_draft.verify_public

    승인서에 숫자 하나가 없었던 날

    2026년 7월 29일 전까지는 순서가 이랬습니다. 쿠팡 카드를 먼저 원고에 끼워 넣고, 그다음 검수·승인하고, 마지막에 발행했습니다.
    이 순서라면 승인서에 적힌 원고 해시와 발행되는 원고가 완전히 같은 바이트라 아무 문제가 없었습니다.

    그날 무인 발행으로 전환하면서 순서가 뒤집혔습니다. 승인을 먼저 받고, 발행하고, 쿠팡 카드는 그 뒤에 채워 넣는 방식으로요.
    문제는 승인서에 원고 전체를 통째로 해시한 값 하나밖에 없었다는 점이었습니다.

    카드가 들어가는 순간 원고 바이트는 반드시 달라집니다. 그러면 그 통짜 해시도 반드시 깨집니다.
    검사 코드는 이걸 "승인 뒤에 누가 몰래 본문을 고쳤다"는 신호로 읽었습니다. 실제로는 예정된 절차(카드 삽입)였을 뿐인데도요.

    무인 발행 3중 안전장치_2

    그 결과 그날 발행된 글들은 쿠팡 카드를 아무리 다시 넣어줘도 반영되지 않는 상태로 남았습니다. 발행 버튼도, 반영 버튼도 눌렀지만 검사 코드 입장에서는 매번 "변조 의심"이었기 때문입니다.
    숫자 하나가 없었을 뿐인데, 그 글은 스스로 고쳐지지 않는 상태가 된 겁니다.

    왜 검사 하나로는 부족했나

    이 사고를 겪고 나서 만든 게 content_sha256입니다. 쿠팡 링크 자리(자리주석이든, 실제로 채워진 카드든)를 지우고 공백을 정리한 뒤 본문만 따로 해시합니다.
    승인 이후 이 값이 그대로면 "쿠팡 자리만 바뀐 예정된 변화"로 보고 통과시킵니다. 문장이 한 글자라도 바뀌면 이 값도 깨지니, 진짜 변조를 놓치는 것도 아닙니다.

    여기서 알게 된 건, 검사 하나가 다른 검사의 실수를 대신 잡아주지 않는다는 사실이었습니다.
    기계검사(①)는 발행 전 원고 자체의 문제를 봅니다. 승인서(②)는 "사람이 실제로 검수를 거쳤는가"라는 절차의 문제를 봅니다. 공개 검증(③)은 "버튼을 눌러도 실제로 실행됐는가"라는 실행의 문제를 봅니다.

    세 검사는 서로 다른 순간에, 서로 다른 실패를 잡습니다. 하나를 빼면 그 순간의 실패는 아무도 보지 못한 채 지나갑니다.
    승인서만 있고 이중 해시가 없었던 그날처럼요.

    이런 '조용한 실패'를 겪은 게 이번이 처음은 아니었습니다. 검수 서브에이전트를 백그라운드로 띄웠다가 결과 자체가 통째로 사라졌던 검수 서브에이전트 백그라운드 사고 글도, 크론이 겹친 포트를 문 채 아무 신호 없이 멈췄던 포트 충돌 자동화 사고 글도 같은 계열입니다. 에러 로그가 뜨지 않는다고 해서 아무 일도 없었던 게 아니라는 교훈이 이번에도 반복됐습니다.

    지금도 이 세 검사가 이 글을 통과하고 있습니다

    이 글도 발행 전 같은 3단계를 거칩니다

    기계검사가 민감정보와 링크 규칙을 먼저 보고, 검수자가 승인서를 발급하며 이중 해시를 남기고, 발행 뒤에는 로그인 없이 열어봐도 진짜 공개인지 확인합니다. 세 단계 중 하나라도 생략되면, 2026년 7월 29일에 벌어졌던 일이 형태만 바꿔 다시 일어날 수 있습니다.

    읽으시는 분이 직접 자동화 파이프라인을 만들고 계신다면, 순서를 이렇게 확인해보시길 권합니다.

    1. 발행 직전에 원고 자체를 기계로 검사하는 단계가 있는지 봅니다. 사람이 눈으로 보기 전에 걸러야 할 것들(민감정보, 필수 표기)은 사람보다 기계가 먼저 잡는 편이 안전합니다.
    2. "검수를 통과했다"는 증거가 파일로 남는지 봅니다. 기억이나 대화 로그가 아니라, 나중에도 다시 열어 대조할 수 있는 파일이어야 합니다.
    3. 원고가 발행 뒤에 다시 손이 갈 여지가 있다면, 그 변화를 "허용된 변화"와 "의심스러운 변조"로 구분하는 장치가 있는지 봅니다. 통째로 하나의 값만 본다면 예정된 변화조차 매번 막힐 수 있습니다.
    4. 버튼을 눌렀다는 사실과 실제로 실행됐다는 사실을 같은 것으로 보지 않습니다. 로그인한 화면에서 정상으로 보이는 것과, 남이 봐도 정상인 것은 다를 수 있습니다.

    FAQ

    Q1. 검사를 세 개나 겹치면 오히려 번거롭지 않나요?
    번거로움은 있습니다. 다만 세 검사가 서로 다른 실패를 잡기 때문에, 하나를 줄이면 그 자리의 실패는 아무도 못 보게 됩니다. 이 파이프라인은 그 대가를 한 번 실제로 치렀습니다.

    Q2. 승인서의 해시가 두 개(html_sha256·content_sha256)인 이유를 한 줄로 설명하면요?
    발행 뒤에 예정대로 바뀌는 부분(쿠팡 카드 자리)과, 예정에 없이 바뀐 부분(본문 변조)을 구분하기 위해서입니다. 하나의 해시로는 이 둘을 가를 수 없었습니다.

    Q3. 발행 버튼을 눌렀는데 왜 또 확인이 필요한가요?
    버튼을 누른 화면은 로그인한 사람 눈에만 보이는 화면일 수 있습니다. 실제로 남들에게 열리는지는 로그인하지 않은 상태로 다시 확인해야 압니다.

    Q4. 이런 사고가 다시 나면 어떻게 알아채나요?
    승인서 파일이 있는지, 그 안의 해시가 지금 원고와 맞는지를 발행 도구가 매번 자동으로 대조합니다. 사람이 매번 기억해서 확인하지 않아도 되게 만든 것이 이번 수리의 핵심이었습니다.