0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

投稿原稿に「確認してください」が混ざる事故を、公開前の静的検査で止める

0
Posted at

重複確認した既存記事:

  • allow-listを絞りすぎたパイプラインは「在庫があるのに0件」と報告して静かに空振りする
  • GASで職員会議の議題フォームを会議アジェンダに自動整形する
  • Playwrightで「固定秒数待ち」がタグ入力を黙って飛ばした話
  • 自動化パイプラインで「カードの日付」と「実行日の日付」を混ぜると依存解決が静かに失敗する
  • allowed_context_filesをbasenameだけでステージングして27件落とした話

何が起きたか

自動投稿の前段で、Markdown本文を作るワーカーと、本文をそのまま投稿するワーカーを分けて運用している。

この形だと、本文を作る側が「最終確認してください」「必要なら差し替えてください」のような運用メモを本文中に入れてしまうと、投稿側はそれも本文として扱ってしまう。

実際の運用ログにも、投稿側は「本文そのまま」「本文は無改変」で公開する前提の記録が残っている。つまり、本文生成側で混入を止めないと、投稿側で自然には消えない。

今回の穴は、allow-listや日付解決ではなく、人間向けの申し送りが、公開本文の一部として流れてしまうという境界の問題だった。

再現用の小さい構成

まず、投稿本文を1つ作る。

POST_QIITA_01

TITLE=投稿原稿に運用メモを混ぜない
TAGS=自動化,Node.js,Markdown,CI,運用

# 投稿原稿に運用メモを混ぜない

本文はここから始まります。

公開前に人が確認してください。

この最後の1行は、作業メモとしては自然に見える。でも、投稿側がMarkdownをそのまま使う設計なら、これは公開本文になる。

次に、本文を受け取る側を極端に単純化する。

import fs from "node:fs";

const markdown = fs.readFileSync("post.md", "utf8");

const title = markdown.match(/^TITLE=(.+)$/m)?.[1];
const tags = markdown.match(/^TAGS=(.+)$/m)?.[1].split(",");
const body = markdown
  .replace(/^POST_QIITA_01\s*/m, "")
  .replace(/^TITLE=.+\r?\n/m, "")
  .replace(/^TAGS=.+\r?\n/m, "")
  .trim();

console.log({ title, tags, body });

実行すると、body の末尾に「公開前に人が確認してください。」が残る。これはパーサのバグではない。入力が公開本文として成立していないのに、機械的には正常なMarkdownだから通ってしまう。

直し方

投稿処理の直前ではなく、本文生成の成果物を受け取った時点で検査する。

今回ほしい検査は大きく3つ。

  1. 投稿ラベル、タイトル、タグがある
  2. 公開本文に残してはいけない運用メモがない
  3. 認証情報らしい文字列やローカルの絶対パスがない

次のような小さい検査スクリプトで十分だった。

import fs from "node:fs";

const file = process.argv[2] ?? "post.md";
const text = fs.readFileSync(file, "utf8");

const required = [
  [/^POST_QIITA_01$/m, "POST_QIITA_01 label is missing"],
  [/^TITLE=.+$/m, "TITLE is missing"],
  [/^TAGS=([^,\n]+,){4}[^,\n]+$/m, "TAGS must contain 5 tags"],
  [/```[\s\S]*?```/m, "at least one code block is missing"],
];

const forbidden = [
  [/下書き|要確認|確認してください|差し替えてください|必要なら|人が確認/u, "editorial memo remains in body"],
  [/[A-Z]:\\[^ \n\r\t`]+/u, "local absolute path remains"],
  [/[A-Za-z0-9_-]{32,}/u, "long credential-like string remains"],
];

const errors = [];

for (const [pattern, message] of required) {
  if (!pattern.test(text)) errors.push(message);
}

for (const [pattern, message] of forbidden) {
  if (pattern.test(text)) errors.push(message);
}

if (errors.length > 0) {
  console.error(errors.map((e) => `- ${e}`).join("\n"));
  process.exit(1);
}

console.log("ok");

この検査は、内容の良し悪しを判定しない。見るのは「公開本文として流してよい最低条件を満たしているか」だけに絞る。

ここを欲張って、文章品質やSEOまで1本の検査に入れると、失敗時に何を直せばよいかが曖昧になる。事故防止の検査は、落とす条件を狭く、説明できる形にした方が扱いやすい。

再現手順

作業用ディレクトリに post.md と check-post.mjs を置く。

node .\check-post.mjs .\post.md

最初の例では、次のように落ちる。

- editorial memo remains in body

そこで、本文末尾の運用メモを消す。

POST_QIITA_01

TITLE=投稿原稿に運用メモを混ぜない
TAGS=自動化,Node.js,Markdown,CI,運用

# 投稿原稿に運用メモを混ぜない

本文はここから始まります。

もう一度実行する。

node .\check-post.mjs .\post.md

今度は通る。

ok

実装で気をつけた点

1. 「投稿側で消す」ではなく「成果物側で落とす」

投稿側で禁止語を削除する実装にすると、本文が勝手に変わる。

今回のように、投稿側が本文をそのまま扱う前提なら、投稿直前の自動修正より、成果物を作った時点で失敗させる方が追いやすい。

2. 禁止語は増やしすぎない

禁止語を増やしすぎると、普通の技術説明まで落ちる。

たとえば「レビュー」「確認」だけで落とすと、コードレビューや確認用ログの説明まで巻き込む。今回は、公開本文に残ると明らかにおかしい表現に寄せた。

const forbiddenEditorialMemo =
  /下書き|要確認|確認してください|差し替えてください|必要なら|人が確認/u;

3. ローカルの絶対パスは本文から消す

公開記事に作業端末の絶対パスが入ると、読者には不要な情報になる。さらに、環境名やユーザー名が見える場合もある。

検査では、Windowsの絶対パスらしいものを落とすだけでも効果があった。

const windowsAbsolutePath = /[A-Z]:\\[^ \n\r\t`]+/u;

必要なら、記事内では ./post.md や ./check-post.mjs のような相対パスに置き換える。

まとめ

自動化で怖いのは、派手に失敗する処理だけではない。

本文としてはMarkdownの形をしている。でも、公開してはいけない作業メモが混ざっている。こういう入力は、パーサや投稿処理から見ると正常に見える。

今回の対策は、投稿処理を賢くすることではなく、成果物の境界で静的検査を1つ足すことだった。

本文をそのまま流すパイプラインでは、「そのまま流してよい本文か」を、本文ができた直後に見る。これだけで、運用メモ混入の事故はかなり減らせる。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?