
この記事は、「NEWT Product Advent Calendar 2025」Day14 および「AIエージェント構築&運用 Advent Calendar 2025」Day18 の記事です。
「NEWT Product Advent Calendar 2025」14日目は、令和トラベル シニアエンジニアのiinumaが、EM yoshikeiからバトンをもらい執筆。ぜひ、最後までご覧ください!
2025年はさまざまなAIコーディングツールが登場し、数ヶ月ごとにツールや開発スタイルが変わっていく変化の大きな一年だったと思います。
そのような状況下、Claude CodeやCodex CLIなどのローカルで動作するコーディングエージェントに関して、このような課題を感じたことはないでしょうか?
- 自動承認するコマンドのパターンをもっと厳格に設定したい(
findは許可したい、ただし任意のコマンドを実行できてしまう-execは承認必須とするなど)
- ネットワークアクセスをもっと厳格に設定したい(
npm installを実行するときだけregistry.npmjs.org:443へのアクセスを許可するなど)
- 開発用のシークレットなどを含むgit-ignoreしているファイルの操作は明示的な承認を必須としたい
コーディングエージェントを活用してアウトプットが増えている理想状態とは、少ない指示とフィードバックでエージェントが自律的に問題解決を進め、その間、私は別の仕事を進められている状態であると考えています。
エージェントに自律的に動いてもらうために、ここまでは勝手にやってもOKという境界を設定して、万が一エージェントが暴走してもプロダクション環境の破壊やセンシティブな情報の流出が起きないレベルの安全性が必要です。
しかし、当時私が実現したい権限設定の仕組みを備えたツールは見つからず、学習目的で作ってみるのも面白いと感じて、プライベートの時間でコーディングエージェントの開発・運用を進めてきました。
結果、7月ごろから始まった3ヶ月以上掛かるプロジェクトでは私がコミットしたコードのうち60%(変更行数)はエージェントで生成したものでした。また、実装はエージェントに並列で実行させる、その間に次の実装方針の整理やコードレビューをする、というスタイルも実践できるようになりました。
※ 当時利用していたモデルはGPT-5
前置きが長くなりましたが、この記事では自作のコーディングエージェントを実務で使えるレベルにするために考えたこと、工夫したポイントを紹介させていただきます。
ツール実行の自動承認厳守すべき原則デフォルトで許可するコマンドプロジェクトごとに設定可能なものSandboxによる隔離コンテキストマネジメントすべての依頼内容と実装ログをプレーンテキストとして残すコンテキストを簡単に渡すためのSyntaxNeovimのkeymap設定おわりに📣 1月のイベント開催のお知らせ【1/28 開催!3社共催】モバイルアプリ開発 ✕ AI ー 組織・技術課題と向き合い、AIと走る【NEWT Chat リリース記念】AI × Travel Innovation Week 開催!令和トラベルでは一緒に働く仲間を募集しています1年間の感謝を込めた、”クリスマスセール🎄” 開催中!📣宣伝
ツール実行の自動承認
最初に実装したのがツール実行の自動承認機能です。安全であることがわかっているツール実行をユーザーの代わりに自動で承認します、というものです。
大まかに、絶対に厳守すべき原則と、用途に応じてカスタマイズする部分に分けて実装しました。
厳守すべき原則
- ワーキングディレクトリの外、.gitignoreで指定されたファイルへのアクセスは承認を必須とする
- ワーキングディレクトリ内でも
..など、ディレクトリトラバーサルの疑いのあるパターンは承認を必須とする
- 他の設定でアクセスを許可したとしてもこの原則を優先して、必ずユーザーの承認を求める
安全性を確認しやすい入力形式の設計:
絶対に突破されてはいけないところなので、確認のロジックが複雑にならないようツールの入力形式に制約を設けています。
具体的には、
exec_command という、マシンにインストールされているコマンドをなんでも実行できるツールの入力には find . -name '*.js' | xargs wc -l のように、パイプ | やリダイレクト > などのシェルの解釈が必要な文字列は受け取らず、 { command: "find", args: [".", "-name"] } のように、コマンドの引数を配列として受ける形式としています。こうすることで、さまざまな入力パターンが想定されるシェルコマンドの文字列の解析を避けています。トレードオフとして、シェルの解釈が必要なコマンドを実行する際は自動承認はできず、ユーザーが内容を確認して承認する必要があります。
デフォルトで許可するコマンド
コーディングタスクに必須のRead系のコマンドはわざわざプロジェクトごとに設定しなくても自動承認するように許可しています。冒頭で挙げた
find は許可するけど -exec オプションは承認を必須とする、というのはこちらで実現しています。わかりやすさのため
find の例を書きましたが、実際にはデフォルトで .gitignore を考慮してくれる fd を利用するようにプロンプトで設定しています。なお、--exec だけでなく --no-ignore など、git ignoreされたファイルにアクセスする可能性のあるオプションは承認を必須としています。こちらは設定ファイルではなく、エージェントのソースコードにハードコードしており、自動テストで動作確認をしています。
const allowedPatterns = [ { toolName: "exec_command", input: { command: "fd", /** * @param {unknown=} args */ args: (args) => Array.isArray(args) && args.every( (arg) => typeof arg === "string" && !arg.match( /^(--unrestricted|-u|--no-ignore|-I|--exec|-x|--exec-batch|-X)(=.+)?$/, ), ), }, }, ...
プロジェクトごとに設定可能なもの
プロジェクト固有のFormatterやLinterなど、エージェントがタスクを進めるうえで必要なツールの実行を許可するための設定です。
以下が設定ファイルのサンプルです。自動承認するツール名と引数のパターンを正規表現で指定することができます。
{ "autoApproval": { "patterns": [ // Linterの実行を許可 { "toolName": "exec_command", "input": { "command": "npm", "args": ["run", { "regex": "^(check|fix)$" }] } }, // srcディレクトリ配下のファイルの書き込み、更新を許可 // ※ もしエージェントが src/../package.json のように指定しても「原則」が適用され、ユーザーに承認を求める { "toolName": { "regex": "^(write_file|patch_file)$" }, "input": { "filePath": { "regex": "^src/" } } } // Google検索ツールの使用を許可 { "toolName": "ask_google", }, // Chrome DevTools MCPの使用を許可 { "toolName": { "regex": "mcp__chrome_devtools__.+" } } ], // 自動承認回数の上限:タスクが完了してなくてもステップが多い場合は途中経過を確認するため "maxApprovals": 50 } }
当初、上記の
fd の例のような柔軟な設定ができるように、設定ファイルをJSONではなくJavaScriptで記述していましたが、ルールが複雑になるとそれが安全なのかがわからずむしろ危険、と判断してJSONファイルに変更しました。Sandboxによる隔離
自動承認機能を使って安全なコマンドだけ許可すれば問題なさそうに見えますが、全然そんなことはありません。
例えば、エージェントにテストを実装、実行してもらうために、ソースファイルの編集権限と
npm run test のようなコマンドの実行を許可するとします。その時点で、任意のJavaScriptのコードの実行権限を与えているのです。ホームディレクトリから認証情報の書かれたファイルを読んだり、それをHTTPリクエストに乗せて外部に送ることも可能です。このような予期せぬ事故を防ぐために、Dockerベースのサンドボックスコマンドを自分で作ることにしました。 デフォルトではカレントディレクトリ配下をRead-onlyでマウントするのみで、明示的に許可した場合のみ書き込みやネットワークアクセスが可能になります。 ネットワークはドメイン or アドレス、ポートの指定が可能です。
意図としては、例えば、(人間が)Cloud SQL Proxyのようなものを利用して、チーム共用の開発環境のDBにつなぐユースケースがあるとします。 そんなときに、たとえローカルホストでもアクセスされたくないと考えてポートレベルで指定可能としました。
# 実行例: コマンド (npm install) をwrapする形式 # --allow-write : ワーキングディレクトリへの書き込みを許可 # --allow-net : npm registryへのアクセスのみを許可 ※ ポートを指定しない場合は443のみ許可 agent-sandbox --dockerfile Dockerfile --allow-write --allow-net registry.npmjs.org npm install
コーディングエージェント側の設定ではコマンド単位でサンドボックス環境で動かすか、そのまま動かすかを設定可能です。 コンテナ環境では時間がかかってしまう(かつ、安全な)コマンドをそのまま動かしたり、npm installを実行するときだけnpm registryへのアクセスを許可するなどの細かな制御が可能です。
※ もちろん、エージェントが暴走してpackagen.jsonのscriptに危険な操作を埋め込まないように、別途自動承認の設定の方で守る必要があります。
{ "sandbox": { // エージェントがコマンドを実行する際はここで指定したコマンドでwrapする "command": "agent-sandbox", // サンドボックスのデフォルト設定: // カレントディレクトリ配下への書き込みを許可する // docker imageは事前にビルド済みなのでビルドはスキップ(高速化のため) "args": ["--dockerfile", ".agent/sandbox/Dockerfile", "--allow-write", "--skip-build"], "rules": [ // 特定のコマンドはホストで直接実行する // ユースケース: 大規模プロジェクトで静的解析に時間が掛かるなど { "pattern": { "command": ["npm"], "args": ["run", "check"] }, "mode": "unsandboxed" }, // ネットワークアクセスを許可する(ポートを指定しない場合は443のみ許可) { "pattern": { "command": "npm", "args": ["install"] }, "mode": "sandbox", "extraArgs": ["--allow-net", "registry.npmjs.org"] } // ルールに一致しない場合はサンドボックスのデフォルト設定を適用 ] } }
コンテキストマネジメント
エージェントには自律的に動いてもらいたいので、初回のメッセージでタスクの完遂に必要な情報を伝えることが重要です。これを効率的に行うために工夫していることを紹介します。
すべての依頼内容と実装ログをプレーンテキストとして残す
エージェントがおかしなアウトプットを出してしまったとき、それを無理に修正するよりも仕切り直して最初からやり直してもらったほうが、面倒を見るコストが安く済むことがあります。
このような時に、初回の依頼内容を再利用しやすいように、
.agent/instructions.md という一つのファイルに書き込んでからエージェントに渡すようにしています。新しい依頼はファイルの先頭に追記する形で運用しており、過去の依頼内容もすべてこのファイルに残しています。また、エージェントに実装ログ(計画と進捗、遭遇した課題など)を
.agent/memory/ というディレクトリに残してもらうことで、後続のタスクを依頼する際にコンテキストとして渡しやすくしています。実装ログを残してもらうために以下のプロンプトを与えています。実装の過程で遭遇した問題や考慮事項を残すことで、コードレビュー時に参考情報としても活用できます。
## Memory Files Use memory files to save progress so tasks can be stopped, resumed, and users stay informed. - Create/Update memory files after creating/updating a plan, completing steps, encountering issues, or making important decisions. - Update existing task memory when continuing the same task. - Write the memory content in the user's language. - For very simple tasks that can be completed in a few actions, skip creating a memory file. Path: ${projectMetadataDir}/memory/<session-id>--<kebab-case-title>.md Create a concise, clear title (3-5 words) that represents the core task. Task Memory Format: <task_memory_format> # [title] ## Task Description [issues, requirements, constraints, ...] ## Context [docs, source files, commands, ...] ## Steps - [x] Completed step - [x] Completed sub-step - [ ] In Progress step - [ ] Next step ## Notes ### [title of note #1] [considerations, decisions, findings, ...] ## Future Notes [key learnings, limitations, improvements, ...] </task_memory_format>
コンテキストを簡単に渡すためのSyntax
依頼内容、ソースコード、作業ログ、スクリーンショット、さまざまなコンテキストを簡単に渡せるように
@<filepath>[start:end] という形式で、ファイル全体 or 行番号で範囲を指定することができます。# .agent/instructions.md このテストを参考に @apps/api/modules/booking/mutations/CancelBooking.test.ts PEXの航空券を使ったツアーのキャンセルのテストを書いて apps/api/modules/booking/mutations/CancelBooking.pex.test.ts 参考 PEXの航空券を使った予約データの作り方 @apps/api/modules/booking/mutations/CreateBooking.pex.test.ts:100-200 検証方法: - format / lint / typecheck: yarn check - test: yarn test <test_file_path>
Neovimのkeymap設定
特に、指示を書き込む
instructions.md のオープンとファイルパスのコピーは多用するので、それぞれ Space→i、 Space→c に割り当てて、すばやくコンテキストを操作できるようにしています。vim.g.mapleader = ' ' -- Space → i で instructions.md を開く vim.keymap.set('n', '<leader>i', ':<C-u>e .agent/instructions.md<CR>') -- Space → c でファイルパスをコピー ※ Visual modeでは行番号付き vim.keymap.set({ 'n', 'v' }, '<leader>c', ':CopyContext<CR>') local commands = { { 'CopyContext', function(opts) -- file explorer -> copy path if vim.bo.filetype == 'oil' then require('oil.actions').copy_entry_path.callback() vim.fn.setreg('+', vim.fn.fnamemodify(vim.fn.getreg(vim.v.register), ":.")) return end -- visual mode or range -> copy path with range if opts.range > 0 then local range_start = opts.line1 local range_end = opts.line2 if range_start == range_end then vim.fn.setreg('+', vim.fn.expand('%:.') .. ':' .. range_start) else vim.fn.setreg('+', vim.fn.expand('%:.') .. ':' .. range_start .. '-' .. range_end) end return end -- normal mode -> copy path vim.fn.setreg('+', vim.fn.expand('%:.')) end, { range = true } }, } for _, command in ipairs(commands) do vim.api.nvim_create_user_command(table.unpack(command)) end
おわりに
以上、コーディングエージェントを実務で使えるレベルにするための取り組みの紹介でした。
みなさんが普段利用しているコーディングエージェントのセキュリティ設定見直しのヒントとして、また、今後、AIエージェントを作る際に、一つの例として少しでも参考になるポイントがあれば幸いです。
📣 1月のイベント開催のお知らせ
令和トラベルでは、毎月技術的な知識や知見・成果を共有するLT会を毎月実施しています。発表テーマや令和トラベルに興味をお持ちいただいた方は、誰でも気軽に参加いただけます。
【1/28 開催!3社共催】モバイルアプリ開発 ✕ AI ー 組織・技術課題と向き合い、AIと走る
2026年のスタートを切る1月の「NEWT Tech Talk」は、”モバイルアプリ開発 ✕ AI ー 組織・技術課題と向き合い、AIと走る” というテーマで開催。
クラシル株式会社 なぐもさん、株式会社ヤプリ にゃふんたさんをゲストに、令和トラベル やぎにいの3名が登壇します。モバイルアプリ開発の現場で、AI活用に取り組む3社のエンジニアが、個人・チーム・技術課題それぞれの視点から、AIとどのように向き合い、どのように開発を前に進めてきたのか、具体の取り組みをシェアしながら語ります!
そのほか、毎月開催している技術発信イベントについては、connpass にてメンバー登録して最新情報をお見逃しなく!
【NEWT Chat リリース記念】AI × Travel Innovation Week 開催!
「NEWT Chat」誕生の裏側や開発ストーリーをお届けする特別企画 “AI × Travel Innovation Week” を令和トラベルのnote上で開催しました!
「NEWT Chat」のリリース背景、プロダクトの価値、開発体制、そして今後の展望など、新規事業の “舞台裏” を公開。特に、AIプロダクト開発に関わるエンジニア・PMの皆さまにとって学びの多い内容となりますので、ぜひご覧ください。
▼ AI × Travel Innovation Week のnoteはこちら:
旅行・観光業に特化したAIエージェントチャット「NEWT Chat(ニュートチャット)」についてはこちらから。
令和トラベルでは一緒に働く仲間を募集しています
この記事を読んで会社やプロダクトについて興味を持ってくれた方は、ぜひご連絡お待ちしています!お気軽にお問い合わせください!
フランクに話だけでも聞きたいという方は、カジュアル面談も実施できますので、お気軽にお声がけください。
1年間の感謝を込めた、”クリスマスセール🎄” 開催中!
NEWTでは現在、海外旅行やホテルをおトクにご予約いただける『クリスマスセール🎄』を12/4〜スタートしています!ぜひこの機会にご利用ください!
📣宣伝
次回の「NEWT Product Advent Calendar 2025」Day15は、「🧙 AI時代のエンジニア組織の進化と実践」と題して執行役員CPOのmagaraが担当します。次のブログもお楽しみに!





