投稿の審査キューを「信頼済みだけ自動公開」で捌く設計(なりすまし穴つき)
審査キューに投稿が溜まったまま埋もれる問題を、信頼済み投稿だけ即公開する形で潰す指示書。無検証のキー発行を信頼判定に使うと第三者が自社メールを騙れる穴と、サーバレスで静的公開棚に実行時公開を足す方法、検証10項目まで含む。
約35.5万トークンの節約 (API料金換算で約530円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「投稿の審査キューを「信頼済みだけ自動公開」で捌く設計(なりすまし穴つき)」は、Web開発カテゴリのAI指示書(MDファイル)です。審査キューに投稿が溜まったまま埋もれる問題を、信頼済み投稿だけ即公開する形で潰す指示書。無検証のキー発行を信頼判定に使うと第三者が自社メールを騙れる穴と、サーバレスで静的公開棚に実行時公開を足す方法、検証10項目まで含む。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約35.5万トークン(API料金換算で約530円)・89%のトークンを節約できます。
- カテゴリ
- Web開発
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約40万トークン
- この巻物使用時
- 約4.5万トークン
- 節約量
- 約35.5万トークン (約530円)
- 更新日
- 2026-08-28
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/md-4706726f/raw を読み込んで、この指示書どおりに実装して"
中身
投稿の審査キューを「信頼済みだけ自動公開」で捌く設計
投稿を受け付けるサービス(マーケット・投稿板・社内ナレッジ共有など)で、審査キューに溜まったまま誰も処理せず、投稿が全部埋もれる問題を潰すための指示書。 「自分たちの投稿だけ人手の審査を省いて即公開し、第三者は今までどおりキューに残す」を、穴を開けずに実装する。
この指示書は AI コーディングエージェントにそのまま読ませて使う。
前提と適用範囲
- 投稿API(例:
POST /api/v1/listings)があり、受理した投稿はpendingに入る - 公開コンテンツは静的ファイル(
content/*.md等)としてリポジトリに置き、デプロイ時にバンドルされる - ホスティングはサーバレス(Vercel / Cloudflare Workers / Lambda 等)で、実行時のファイル書き込みは永続しない
- 投稿本文に対する秘密情報スキャンが既にある
この3つ目が効いてくる。「承認したら公開ファイルを書き出す」はサーバレスでは動かない。
やることの全体像
- 承認判定を1か所に集約する(
lib/approve.ts相当) - 実行時に公開したものを置く動的ストアを用意し、静的棚とマージして配信する
- 投稿状態を機械的に問い合わせられる API を足す
- 溜まった分を一括承認する管理APIを足す
- 検証(ここまでやって完了)
1. 「信頼済み」の判定を絶対に間違えない
踏んではいけない罠: メールアドレスは本人確認ではない
多くの「軽量APIキー」実装は、こういう形をしている。
POST /api/v1/keys { "email": "who@example.com" }
→ { "apiKey": "<base64url(email)>.<HMAC(email, SECRET)>" }
DB 不要で検証できて便利だが、メール所有確認をしていない。 つまり 誰でも「あなたの会社のメールアドレス」でキーを発行できる。
この状態で
「APIキーに紐づくメールが信頼リストに載っていたら自動公開する」
と実装すると、第三者があなたのドメインのアドレスを名乗ってキーを取り、任意の内容を自動公開できる。 自動承認は「審査の人手を省く」ためのものであって、「本人確認を省く」ためのものではない。
正しい形: 別シークレットで署名した「信頼済みキー」を必須にする
公開エンドポイントからは絶対に発行できない種類のキーを用意し、自動承認の必要条件にする。
通常キー : k_<base64url(email)>.<HMAC(email, KEY_SECRET)> ← 誰でも取れる
信頼キー : t_<base64url(email)>.<HMAC(email, TRUSTED_KEY_SECRET)> ← 運用者が配る
TRUSTED_KEY_SECRETはKEY_SECRETとは別の値。公開APIのどこからも発行経路を生やさない- 発行は運用者のスクリプトだけ(後述)
- 自動承認の条件は 「信頼キーであること」AND「メールが信頼リストにあること」 の2つ
- 環境変数が未設定なら全件
pending(既定を安全側に倒す)
判定関数の骨格:
export function isTrustedSeller(email?: string | null): boolean {
if (!email) return false;
return (process.env.TRUSTED_SELLER_EMAILS ?? "")
.split(",").map(s => s.trim().toLowerCase()).filter(Boolean)
.includes(email.trim().toLowerCase());
}
// 投稿API側
if (identity?.trusted && isTrustedSeller(identity.email)) {
const result = await approveSubmission(stored, { requireTrustedKey: true });
if (result.ok) return json({ status: "published", slug: result.slug, ... });
// 承認できなくても投稿自体は受理済み → pending として正常応答する
}
信頼キー発行スクリプト(運用者だけが実行):
import crypto from "node:crypto";
const email = process.argv[2].trim().toLowerCase();
const sig = crypto.createHmac("sha256", process.env.TRUSTED_KEY_SECRET)
.update(email).digest("base64url");
console.log(`t_${Buffer.from(email).toString("base64url")}.${sig}`);
緩めてはいけない3点
- 秘密情報スキャンは外さない。 自動化するのは人手の審査だけ
- 第三者の投稿は自動承認しない。 リストに無ければ必ず
pending - 管理トークンを投稿側の端末へ配らない。 承認はサーバ側だけで完結させる
2. サーバレスで「実行時に公開」する
公開コンテンツが静的ファイルなら、承認してもデプロイしない限り増えない。実行時のファイル書き込みは消える。 そこで「静的棚」と「動的棚」の2系統にして、配信時にマージする。
静的棚: content/*.md … デプロイで増える。既存の公開分
動的棚: 永続ストアの1ファイル … 実行時の承認で増える
配信 : 両方をマージして返す(slug 衝突は静的側を正とする)
動的棚の置き場所は、既にあるストア層に合わせる(RDB のテーブル、オブジェクトストレージ、 プライベートリポジトリの JSON など)。1回の読み出しで全件取れる形にするのが肝心で、 1件1ファイルにすると一覧のたびに N 回叩くことになる。
// 読み出しは短期キャッシュ、書き込み前は必ず最新を読む
export async function publishedItems(opts: { fresh?: boolean } = {}) { ... }
export async function appendPublished(records: Published[]) {
// read-modify-write。バージョン/ETag/sha を渡して競合したら読み直して1回リトライ
}
ここでハマる: 「キャッシュしないfetch」が静的生成を壊す
ストア読み出しを cache: "no-store" にすると、静的生成やISRのページが
「動的サーバ使用」でビルド時に落ちる(Next.js App Router の場合)。
読み取り経路は短期キャッシュ(例: 30秒)、書き込み前と管理APIだけ強制フレッシュに分ける。
...(fresh ? { cache: "no-store" as const } : { next: { revalidate: 30 } })
一覧・詳細ページには export const revalidate = 60 を付けて ISR にすると、
承認から1分以内にサイトへ出る。詳細ページは静的生成の対象に無い slug も
オンデマンド生成されるようにしておく(Next.js なら dynamicParams 既定のまま)。
同期関数を全部 async に変えるとき
getAll() / search() / getOne(slug) の同期版が各所から呼ばれているはず。
同期版を消さず、async 版を追加して呼び出し側を移す。検索本体は対象リストを引数で受ける形に切り出すと重複しない。
export function searchIn(source: Item[], params: SearchParams): Item[] { ... }
export function search(params) { return searchIn(getAllSync(), params); }
export async function searchAsync(params) { return searchIn(await getAllAsync(), params); }
移し忘れ(メタデータ生成・OGP画像・sitemap・カテゴリ一覧・関連表示)があると 「APIには出るがサイトに出ない」という中途半端な公開になる。呼び出し箇所を機械的に列挙して潰す。
3. 投稿状態を問い合わせる API
これが無いと「公開まで到達したか」を投稿側が判定できず、同じ放置が再発する。
GET /api/v1/listings/{submissionId} (投稿時と同じキー認証)
→ { ok, submissionId, status: "pending" | "published", slug, title }
- 自分が出したものだけ返す。他人のIDも存在しないIDも 404(存在を漏らさない)
statusは投稿レコードに持たせず、「公開棚に載っているか」で判定すると二重管理にならない- 投稿の受理応答に
statusUrlを入れておくと、クライアントが後から追える
4. 溜まった分の一括承認(管理用)
POST /api/v1/admin/approve
認証: Bearer <ADMIN_TOKEN>(投稿用キーとは別物)
body: { submissionIds?: string[] } 省略時は「信頼済みの pending 全件」
→ { ok, approved, items, skipped: [{ submissionId, reason }] }
実装上の必須事項:
- 承認時にもう一度、秘密情報スキャンを通す(投稿時から本文が変わっていない保証は無い)
- 重複を二重公開しない。正規化(NFKC・空白除去・小文字化)したタイトルと本文で既存と突き合わせる
- 公開棚の読み書きは1往復にまとめる。1件ずつ書くと件数が増えたときに関数のタイムアウトに当たる
- 認証は定数時間比較(長さ比較 →
timingSafeEqual)。トークン未設定なら 503 で明示的に落とす
一括公開の前に必ず「下見」を作る
公開は取り消しが効かない。同じ認証で何も変更しない GET を用意し、
「誰の投稿が何件溜まっているか」「タイトルは何か」を人が見てから実行する。
GET /api/v1/admin/approve
→ { pending, trustedPending, bySeller: [{ sellerEmail, pending, trusted }], pendingItems: [{ title, ... }] }
実際、下見をすると「社内アカウントだと思っていたが素性の分からないアドレスが混ざっている」ことがある。 その扱いは人に決めさせる(自動で信頼リストに入れない)。
スラッグ生成の注意
タイトルからASCIIスラッグを作る場合、日本語などASCIIを含まない言語ではほぼ空になる。
「ASCII文字が3文字以上残ったときだけ採用、それ以外は item-<id> にフォールバック」にしないと、
タイトル中の数字だけが残った意味不明なURLができる。一度公開した slug は後から書き換えない(参照が壊れる)。
5. 検証(ここまでやって完了)
サーバに環境変数を渡して起動し、自動承認が効いた状態で回す。
環境変数が無いと全件 pending になり、テストが素通りして「通った」ように見えるので注意。
TRUSTED_SELLER_EMAILS=trusted@example.com ADMIN_TOKEN=test-token TRUSTED_KEY_SECRET=test-secret <起動コマンド>
自動テストで最低限これだけ確認する。
- 信頼キーで投稿 → 応答が
publishedでslugが返る - 検索APIにそのタイトルの特徴語で出る
- 本文APIが 200 で本文を返す
- 公開エンドポイントで発行した通常キーで、信頼リストのメールを名乗って投稿 →
pendingのまま(なりすまし防止の要) - 信頼リストに無いメール →
pendingのまま - 状態API: 自分のは引ける/他人のIDと存在しないIDは 404/無認証は 401
- 管理API: トークン無し・誤り・投稿用キーのいずれでも 401
- 管理API: 第三者のIDを明示指定しても承認されない
- 承認済みを再指定しても二重公開されない
- 本文に秘密情報を混ぜた投稿は信頼済みでも弾かれる
本番反映後は、実際に踏むURLを叩いて確認する(検索・本文・詳細ページ・状態API)。 検証用に投入した投稿は、公開棚とキューの両方から取り下げる。
一覧APIの上限にも注意
投稿側クライアントが「一覧を取って自分の投稿と突き合わせる」実装だと、 掲載数が一覧APIの上限を超えた瞬間に、公開済みを取りこぼして「まだ審査待ち」と誤報する。 掲載規模に合わせて上限を上げるか、状態API(第3節)で1件ずつ確認する形に移す。
運用として決めておくこと
- 信頼キーは投稿する端末ごとに配る。配布は非公開の経路で行い、公開チャットに貼らない
- 管理トークンはサーバの環境変数だけに置く。端末には置かない
- 投稿フォーム(メール紐付けの無い経路)からの投稿は自動承認の対象外。人が見る
- 信頼リストと信頼キーのシークレットを更新したら、環境変数の反映のために再デプロイする
よくある質問
+「投稿の審査キューを「信頼済みだけ自動公開」で捌く設計(なりすまし穴つき)」とは何ですか?
審査キューに投稿が溜まったまま埋もれる問題を、信頼済み投稿だけ即公開する形で潰す指示書。無検証のキー発行を信頼判定に使うと第三者が自社メールを騙れる穴と、サーバレスで静的公開棚に実行時公開を足す方法、検証10項目まで含む。
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約40万トークンかかりますが、この巻物を使えば約4.5万トークンで済みます。差し引き約35.5万トークン(API料金換算で約530円)・89%の節約です。
+どうやって使いますか?
無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。
+どのAIツールに対応していますか?
claude-code、cursor、codex-cli に対応しています。
+商用利用できますか?
ライセンスは「商用利用可 (再販不可)」です。
🤝 自分でAIを動かすのは、まだ不安…という方へ
この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。
関連する巻物
Next.js + Supabase + Vercel 立ち上げ完全自動化MD
新規Webサービスの立ち上げ (GCP/GitHub/Vercel/Supabase のプロジェクト作成〜環境変数〜本番デプロイ) を AI に一気通貫でやらせる指示書。人間の作業はログイン1回だけ。
DB型サイトを「一覧だけ会員限定・個別ページは残す」に切り替える指示書(Next.js App Router)
自社DBで集客していたサイトが競合のリスト抜き取りに気づいた時の改修手順。名前が並ぶバルクな一覧だけを会員限定にし、個別ページはtitle/H1とCTAを残す。ItemList JSON-LDやsitemapの漏れ、force-dynamic化のコスト副作用、Layer1(HTML)+Layer2(Playwright実描画)の受け入れテストまで含む。
アプリ内「不具合・要望」窓口を全アプリへ標準搭載する
利用者がその場で不具合・要望を送れる窓口を、npm依存ゼロ・CSS非依存・DBなしでも動く形で1コマンド後付けする手順。公開サイト向けの濫用対策、2段検証、配布物として崩してはいけない点まで含む。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア