요약
ChatGPT로 원고를 만들고 Codex로 WordPress REST API 업로드 코드를 작성할 수 있지만, 곧바로 게시하는 단일 스크립트는 안전한 자동화가 아닙니다. 입력 계약과 사전 검증, 미디어 업로드, 초안 생성, 인증된 조회와 시각 검토, 사람 승인, 별도 게시 전환을 상태로 나누고 충돌·부분 실패·복구 기록을 설계해야 합니다.
최신성: 2026년 7월 19일 OpenAI와 WordPress 공식 문서를 기준으로 다시 검증했습니다. WordPress 권한·플러그인·서버 설정과 Codex 승인 정책은 환경마다 다르므로 운영 적용 전 대상 사이트의 현재 REST 스키마와 조직 정책을 다시 확인하세요.
- 자동화 범위와 위협 모델
- 입력 계약과 변경 전 조건
- WordPress 인증·미디어·글 모델
- 안전한 상태 전이
- 안전 기본값 코드
- 중복·충돌 방지
- 비밀정보와 권한
- 승인·부분 실패·복구
- 게시 전 검증
- 내부 학습 경로
- 공식 출처와 결론
자동화 범위와 위협 모델

역할을 세 층으로 나누면 사고 범위가 작아집니다. ChatGPT 출력은 검토되지 않은 콘텐츠 입력, Codex가 만드는 코드는 저장소 안의 변경과 테스트 대상, WordPress는 외부 상태를 바꾸는 게시 시스템입니다. 한 층의 성공을 다음 층의 승인으로 간주하지 않습니다.
| 층 | 허용할 결과 | 금지할 지름길 |
|---|---|---|
| 콘텐츠 생성 | 제목·본문·메타·이미지 설명의 후보 | 사실 확인 없이 최종 원고로 간주 |
| 저장소 작업 | 스키마, 검증기, 테스트, dry-run 결과 | 비밀정보 삽입, 승인 없는 외부 POST |
| WordPress 쓰기 | 기록 가능한 미디어와 초안 ID | 검토 없이 publish, 실패 시 자동 삭제 |
| 게시 승인 | 승인자·시각·대상 해시가 있는 별도 전환 | 초안 생성 성공을 게시 승인으로 재사용 |
위협 모델에는 잘못된 원고, 프롬프트 인젝션, 자격증명 노출, 잘못된 분류 ID, 이미지 일부만 업로드된 상태, 네트워크 타임아웃 뒤 중복 생성, 다른 편집자의 동시 수정, 게시 후 캐시·검색·구독 알림까지 포함합니다.
입력 계약과 변경 전 조건
내부 작업 매니페스트와 WordPress Posts API 요청 본문을 분리하세요. 내부 매니페스트는 승인과 충돌 검사용 필드를 포함할 수 있지만 WordPress에 그대로 보내는 스키마가 아닙니다.
{
"operation": "update",
"postId": 6802,
"slug": "chatgpt-codex-wordpress-auto-upload-pipeline",
"expectedModified": "2026-07-19T09:00:00",
"beforeSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"desiredStatus": "draft",
"title": "검토할 제목",
"html": "<div>검토할 본문</div>",
"categories": [12],
"tags": [34],
"featuredMediaId": 456,
"requestKey": "2026-07-19-post-6802-rewrite-01"
}
expectedModified와 beforeSha256는 업데이트에 필수입니다. 실행 직전 인증된 GET의 modified와 content.raw를 BOM 없는 UTF-8로 해시한 값이 둘 중 하나라도 다르면 중단합니다. 생성 작업은 두 필드를 null로 두고, 별도의 slug·requestKey 중복 점검을 수행합니다. 예시 해시는 형식 설명용이므로 실제 조회값으로 교체해야 합니다.
- 제목과 HTML이 비어 있지 않고 길이·허용 태그 정책을 통과하는가?
- 본문 H1이 0개이며 ID가 중복되지 않고 내부 조각 링크가 실제 ID를 가리키는가?
- 모든
pre에lang이 있고 자식code의language-*와 일치하는가? - 카테고리·태그·대표 이미지가 이름이 아니라 대상 사이트의 정수 ID로 검증됐는가?
- 이미지 원본과
srcset파생본이 존재하고 실제 픽셀과 설명자가 일치하는가?
WordPress 인증·미디어·글 모델
인증: Application Password를 HTTPS에서 사용한다
WordPress의 REST API 인증 문서는 원격 요청에서 Application Password를 HTTPS의 Basic Auth로 사용하는 방식을 안내합니다. 일반 계정 비밀번호를 넣지 말고 전용 사용자의 Application Password를 비밀 저장소에서 실행 시점에 주입합니다. 인증이 됐어도 현재 사용자가 해당 글·미디어를 만들거나 수정할 capability가 없으면 요청은 실패합니다.
미디어: 먼저 업로드하고 ID와 대체 텍스트를 검증한다
Media API의 생성 엔드포인트는 POST /wp/v2/media입니다. 응답의 읽기 전용 id와 source_url을 기록하고, 편집 가능한 alt_text를 설정한 뒤 다시 조회합니다. 글의 featured_media에는 URL이 아니라 검증된 정수 ID를 보냅니다.
글: 초안 생성과 게시 전환을 분리한다
Posts API는 POST /wp/v2/posts로 글을 만들고, POST /wp/v2/posts/<id>로 업데이트합니다. status에는 draft, pending, private, future, publish 같은 값이 있으며 categories와 tags는 정수 배열, featured_media는 정수입니다. 첫 쓰기는 항상 draft로 고정하고 publish는 다른 승인 이벤트에서만 사용합니다.
안전한 상태 전이
GENERATED
→ VALIDATED
→ MEDIA_UPLOADED
→ DRAFT_CREATED
→ API_VERIFIED
→ HUMAN_APPROVED
→ PUBLISHED
어느 단계든 실패
→ STOPPED + 생성된 postId/mediaId + 오류 코드 + 재개 조건 기록
1. 생성과 검증
ChatGPT 원고는 입력 후보일 뿐입니다. 사실, 인용, 개인정보, 금지어, HTML 구조와 링크를 검토한 뒤 스키마 검증을 통과시킵니다. Codex에는 Goal·Context·Constraints·Done when과 외부 쓰기 금지를 명시하고 검증기와 테스트부터 만들게 합니다. OpenAI의 Codex 모범 사례도 복잡한 작업의 계획, 저장소 지침, 테스트·린트·타입 검사와 diff 검토를 권장합니다.
2. 미디어와 초안
미디어를 하나씩 올리고 ID·URL·대체 텍스트를 확인합니다. 모두 준비된 뒤 status가 draft인 글을 생성하고 postId, modified, slug와 연결된 미디어 ID를 실행 로그에 남깁니다. 타임아웃이 발생하면 재시도 전에 서버에서 기존 결과를 조회합니다.
3. API·시각 검증과 사람 승인
인증된 GET으로 title.raw, content.raw, content.rendered, status, categories, tags와 featured_media를 확인하고 WordPress 관리자 미리보기에서 모바일·데스크톱 레이아웃, 접근성, 링크와 이미지를 검토합니다. 승인 기록에는 대상 postId, 검증한 콘텐츠 해시, 승인자와 시각을 넣습니다.
4. 별도 게시 전환
게시 작업은 앞 단계의 로그를 입력으로 받는 별도 명령이어야 합니다. 실행 직전 modified와 해시를 다시 비교하고 승인 대상과 같을 때만 status를 publish로 전환합니다. 게시 후 GET과 공개 URL을 확인하고 캐시·검색·구독 알림 같은 외부 효과를 모니터링합니다.
안전 기본값 코드
구성과 초안 페이로드
다음 예시는 자격증명을 미리 Base64로 저장하지 않고 실행 시점에 헤더를 만들며, HTTPS와 dry-run을 강제하고 초안만 생성하는 최소 경계입니다. 미디어 업로드·충돌 검사·게시 전환은 의도적으로 별도 단계로 남겨 둡니다.
const { WP_BASE_URL, WP_USERNAME, WP_APP_PASSWORD } = process.env;
function requireConfig() {
const base = new URL(WP_BASE_URL);
if (base.protocol !== 'https:') throw new Error('HTTPS is required');
if (!WP_USERNAME || !WP_APP_PASSWORD) throw new Error('Missing WordPress credentials');
return base;
}
function buildDraft(input) {
if (!input.title?.trim() || !input.html?.trim()) throw new Error('Invalid content');
if (!Array.isArray(input.categories) || !Array.isArray(input.tags)) {
throw new Error('Invalid taxonomy IDs');
}
return {
title: input.title, content: input.html, slug: input.slug, status: 'draft',
categories: input.categories, tags: input.tags,
featured_media: input.featuredMediaId || 0
};
}
HTTPS 요청과 dry-run
async function wpJson(path, options = {}) {
const base = requireConfig();
const token = Buffer.from(`${WP_USERNAME}:${WP_APP_PASSWORD}`, 'utf8').toString('base64');
const response = await fetch(new URL(path, base), {
...options,
redirect: 'error',
headers: { Authorization: `Basic ${token}`, 'Content-Type': 'application/json' }
});
const text = await response.text();
if (!response.ok) {
let code = 'unknown';
try { code = String(JSON.parse(text).code || code).slice(0, 80); } catch {}
throw new Error(`WordPress request failed: ${response.status} ${code}`);
}
return text ? JSON.parse(text) : null;
}
async function createDraft(input) {
const payload = buildDraft(input);
if (process.env.WP_APPLY !== 'YES') {
return { dryRun: true, payload: { ...payload, content: '[omitted]' } };
}
const post = await wpJson('/wp-json/wp/v2/posts', {
method: 'POST', body: JSON.stringify(payload)
});
return { id: post.id, status: post.status, modified: post.modified };
}
오류 응답 전체, Authorization 헤더와 환경 변수는 로그로 남기지 않습니다. WP_APPLY=YES는 초안 생성을 허용할 뿐 게시 승인이 아닙니다. 실제 구현에서는 요청 제한 시간, 취소, 구조화된 감사 로그, 테스트용 WordPress 환경과 네트워크 허용 목록을 추가합니다.
중복·충돌 방지
업데이트: 두 개의 변경 전 조건을 비교한다
- 실행 직전
GET /wp/v2/posts/<id>?context=edit로 편집 컨텍스트를 조회합니다. - 응답의 modified가 expectedModified와 같은지 확인합니다.
- content.raw의 정확한 UTF-8 바이트 SHA-256이 beforeSha256과 같은지 확인합니다.
- 하나라도 다르면 다른 편집이 있었다고 보고 쓰기를 중단하고 새 스냅샷으로 다시 검토합니다.
이 비교는 클라이언트 측 안전장치이며 Posts API의 원자적 compare-and-set이 아닙니다. 확인과 업데이트 사이에 다른 수정이 들어오는 경쟁 구간은 남습니다. 동시 편집 위험이 큰 사이트라면 서버 측 잠금이나 조건부 업데이트를 제공하는 별도 구현을 검토하고, 그 동작을 독립적으로 시험해야 합니다.
생성: 재시도 전에 결과를 조정한다
requestKey는 로컬 실행 로그의 추적 키이며 WordPress 코어가 중복 방지를 보장한다는 뜻이 아닙니다. slug, 실행 키, 생성 시각과 응답 postId를 함께 기록합니다. 타임아웃 뒤에는 같은 slug의 초안과 직전 응답을 조회해 생성 여부를 판별하고, 불명확하면 사람이 선택할 때까지 다시 POST하지 않습니다.
비밀정보와 권한
OpenAI의 에이전트 승인과 보안 문서는 로컬 Codex의 샌드박스와 승인 정책, 제한된 네트워크 기본값을 구분합니다. WordPress 쓰기는 외부 시스템 변경이므로 계획·dry-run·초안 적용·게시를 각각 명시적으로 허용하고, 에이전트가 웹 콘텐츠의 지시를 그대로 실행하지 않게 합니다.
- 자격증명: Application Password는 비밀 저장소에 두고 프롬프트, 소스, JSON, 명령 인수와 오류 로그에서 제외합니다. 유출되면 즉시 해당 비밀번호를 폐기하고 감사 로그를 확인합니다.
- 최소 권한: 자동화 전용 사용자를 만들고 필요한 글·미디어 capability만 부여합니다. 관리자 계정과 일반 비밀번호를 사용하지 않습니다.
- 네트워크: 대상 WordPress HTTPS 호스트만 허용하고 리디렉션의 최종 호스트, TLS와 프록시 정책을 확인합니다.
- 콘텐츠: 스크립트, 이벤트 속성, 추적 픽셀, 외부 iframe과 의도하지 않은 링크를 허용 목록 정책으로 검사합니다. WordPress의 필터링만 마지막 방어선으로 두지 않습니다.
- 로그: requestKey, 단계, 대상 ID, 상태 코드, 해시와 시각만 남기고 본문·인증 헤더·전체 서버 오류는 기록하지 않습니다.
승인·부분 실패·복구

생성 작업의 부분 실패
미디어 일부만 성공하거나 초안 생성 뒤 검증이 실패해도 자동 DELETE로 정리하지 않습니다. 성공한 mediaId와 postId, 단계와 오류 코드를 남기고 초안 상태를 유지합니다. 재사용, 연결 해제, 휴지통 이동 또는 삭제는 영향과 권한을 확인한 사람이 별도로 승인합니다.
업데이트 작업의 복구
쓰기 전에 title.raw, content.raw, status, categories, tags, featured_media와 modified를 정확한 before 스냅샷으로 보관합니다. 문제가 생기면 자동으로 덮어쓰지 말고 현재 상태가 예상 실패 상태인지 확인한 뒤, 사람이 승인한 경우에만 이전 필드를 다시 POST하고 GET으로 복구 결과를 검증합니다.
Post Revisions API로 글의 리비전 목록과 특정 리비전을 조회할 수 있지만, 리비전이 모든 외부 효과를 원자적으로 되돌린다고 가정하면 안 됩니다. 게시 후 캐시, 검색 색인, 웹훅, 이메일과 구독 알림은 콘텐츠 복원과 별개로 대응해야 합니다.
게시 승인 기록
승인에는 postId, 검토한 title·content 해시, status, featured_media, 승인자, 승인 시각과 만료 시간을 포함합니다. 게시 직전 값이 하나라도 달라지거나 승인이 만료되면 초안 검토 단계로 돌아갑니다. 승인 없는 publish 요청은 코드 경로 자체에서 거부해야 합니다.
게시 전 검증
| 단계 | 통과 기준 | 남길 증거 |
|---|---|---|
| 로컬 구조 | JSON 파싱, BOM 없음, postId·필수 필드·해시 형식 통과 | 검증 명령과 결과 |
| HTML | H1 0, 중복 ID 0, 조각 링크 누락 0, pre 언어 일치 | 파서 감사 결과 |
| 링크·이미지 | HTTP 응답 정상, 실제 픽셀·width·height·srcset 설명자 일치 | URL·상태·픽셀 표 |
| WordPress API | 초안 ID·status·modified·slug·분류·대표 이미지와 raw/rendered 일치 | 민감정보를 지운 응답 요약 |
| 시각·접근성 | 관리자 미리보기에서 모바일·데스크톱, 키보드, 포커스, alt 확인 | 검토자와 체크리스트 |
| 실패 주입 | 401·403·타임아웃·미디어 실패·동시 수정에서 publish·delete 없음 | 중단 상태와 생성 ID |
| 게시 | 승인 해시 재일치, 공개 GET·캐시·모니터링 확인 | 승인 기록과 게시 후 점검 |
dry-run은 요청 본문을 검증하는 단계이지 WordPress 권한과 서버 필터를 증명하지 않습니다. 테스트 사이트에서 초안 생성과 실패 경로를 확인한 뒤 운영 사이트에서도 처음에는 한 건씩 적용하고, 관찰 가능한 로그와 중단 스위치를 유지합니다.
내부 학습 경로
- Cursor·Claude Code·Codex 바이브 코딩 기준 — 에이전트 권한·검증·롤백의 기본 경계를 먼저 익힙니다.
- 생성형 AI 보안 체크리스트 — 원고와 자격증명, 외부 도구의 데이터 경계를 점검합니다.
- 노코드와 코드 자동화 선택 기준 — 운영 책임과 예외 처리를 비교합니다.
- n8n·Make 자동화 준비 — 상태·재시도·관찰 가능성 설계를 다른 자동화 사례로 확장합니다.
내부 글은 학습 경로이며 OpenAI 제품 사실과 WordPress API 계약의 근거는 아래 공식 문서를 우선합니다.
공식 출처와 결론
- OpenAI Codex 모범 사례
- OpenAI 에이전트 승인과 보안
- WordPress REST API 인증
- WordPress Media API
- WordPress Posts API
- WordPress Post Revisions API
판단 1: ChatGPT 원고, Codex 코드와 WordPress 외부 쓰기를 같은 신뢰 단계로 취급하지 않습니다.
판단 2: 미디어와 글은 초안으로 만들고 인증된 GET·시각 검토·사람 승인을 통과한 뒤 별도 명령에서만 게시합니다.
판단 3: expectedModified와 beforeSha256은 유용한 중단 조건이지만 원자적 잠금이 아닙니다. 중복·경쟁·부분 실패를 기록하고 자동 삭제 없이 사람이 복구를 승인하게 합니다.
다음 행동 체크리스트: 테스트 WordPress 사용자와 Application Password를 준비하고 대상 호스트만 허용하세요. 내부 매니페스트 스키마와 dry-run 검증기를 먼저 통과시킨 뒤 이미지 한 개와 status=draft인 글 한 건을 만들고, 인증된 GET·관리자 미리보기·실패 주입·복구 기록을 검토하세요. publish 경로는 그 증거와 승인 형식이 완성된 뒤 별도로 구현합니다.
변경 기록: 2026년 7월 19일, 일반 계정 비밀번호와 즉시 게시 중심 예시를 제거하고 Application Password·HTTPS·최소 권한, 입력 계약, 초안 상태 전이, 변경 전 해시, 중복·동시성, 승인·부분 실패·비파괴 복구와 검증 절차를 보강했습니다.