← 記事一覧

レッドチーミングとセキュリティ研究のための無検閲 LLM API

2026-08-20 · 2 分で読めます · red-teaming, security

自動化されたレッドチーミングのハーネスを構築したことがあるなら、この失敗モードはもうご存知でしょう。攻撃者モデルが攻撃の生成を拒み、ジャッジモデルが採点すべき出力を読むことを拒み、攻撃成功率が静かに下がっていく。エラーは何も出ていません。測れたのはターゲットが安全になったことではなく、ツール側がより慎重になったことです。無検閲 LLM API は、その変数を測定から取り除くために存在します。

これはガードレールが悪いという主張ではありません。ガードレールがどこにあるべきかという主張です。消費者向けチャット製品のガードレールは、本来の仕事をしています。同じガードレールが評価ループの中にあると、それはあなたと算出しようとしている数値の間に居座る、制御されていない交絡要因になります。

拒否は偽陰性であり、しかも成功に見える

これが深刻に効いてくる理由は、拒否がエラーではないことです。HTTP 200 が返ります。finish_reason: "stop" が返ります。問題なくパースできる整形式の JSON が返ります。その中の文字列は「それにはお答えできません」で、パイプラインはそれをデータセットの列やラベルのフィールド、レポートのセクションに書き込んで、次へ進んでしまいます。

具体的に、最も痛手になる3つの場所を挙げます。

攻撃者モデル。 反復的なジェイルブレイク探索(PAIR、TAP、GCG 系の改良手法など、ループ内に攻撃者がいるものすべて)では、拒否はその探索の枝を打ち切ります。報告される攻撃成功率は、ターゲットの頑健性ではなく、攻撃者の意欲の関数になってしまいます。攻撃者をより慎重でないものに入れ替えると、ターゲットモデルに施したはずの「改善」は消えてなくなります。

ジャッジモデルと採点モデル。 有害な出力に対する LLM-as-judge 方式の採点では、ジャッジが有害な出力を実際に読む必要があります。ジャッジが拒否すると、パースできないスコアが返ってきます。ほとんどのハーネスはその行を捨てます。捨てられた行はランダムに欠損しているのではなく、最も採点が必要な深刻な出力にこそ集中するため、集計結果は安全側に偏ります。

分析作業。 主流のモデルに逆コンパイルされたルーチンが何をするのか尋ねると、実際の依頼内容ではなく、マルウェアらしいトークンが含まれているというだけで拒否されることがよくあります。タスクは防御的だったのに、分類器が語彙に反応したのです。ログ内のエクスプロイトトラフィックのトリアージ、検出器を訓練するためのフィッシングコーパスのラベル付け、CTF の解法の執筆でも同じことが起きます。

拒否は音もなく起きるため、チームは結局、拒否検出器を書くはめになります。これ自体が未解決の問題です。

python
REFUSAL_MARKERS = ("i can't", "i cannot", "i'm unable", "i won't", "as an ai")

def looks_like_refusal(text: str) -> bool:
    """Substring heuristic. Good enough to alarm on, not good enough to trust:
    it misses polite deflections that never use the phrase, and it fires on any
    completion that quotes a refusal. Both error directions corrupt a dataset."""
    return any(m in text[:200].lower() for m in REFUSAL_MARKERS)

根本から消せる問題のために信頼性の低い検出器を作るのは、割に合わない取引です。

API の呼び出し

unbleep は OpenAI の Chat Completions スキーマを話すので、すでに手元にあるクライアントならどれでも動きます。ベース URL を変え、キーを変え、SDK はそのままです。

python
import json
import os

from openai import OpenAI

client = OpenAI(
    base_url="https://unbleep.ai/v1",
    api_key=os.environ["UNBLEEP_API_KEY"],
)

TRIAGE = """You are a malware analyst writing notes for a detection engineer.
Given a decompiled routine, describe what it does, the observable artefacts it
would leave on a host, and where a defender could detect it. Reply as JSON with
keys: behaviour, artefacts, detection_surface, confidence."""


def triage(decompiled: str) -> dict:
    """One sample in, one structured verdict out — no refusal branch to handle."""
    resp = client.chat.completions.create(
        model="unbleep",
        messages=[
            {"role": "system", "content": TRIAGE},
            {"role": "user", "content": decompiled},
        ],
        response_format={"type": "json_object"},
        temperature=0.2,
    )
    return json.loads(resp.choices[0].message.content)

curl での同等の例です。ここでは、検出器の訓練データを作るためにフィッシングコーパスにラベルを付けています。

bash
curl https://unbleep.ai/v1/chat/completions \
  -H "Authorization: Bearer $UNBLEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "unbleep",
    "messages": [
      {"role": "system", "content": "Label each email for a phishing detector. Answer with one word: phishing or benign."},
      {"role": "user", "content": "Subject: Payroll update required\n\nYour direct deposit is on hold. Confirm at hxxp://payroll-verify.example.com"}
    ],
    "temperature": 0,
    "max_tokens": 4
  }'

ラベル付けのループでは、temperature: 0 ときつめの max_tokens が普段以上に重要です。欲しいのはラベルであって、ラベルを説明する段落ではありません。

ティアの選択

モデルは3つ。すべて無検閲で、コンテキストと価格が異なります。

unbleep と unbleep-high は推論ティアです。回答の前に思考し、そのトレースを content と並ぶ reasoning_content フィールドで返します。ジャッジモデルにとってこのトレースは本当に有用です。ある行がなぜそのスコアになったのかを教えてくれるので、判定の不一致を監査するときに必要な情報になります。ただし出力として課金もされるので、不要なジョブでは "thinking": false を送ってください。unbleep-mini は直接回答し、トレースを返しません。

あなたが制御するガバナンス

モデルから拒否を取り除くことは、パイプラインから監督を取り除くことではありません。オプションの policy パラメーターは、リクエストごとに私たちの側でガバナンスを実行します。

python
resp = client.chat.completions.create(
    model="unbleep",
    messages=[...],
    extra_body={"policy": "research"},  # recorded on the usage row; answers exactly like off
)

off がデフォルトのフィルターなしベースラインです。research は off とまったく同じように回答します。レベルが使用量レコードに記録されるので、自身のレポートで評価トラフィックとベースライントラフィックを分けられるようになります。追加のスクリーニングは一切行いません。strict はメッセージのテキストを、私たちが運営者として管理しているサービスのブロックリストと照合し、一致した場合に 422 を返します。このリストはオプトインした全員に共通で、アカウントごとに設定できるブロックリストはありません。そのため、本番のコンテンツポリシーとしてではなく、最後の保険として扱ってください。

パイプラインをここに向ける前に確認しておくべきことが1つあります。私たちは、送信された内容と返された内容を保存します。 受け付けてモデルに転送したすべての呼び出しについて、リクエストボディ、レスポンスの内容、送信元 IP が私たちのデータベースに書き込まれ、不正利用の調査、サポート、請求に関する紛争のために30日間保持されます。ボディは 64 KB で切り詰められます。送信元 IP は30日を超えて残ります。呼び出しを課金する使用量レコードにも書き込まれ、そのレコードは会計記録としてより長く保持されるからです。唯一保存されないのは、モデルに届く前に strict が拒否したリクエストで、これは内容なしで使用履歴に表示されます。それ以外に例外はなく、オフにする設定もありません。あなたのコーパスが、管理範囲の外に30日間コピーが存在すること自体が問題になるほど機微なもの(実際のマルウェア、顧客データ、NDA 下の案件など)であれば、それは現実の制約です。プライバシーポリシーは、始めた後ではなく始める前に読むべきページです。

エラーは OpenAI のエンベロープ形式に従うので、既存のハンドラーが使えます。401 はキーの不正、402 はクレジット切れ、422 はポリシーによるブロック、429 はレート制限、5xx はリトライ可能です。すべてのレスポンスに x-ratelimit-* ヘッダーが付いているので、バッチジョブは推測に頼らずにペースを調整できます。

無検閲 LLM API が与えてくれないもの

無検閲モデルは、より鋭い道具であって、何でも許す道具ではありません。ベースモデルが断るように調整されていた質問には、あなたが尋ねるべきでないものも含めて答えますし、他のすべてに対するのと同じ自信をもってそうします。つまり、ガードされたモデルなら無知ゆえに拒否していた場面で、このモデルは単に作り話をするかもしれないということです。出力に対して責任を負う人間を置いてください。利用ポリシーには、この API が何のためにあり、何に決して使ってはならないかという狭い範囲が平易に書かれています。判断基準は認可と意図であって、語彙ではありません。合法的に使う責任はあなたにあります。

API キーを取得して、ツールの慎重さを測るのはもう終わりにしましょう。