概要

shelpa は、LLM エージェントに安全なファイル書き込み手段を提供するために開発した MCP(Model Context Protocol)準拠の仮想パイプラインシェルである。書き込みを tee コマンドに一本化し、全操作を .shelpa/ にミラーリングして編集履歴を保持するという設計は、コンセプトとしては良かった。

しかし、最終的にこのツールはボツにした。本稿では、shelpa の設計思想、セキュリティ実装、そしてなぜ廃止に至ったかを記録する。

設計思想:書き込みを tee に一本化する

狙い

従来の MCP ファイル操作ツールでは、LLM エージェントが write_fileedit_file で自由にファイルを書き換えられる。shelpa の狙いは、書き込み手段を UNIX パイプラインの tee に限定することだった:

  rg "pattern" src/ | awk '{print $2}' | tee output.txt
  
  • rgawksedjq 等は読み込みバッファとして使う
  • 最終的に tee で書き出す — これが唯一の書き込み手段
  • tee の書き込みは自動的に .shelpa/ にミラーコピーされ、編集履歴が残る

目論見:シェルに偽装してモデルを騙す

shelpa の設計には、もうひとつ重要な狙いがあった。ツールのインターフェースをシェルコマンドに偽装して、LLM が事前学習で獲得済みの「シェルの使い方」をそのまま流用させること — つまり、モデルを騙して定着させるという目論見だった。

LLM は事前学習で大量のシェルスクリプトやターミナルログを学習しており、tailrgawksedtee といったコマンドの使い方を「知っている」。この知識を活用するため、ツール名を意図的に UNIX シェルの語彙に寄せた:

ツール名役割
shelpa_pipeパイプラインコマンドの実行
shelpa_teeファイル書き込み(tee 経由)
shelpa_write直接書き込み(tee のラッパー)
shelpaナビゲーション(cd, pwd)

実際のヘルプ出力はこうなっていた:

  shelpa-mcp (MCP stdio server)
Usage:
  shelpa-mcp [--root <ROOT>] [--help]
Notes:
  - This binary speaks MCP over stdio. It does not serve HTTP.
  - Use your MCP client to call tools below.
  - --root sets the workspace root directory for all tool calls.
  - cwd defaults to the workspace root if not provided.
Tools:
  - shelpa_pipe    Execute a virtual safety pipeline
  (tail  rg  awk  sed  tr  jq  wc  tee  fd  ls  sort  head)
  - shelpa_write   Execute a virtual safety tee (auto guard, save override history)
Allowed pipeline commands: tail rg awk sed tr jq wc tee fd
Navigation commands (single-stage only): pwd  cd <path>  ls [path]
Pipes only. No redirects (> >>). No sed -i. No awk file output. Save via tee only.
CRITICAL: Never use standard file editing tools (such as write_file, replace, etc.)
  - always use the specified tool exclusively.
  

shelpa_pipe というツール名を見て、LLM が「これはシェルパイプラインを実行するツールだ」と認識し、学習済みのシェルコマンド知識で自然にパイプラインを組み立ててくれる — という仮説だった。ヘルプ出力の CRITICAL 行も、システムプロンプトとして LLM に「標準のファイル編集ツールは使うな、shelpa を使え」と強く指示する設計だった。

セキュリティ設計

脆弱性1:cwd オプションによるパス脱出

tool_pipecwd パラメータが canonicalize されているが、ワークスペースルート内にあることが検証されていなかった。

  // 修復後
let canonical = fs::canonicalize(&resolved)?;
if !canonical.starts_with(root.canonicalize()?) {
    return Err(GuardViolation::new(
        GuardReason::PathEscape,
        "cwd must be within workspace root"
    ));
}
  

脆弱性2:.shelpa 内のパス脱出

shelpa_write_path が target を直接 join するため、../ パターンで .shelpa 外に書き込み可能だった。

  // 修復後:正規化済みの実パスから相対パスを計算
pub fn shelpa_write_path(workspace_root: &Path, real_path: &Path) -> Result<PathBuf, GuardViolation> {
    let real_canonical = fs::canonicalize(real_path)?;
    let relative = real_canonical.strip_prefix(workspace_root.canonicalize()?)
        .map_err(|_| GuardViolation::new(GuardReason::PathEscape, "Path is outside workspace"))?;
    Ok(workspace_root.join(".shelpa").join(relative))
}
  

脆弱性3:tee のメモリバッファ無制限

tee 操作時に全出力をメモリにバッファリングするため、大容量出力で OOM が発生しうる問題。2段階の制限で対応した:

  1. ハードリミット(1MB):超過分は切り捨て、truncated=true をメタデータに設定
  2. ソフトリミット(2KB、承認ゲート):超過時は APPROVAL_REQUIRED エラーを返し、ユーザーの明示的な承認を要求
  if out_data.len() > TEE_APPROVAL_BYTES && !confirm_oversize {
    return Err(GuardViolation::new(
        GuardReason::ApprovalRequired,
        format!("tee would write {} bytes, exceeding the {}-byte threshold. \
                 Re-invoke with confirm_oversize=true to proceed.",
                out_data.len(), TEE_APPROVAL_BYTES)
    ));
}
  

監査ミラーの設計

.shelpa/ にすべての書き込みをミラーコピーする設計。tee 実行のたびに、実ファイルと同内容を .shelpa/ 内に保存し、上書き時にはタイムスタンプ付きセパレータを挿入:

  --- shelpa:overwrite ts=2026-02-25T13:17:30Z record_id=1772025450395572000 ---
(新しい内容)
  

なぜボツにしたか

根本的な問題:モデル矯正の難しさ

shelpa の設計は技術的には整っていたが、LLM エージェントに「shelpa 経由で書き込め」という行動パターンを定着させることができなかった。

具体的には:

  • ツール選択の優先度が低い:LLM はデフォルトで write_fileedit_file を使おうとする。システムプロンプトで shelpa の使用を強制しても、コンテキストが長くなると忘れる
  • パイプライン構文の構築ミスrg pattern | awk '{print $2}' | tee output.txt のようなパイプラインを正しく構築できないケースが頻発。クォートのエスケープで壊れることが多い
  • シェル偽装は通用しなかったshelpa_pipeshelpa_tee というシェル風の命名で LLM を騙す狙いだったが、モデルは「これはシェルの一種」とは認識してくれなかった。事前学習で獲得した shell 知識を流用させるという目論見は、思ったより効果が小さかった

得られた教訓

1. LLMのデフォルト行動に逆らうツールは定着しない

LLM は事前学習で学んだツール使用パターンに強く引っ張られる。独自のインターフェースを提供する場合、システムプロンプトだけでは矯正が難しい。

2. シェル偽装でモデルは騙せない

shelpa_pipeshelpa_tee のようにシェル風の命名にして、事前学習済みの shell 知識を流用させようとしたが、LLM の行動を変えるには至らなかった。ツール名の工夫よりも、LLM が内部的に持っている「ファイルを書くときは write_file を呼ぶ」というパターンの方が圧倒的に強い。それでもなんどかpipeにワンライナー流してくるのだけれども、それを精査するのもまた大変だった。

3. 監査ミラーのアイデア自体は有効

.shelpa/ に全書き込みをミラーコピーする設計は、事後監査の観点から有用だった。パターンマッチングを使えば、戻したいチェックポイントをすぐに見つけられる。これは、LLM エージェントが誤って重要なファイルを書き換えてしまった場合のリカバリーに便利だった。

まとめ

shelpa は「tee による唯一の書き込み手段 + 監査ミラー」というコンセプトで実装したMCPツールだったけど、LLMにこのツールの使用を定着させることができず、最終的にボツとなった。

モデル矯正の難しく、基本的なツール(読み書き)強制することは現時点では現実的ではなかった。とはいえ、仮想パイプラインの設計やセキュリティの防御パターンなど面白い実装ができたので、ボツにしたけど楽しかった。