Hermes Agentのdenyルールでforce pushを止める|YOLOより強い拒否線を作る

Hermes Agentのdenyルールでforce pushを止める|YOLOより強い拒否線を作る

git push --force origin mainを試しても、GitHub へは 1 byte も送られませんでした。Hermes Agent のapprovals testが実行前に判定し、終了コード 3 で拒否したからです。

verdict : user-deny  (exit 3)
rule    : git push --force*

通常の承認は、人が「今回だけ許可する」と答えられます。しかし、force push や remote script の実行は、承認の選択肢に出したくない場合があります。私は公開先や共有履歴を壊す操作に、モデルが越えられない拒否線を置く方が安心です。

今回はapprovals.denyへ 2 件を登録し、通常時、YOLO 時、Docker backend で判定がどう変わるかを実機で確認します。

前回の権限設計から一段狭くする

前回の無人で動かすAIコーディングエージェントの権限設計では、書き込み、network、承認、Git push を製品別に分けました。今回は Hermes Agent だけに絞り、禁止した command を事前に検査します。

前回完了時今回完了後
危険操作には承認を挟みます特定操作は承認画面へ進む前に拒否します
approvals.cron_mode: denyで無人時の確認待ちを防ぎますapprovals.denyで対話時と無人時に同じ禁止線を使います
Git pushを独立した権限として扱います通常のpushを残し、force pushだけを止めます
実行環境で権限範囲を変えますlocalとDockerでguardの適用差を検査します

すでに deny rule を設定済みなら、「実行せずに 4 通りを試す」まで飛ばせます。

なぜpromptではなく実行前のguardに置くのか

「main へ force push しないでください」と prompt に書く方法は必要です。ただし、これはモデルへの指示です。長い作業や外部文書の読み込みが重なると、指示の優先度を誤る余地が残ります。

approvals.denyは terminal command の実行前に働きます。一致した command は、--yolo/yoloapprovals.mode: offより先に拒否されます。agent へ返る message にも、再試行や言い換えをしないよう明記されます。

私は prompt を進行方向、deny rule を道路の車止めとして分けています。自律化を進めるほど、守る対象は model の外へ置きます。

用語表

用語今回の意味
dangerous commandHermes Agent が破壊的と判定し、通常は承認を求める command です
approvals.modesmartmanualoffから承認方針を選ぶ設定です
YOLOdangerous commandの承認を省くセッション modeです
hardline blocklistroot filesystemの削除など、設定にかかわらず拒否する組み込み規則です
approvals.deny利用者が追加する拒否用のglob patternです
glob*?で文字列へ一致させるshell風patternです。正規表現ではありません
normaliZed tracequoteによる分割などを正規化し、判定に使ったcommand候補です
exit code 3approvals testがhardlineまたはuser denyで拒否した結果です

判定順を先に見る

今回触る範囲は、次の順で動きます。

terminal command


┌──────────────────────────────┐
│ 1. isolated containerか      │── yes ──▶ guardを省略
└──────────────┬───────────────┘
               │ no

┌──────────────────────────────┐
│ 2. hardline blocklist         │── match ─▶ 拒否
└──────────────┬───────────────┘


┌──────────────────────────────┐
│ 3. approvals.deny             │── match ─▶ 拒否
└──────────────┬───────────────┘


┌──────────────────────────────┐
│ 4. YOLO / mode: off           │── on ────▶ 許可
└──────────────┬───────────────┘


┌──────────────────────────────┐
│ 5. allowlist / 危険pattern    │── match ─▶ 許可または承認
└──────────────────────────────┘

deny rule は YOLO より前です。ここが単なる承認設定との違いです。

一方、Docker、Singularity、Modal、Daytona、Vercel Sandbox のような isolated backend は、container 自体を境界として command guard を省きます。deny rule を host の防御として使う設計だからです。container 内でも command を禁止したい場合は、image、権限、network policy で別に制限します。

まずversionとcommandを確認する

2026 年 8 月 27 日に、手元の Hermes Agent で次を実行しました。

hermes --version
hermes approvals --help

version は次のとおりです。

Hermes Agent v0.20.5 (2026.8.19) · upstream 261a4efb

approvalsには、履歴から候補を作るsuggestと、command を実行せず判定するtestがありました。

positional arguments:
  <subcommand>
    suggest     Propose command_allowlist entries from past approvals
    test        Dry-run the approval verdict for a command (never executes it)

approvals testは対象 command を動かしません。approval prompt も出さず、設定や履歴も更新しません。終了コードは、許可が 0、承認が必要なら 2、拒否なら 3 です。

専用homeで設定を試す

普段の~/.hermes/config.yamlを変えずに試すため、mktempで一時的な Hermes home を作りました。

tmp=$(mktemp -d)
export HERMES_HOME="$tmp"
hermes config set approvals.deny \
  '["git push --force*", "*curl*|*sh*"]'
hermes config get approvals.deny

実行結果です。一時パスは実行時に作られた値です。

✓ Set approvals.deny = ['git push --force*', '*curl*|*sh*'] in /tmp/tmp.BVPX4v1f7z/config.yaml
- git push --force*
- '*curl*|*sh*'

1 件目は force push を止めます。2 件目はcurlの出力を shell へ渡す形を止めます。pattern は case-insensitive で command 全体に照合されます。

pattern は必ず quote します。YAML では行頭の*が alias として扱われるため、裸の*curl*|*sh*は設定 file を壊します。hermes config setを使えば、手作業の indent ミスも避けられます。

実行せずに4通りを試す

force pushは終了コード3になる

--より後ろが検査対象です。command 自体は実行されません。

hermes approvals test -- git push --force origin main
printf 'exit=%s\n' "$?"
command : git push --force origin main
env-type: local
verdict : user-deny  (exit 3)
rule    : git push --force*
detail  : matches a user-defined approvals.deny rule in config.yaml (blocked even under --yolo / mode=off)
normalized trace (variants the detectors evaluated):
  - git push --force origin main
exit=3

rule 欄に設定した pattern が出ています。曖昧な「危険そうです」ではなく、どの設定に一致したかを確認できます。

通常のcontent branchへのpushは残す

禁止範囲を広げすぎていないかも確認します。

hermes approvals test -- git push origin content/test
printf 'exit=%s\n' "$?"
command : git push origin content/test
env-type: local
verdict : allow  (exit 0)
detail  : no guard matched; would run without a prompt
normalized trace (variants the detectors evaluated):
  - git push origin content/test
exit=0

通常の push は終了コード 0 です。git push *を deny へ入れるより、壊したくない操作だけを狭く指定した方が日常作業を止めません。

YOLOでもforce pushは拒否する

次はセッションだけ YOLO を有効にして、同じ検査を繰り返します。

HERMES_YOLO_MODE=1 \
  hermes approvals test -- git push --force origin main
printf 'exit=%s\n' "$?"
verdict : user-deny  (exit 3)
rule    : git push --force*
detail  : matches a user-defined approvals.deny rule in config.yaml (blocked even under --yolo / mode=off)
exit=3

YOLO は approval prompt を省きますが、user deny rule を消しません。自動化で YOLO が必要でも、force push だけは許さない運用を作れます。

Docker backendではguardを省く

最後に backend だけ変えます。これも Docker を起動せず、判定だけを行います。

hermes approvals test \
  --env-type docker \
  -- git push --force origin main
printf 'exit=%s\n' "$?"
command : git push --force origin main
env-type: docker
verdict : allow  (exit 0)
detail  : env_type 'docker' is an isolated container backend; the runtime skips all command guards for it
exit=0

この 0 は、host の Git repository へ force push してよいという意味ではありません。isolated container では、host 向けの command guard を適用しないという判定です。credential や repository を container へ mount すれば、container から外部へ影響できます。mount と credential の範囲は別に確認します。

deny ruleはsandboxではない

approvals.denyが守るのは、Hermes Agent の terminal 実行経路です。悪意ある process を閉じ込める OS sandbox ではありません。

例えば、許可した Python program が内部で別 process を起動する場合、外側の command 文字列だけでは全動作を表せません。MCP、browser、Computer Use も terminal とは別の経路です。force push を本当に不可能にしたいなら、GitHub 側の branch protection、権限を絞った credential、隔離環境を重ねます。

私なら次のように役割を分けます。

  • prompt に運用方針を書きます
  • approvals.denyで agent 経由の禁止 command を止めます
  • GitHub の権限で force push 自体を拒否します
  • untrusted code は isolated backend へ移します
  • 外部操作の後は remote state を読み返します

1 つの guard へ安全性を預けません。deny rule は、日常の自動化を残したまま、越えてほしくない操作を明文化する層です。

よくあるエラー

症状原因対応
unrecognized arguments: --forceと出ます検査対象のflagをHermes側が読んでいますcommandの前に--を置きます
YAML parse errorになります*から始まるpatternをquoteしていませんhermes config setでJSON配列として設定します
通常のpushまで拒否されますpatternがgit push *のように広すぎますgit push --force*まで狭め、approvals testで両方試します
YOLOなのに終了コード3ですdeny ruleはYOLOより先に判定されます意図した拒否なら正常です。解除は設定変更として扱います
Docker指定では終了コード0ですisolated backendはhost向けguardを省きますmount、credential、networkをcontainer側で制限します
patternを変えたのに古い判定です別profileや別のHERMES_HOMEを見ていますhermes config pathで対象fileを確認します
どのruleに一致したか分かりません実commandを直接試していますhermes approvals test -- <command>でruleとtraceを読みます
quoteで分割すれば通ると思いました判定前に正規化候補を作りますnormalized traceを確認し、回避ではなくruleを直します

拒否線を先に決める

今回の実行では、force push は通常時も YOLO 時も終了コード 3 でした。通常の content branch への push は 0 です。禁止範囲を絞りながら、検査結果を機械で読めます。

自律化では「何をできるか」から設定しがちです。私は先に「何だけはさせないか」を決めます。その線をapprovals.denyへ置き、approvals testで実行前に確かめます。許可を増やす作業と、拒否線を消す作業を同じ変更にしないことも大切です。

出典

一次情報確認した内容
Hermes Agent公式ドキュメント: Securityapproval mode、YOLO、hardline blocklist、approvals.deny、containerでのguard省略
Hermes Agent: tools/approval.pydeny ruleの照合順、case-insensitive照合、command正規化、拒否message
Hermes Agent: hermes_cli/approvals_test.pyapprovals testのread-only性、判定順、終了コード0・2・3
Hermes Agent: test_approval_deny_rules.pyYOLO、allowlist、containerとの優先順位を確認するtest
Python公式ドキュメント: fnmatch*?、文字classを使うshell風patternの仕様
この記事をシェア