ANTIGRAVITY LABEN
記事一覧/Antigravity 基本
Antigravity 基本/2026-08-10上級

何にも一致しないつもりの1行が、全コマンドを承認していました — allowlist の空集合を棚卸しする

コマンド語がゼロ個に分解される allowlist entry が、すべてのコマンドに一致して自動承認していた問題。手元の設定を分解して測り、修正で閉じた穴と閉じなかった穴を切り分け、先頭トークン列一致へ書き換えるまでの手順をまとめました。

Antigravity CLI23権限設計8allowlistセキュリティ14設定監査

プレミアム記事

自分の設定ファイルに # 週次ジョブ用に一時追加(戻すこと) という行が残っていました。数週間前に一時的な許可を足して、あとで戻すつもりでコメントアウトした行です。

コメントアウトしたのだから、その entry は無効になっている。そう思っていました。

CLI 1.1.11 の変更内容を読んで、その前提が間違っていたことに気づきました。コマンド語がゼロ個に分解される entry は、無効になるのではなく、すべてのコマンドに一致していたという記述があったためです。

無効化したつもりの1行が、全承認のスイッチとして働いていた。個人開発でエージェントに作業を任せている側としては、背筋が冷える種類の話でした。

「何にも一致しない」と「すべてに一致する」を分けているもの

allowlist の照合は、素朴に書くと次の形になります。entry から取り出したコマンド語がすべて候補コマンドに含まれていれば、その entry に一致したとみなす、という判定です。

def matches(entry_words, candidate_words):
    return all(w in candidate_words for w in entry_words)

この関数に空のリストを渡すとどうなるか。Python の all() は空のイテラブルに対して True を返します。数学でいう空集合上の全称量化がそのまま真になる、という性質です。

>>> all(w in ["git", "status"] for w in [])
True
>>> all(w in [] for w in [])
True

つまり entry_words が空になった瞬間、この entry はあらゆる候補に一致します。「条件がないから何も通さない」ではなく「条件がないから全部通る」側に倒れます。

言語仕様としては正しい挙動です。問題は、この関数が権限判定に使われているとき、空集合が意味するものが「許可なし」ではなく「無制限の許可」になってしまう点にあります。フェイルクローズであるべきところが、フェイルオープンに倒れている。

そして entry がコマンド語ゼロに分解される書き方は、意図せず書けてしまいます。

書き方分解されるコマンド語書いた人の意図
timeなし計測用に足したが、後続コマンドを書き忘れた
command(time)なしラッパ記法を試した残骸
# 一時的に無効化なしコメントアウトして無効化したつもり
()なし複合コマンドを書きかけたまま保存
!なし否定を書きかけた

time は シェルキーワードであって実行ファイルではありません。time git status なら git が残りますが、time 単体ではコマンド語が残らない。ここが落とし穴でした。

手元の設定を分解して数える

概念として理解しても、自分の設定に該当する行があるかどうかは別の話です。実際に走査してみました。

entry を分解してコマンド語を取り出す部分を、次のように書きました。コメント除去 → ラッパ記法の剥がし → シェル語彙での分割 → 区切り記号とキーワードの除外、という順です。

import re, shlex
 
SHELL_KEYWORDS = {
    "time", "do", "done", "then", "else", "elif", "fi", "esac",
    "in", "!", "{", "}", "[[", "]]", "coproc", "function", "select",
}
SEPARATORS = {"&&", "||", "|", ";", "&", "(", ")"}
 
def command_words(entry: str):
    src = re.sub(r"#.*$", "", entry, flags=re.M).strip()   # コメント除去
    if not src:
        return []
    src = re.sub(r"\bcommand\(([^)]*)\)", r"\1", src)      # command(x) を剥がす
    lex = shlex.shlex(src, posix=True, punctuation_chars="();&|")
    lex.whitespace_split = True
    try:
        toks = list(lex)
    except ValueError:
        return []
    words, expect_head = [], True
    for t in toks:
        if t in SEPARATORS or re.fullmatch(r"[();&|]+", t):
            expect_head = True
            continue
        if expect_head:
            if t in SHELL_KEYWORDS or re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*=.*", t):
                continue        # キーワードと環境変数代入は「頭」を消費しない
            words.append(t)
            expect_head = False
    return words

expect_head は「次に来るトークンがコマンド語の位置かどうか」を持つフラグです。パイプや && を跨ぐたびに立て直すことで、git diff --stat | head -20 から githead の両方を拾えます。環境変数代入を読み飛ばしているのは、TZ=Asia/Tokyo date の実体が date だからです。

実行結果を並べます。

'git status'                  -> ['git']
'time'                        -> []
'command(time)'               -> []
'# 一時的に無効化'             -> []
'()'                          -> []
'TZ=Asia/Tokyo date'          -> ['date']
'(cd build && make)'          -> ['cd', 'make']
'git diff --stat | head -20'  -> ['git', 'head']
'time git status'             -> ['git']

time 単体は空、time git statusgit が残る。想定通りの分解になりました。

ここから先の検証は、CLI 本体の実装をそのまま動かしたものではありません。公開されている変更内容の記述に沿って照合ロジックを再現し、私自身の運用設定に近い entry を並べて測ったものです。実装の細部は異なる可能性があるため、数値は「この形の判定ではこうなる」という範囲で読んでいただければと思います。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
縮退した entry を1件混ぜるだけで自動承認率が 51.6% から 100% へ跳ね上がる過程を、実際に走らせた数値で示します
修正後も残る穴: コマンド語単位の一致では、意図した12件の entry のうち 58.3% が意図を超えた範囲に一致していました
先頭トークン列一致への書き換えで意図超過を 25.0% まで下げ、git push --force や cat ~/.ssh/id_ed25519 を確認側へ戻す実装
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

Antigravity 基本2026-07-27
&& の右側は誰が承認していたのか — コマンドを分解してから allow ルールに当てる
承認画面に出ていたのは、実行されるコマンドの一部でした。&& や ; で連結したコマンドを引用符とコマンド置換を越えずに分解し、セグメント単位で allow ルールに当てるハーネスを、自分の運用コマンド45本での実測とともにまとめます。
Agents & Manager2026-07-19
作業フォルダの外に書けてしまった朝 — 無人実行の書き込み先を1関数で検札する
Antigravity CLI 1.1.3 で、always-proceed のときワークスペース外への書き込みが誤って自動承認されていた不具合が塞がれました。無人実行の書き込み先を、正規化と包含判定だけで検札する小さなガードを、すり抜け試験つきでまとめます。
Antigravity 基本2026-08-02
同じ agent.md を別のリポジトリへ置いたら、静かに別の仕事をしていました — 移植先で能力契約を解決してから走らせる
CLI 1.1.6 で agent.md をファイルとして持ち回れるようになりました。同じ定義を8つのリポジトリへ置き、走らせる前に前提が揃っているかを解決する仕組みを実装し、素朴な確認器との差を実測しています。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →