Claude CodeでDenoを開発する — deno taskと--allowの二重の権限設計
Denoの--allowフラグはdeno.jsonのtasksに閉じ込め、Claude Codeにはdeno taskだけを許可する。lint・check・testを検証コマンドにする設定例と、二重でも残る抜け穴を示します。
Claude CodeでDenoプロジェクトを進めるなら、権限は二か所で絞れます。Claude Code側では「どのコマンドを承認なしで実行してよいか」を決め、Deno側では「実行されたプログラムが何に触れてよいか」を決めます。
この2つを別々に設計すると、--allow-all が紛れ込む余地が生まれます。そこで、Denoの --allow-* フラグは deno.json の tasks に閉じ込め、Claude Codeには deno task だけを許可する構成にします。検証コマンドは deno lint、deno check、deno test の3つです。
2つの権限は何を見ているか
Denoは、明示的に許可しない限りファイル、ネットワーク、環境変数、サブプロセスにアクセスできません。許可は --allow-* フラグか、実行時の確認プロンプトで与えます。
Claude Codeの権限ルールは、Claudeが書いたコマンドの文字列を見ます。Bash(deno task test) というルールは、その文字列と一致した呼び出しを承認なしで通します。
2つの層が見ているもの
Claude Codeの権限ルール
Claudeが呼ぶコマンドを承認するか、聞くか、止めるかを決めます。deno task test を通すかどうかはここで決まります。
Denoの権限
起動した deno run や deno test が何に触れてよいかを決めます。--allow-read=./data なら、読めるのは ./data だけです。
片方だけでは足りません。Claude Codeのルールだけだと、許可したコマンドの中身が広い権限で走ります。Denoのフラグだけだと、Claudeが別のフラグでコマンドを書き直せます。
フラグはdeno.jsonのtasksに置く
deno task は、deno.json の "tasks" に定義したコマンドを実行します。Denoのドキュメントにある例では、タスクの値に deno run --allow-read=. --allow-write=. scripts/collect.js のようにフラグ込みで書いています。
この性質を使います。権限フラグをタスクに固定すれば、Claudeが打つコマンドは deno task collect のように短く安定します。承認ルールも、その文字列に合わせて書けます。
{
"tasks": {
"check": "deno check src/main.ts",
"lint": "deno lint",
"fmt:check": "deno fmt --check",
"test": "deno test --no-prompt --allow-read=./testdata",
"collect": "deno run --no-prompt --allow-net=api.example.com --allow-env=API_KEY --allow-read=./data --allow-write=./data scripts/collect.ts",
"verify": "deno task lint && deno task check && deno task test"
}
}権限の絞り方は、Denoのセキュリティ解説にある次の3点に沿っています。
--allow-net=example.comのように、許可する対象を絞る--allow-read=./dataはディレクトリ単位、--allow-env=API_KEYは変数単位で絞る- 値なしの
--allow-netはカテゴリ全体を許可するため、必要以上に広い
--no-prompt は、許可されていない操作を確認プロンプトで待たせず、そのまま失敗させるための指定です。権限不足は NotCapable エラーとして表に出るので、Claudeが出力を読んで原因を判断できます。
verify は3つのタスクをつなげただけです。deno lint は違反があると非ゼロで終了するので、&& でつなぐと最初の違反で止まります。deno check と deno test も失敗時に非ゼロで終わるかは、deno task verify; echo $? を壊れたコードで走らせれば手元で確かめられます。
なぜverifyを1本にまとめるのか
Claude Codeは &&、||、;、|、&、改行を区切りとして認識し、複合コマンドを部分ごとに分けて照合します。許可ルールは、すべての部分に一致して初めてコマンド全体を通します。
そのため、Claudeが deno task lint && deno task check と直接連結して打っても、両方が allow にあれば通ります。ところが deno task test && deno run -A main.ts のように1つでも許可にない部分が混ざると、その部分の ask が効いて確認が入ります。deny と ask は、サブシェルやコマンド置換の中に入れたコマンドにも及びます。
ここまでなら連結を許してもよさそうですが、末尾が && で終わるような解析できない形は、部分に分けられず allow では通りません。timeout 30 deno task test のように timeout、time、nice、nohup、stdbuf を前に付けた形は、ラッパーが剥がされて同じルールに照合されます。
連結の組み合わせはClaudeの書き方しだいで増えるため、3つの検証をタスク名 verify に固定します。許可ルールは1行で済み、確認が入るのは検証の外に出た部分だけになります。
Claude Code側はdeno taskだけを許可する
次に .claude/settings.json です。許可するのは、deno.json に定義済みのタスク名だけにします。
{
"permissions": {
"allow": [
"Bash(deno task verify)",
"Bash(deno task lint)",
"Bash(deno task check)",
"Bash(deno task test)",
"Bash(deno fmt --check)"
],
"ask": [
"Bash(deno run *)",
"Bash(deno eval *)",
"Edit(/deno.json)",
"Edit(/deno.jsonc)"
],
"deny": [
"Read(./.env)"
]
}
}ルールは deny、ask、allow の順に評価され、最初に一致したものが結果を決めます。Bashのルールは * が任意の文字列に一致します。そのため Bash(deno run *) を allow に入れると、deno run -A main.ts まで承認なしで通ります。
だから deno run は ask に置きます。確認画面にコマンド全体が出るので、フラグを読んでから承認できます。
deno eval を ask に置く理由は、Denoの権限が実行する側のコードに差をつけないことにあります。Denoは同じ権限レベルでのコード実行に制限を設けないため、eval や new Function、動的import、Web Workerで組み立てたコードは、呼び出し元と同じ権限で動きます。文字列で渡したコードが何をするかは、フラグだけからは読めません。
deno.jsonの編集も確認に回す
見落としやすいのがここです。タスクが権限の入れ物になっている以上、deno.json を書き換えれば権限も変わります。Claudeが "test" の値に -A を足せば、許可済みの deno task test が全権限で走ります。
Edit(/deno.json) を ask に入れておくと、この変更だけは毎回確認が入ります。パスの先頭の / は、プロジェクト設定の場合は作業ディレクトリ基準で解決されます。/Users/... のような絶対パスとして扱われるわけではありません。
差分を見るときは、--allow-* の追加と値の広がりを探します。
--allow-run、--allow-ffi、-A/--allow-allが増えていないか--allow-net=hostが値なしの--allow-netに変わっていないか--allow-writeの対象が広がっていないか
二重にしても残る抜け穴
allow-runとallow-ffiはサンドボックスの外に出る
Denoのドキュメントは、2つのフラグを --allow-all と同等に扱うよう求めています。
--allow-runで起動したサブプロセスは、Denoプロセスに与えた制限を引き継ぎません。--allow-run=denoは特に危険で、新しいdenoを--allow-allで起動できてしまいます--allow-ffiで読み込んだネイティブライブラリは、機械語のままシステムコールを発行できます
--allow-write と --allow-run の組み合わせにも注意が必要です。実行を許可した実行ファイルの置き場所に書き込めると、そのバイナリを上書きされるおそれがあります。タスクのレビューでは、この3つのフラグが出たら立ち止まる価値があります。
Bashのdenyとaskは境界ではない
Claude Codeのドキュメントによると、Bashの deny や ask のルールは、Claudeが通常書く形のコマンドだけを止めます。/bin/rm の絶対パス指定や bash -c 'rm -rf build/' は、Bash(rm *) では止まりません。
deno run の ask も同じ性質です。sh -c 'deno run -A ...' のような書き方までは拾えないと考えておきます。パスやネットワークをOSの層で強制したいなら、次のサンドボックスを併用します。
静的importは権限を通らない
Denoは、最初の静的モジュールグラフ(静的な import と、文字列リテラルの import())を、権限の確認なしで読み込みます。ローカルファイルの読み込みに --allow-read は要らず、リモートモジュールの取得に --allow-net も要りません。
制限がかかるのは読み込みが終わってからです。変数を渡す import(someVariable) のような動的importは、実行時に --allow-read や --allow-import の対象になります。依存関係の脆弱性は権限では防げないので、Denoの deno audit をCIに加える手があります。
3層目: Claude Codeのサンドボックス
Claude Codeには、シェルコマンドの到達先をOSが強制するサンドボックスがあります。macOS、Linux、WSL2で動き、Bash、PowerShell、Monitorのコマンドと、その子プロセスが対象です。既定ではオフで、/sandbox か設定の sandbox.enabled で有効にします。
{
"sandbox": {
"enabled": true,
"network": {
"allowedDomains": ["api.example.com"]
}
}
}Denoの権限はDenoランタイム自身が強制し、OSの仕組みには依存しません。サンドボックスはその外側で効く別の層です。許可ドメインにDenoの取得先を含める必要があるかは、レジストリや依存の構成で変わるため、最初のタスク実行で失敗を見ながら足していきます。
検証コマンドをCLAUDE.mdに書く
権限ルールは、Claudeが何をできるかを決めます。何をするべきかは CLAUDE.md で伝えます。ただし CLAUDE.md はClaudeの振る舞いを方向づけるだけで、許可と禁止は変えません。
# Deno プロジェクトのルール
- 変更後は `deno task verify` を実行し、出力を読んでから完了を報告する
- 権限フラグ(`--allow-*`)は `deno.json` の tasks にだけ書く。コマンド直書きや `-A` は使わない
- 権限エラー(NotCapable)が出たら、`-A` で回避せず、必要な最小の権限をタスクに足す案を示す
- `deno.json` を編集するときは、権限フラグの変更点を明記するdeno test は絞り込みの手段も持っています。反復中は、名前で絞る --filter、最初の失敗で止める --fail-fast、gitの変更に関係するテストだけを走らせる --changed が使えます。
deno task verify
deno test --changed
deno test --filter "connect" --fail-fast2行目以降は deno test を直接呼ぶ形です。許可ルールに deno task test しか入れていない場合は、確認が入ります。承認する前に、付いているフラグを見ます。
使い分けの目安
| 場面 | Claude Code側 | Deno側 |
|---|---|---|
| lint・型チェック・テスト | Claude Code側deno task を allow | Deno側タスクに --no-prompt と絞った --allow-* |
| 外部APIを叩くスクリプト | Claude Code側個別タスク名を allow | Deno側--allow-net=ホスト名 と --allow-env=変数名 |
その場限りの deno run | Claude Code側ask で毎回確認 | Deno側確認画面でフラグを読む |
deno.json の変更 | Claude Code側Edit を ask | Deno側差分で権限フラグの増減を見る |
| 信頼できないコードの実行 | Claude Code側サンドボックスを併用 | Deno側Denoの権限だけに頼らない |
Denoのドキュメントは、信頼できないコードを走らせる場合は多層で守るよう勧めています。権限を絞ること、依存を固定すること、OSのサンドボックスや仮想環境を重ねることです。
依存の固定は、--frozen のロックファイルと --cached-only で、実行前に決めた範囲より多くのコードを読み込ませない形で行います。信頼できないコードに近い作業をClaudeに任せるなら、この2つをタスクのフラグに足す手があります。ロックファイルが変わる更新作業はタスクの外に出し、deno.json と同じく差分で確かめます。
つまずきやすい点
- 権限ブローカーを使っている場合:
DENO_PERMISSION_BROKER_PATHを設定すると、すべての--allow-*と--deny-*は無視され、確認プロンプトも出ません。判断は外部のブローカーに移るため、タスクに書いたフラグは効きません - テストでも権限が要る:
deno testはdeno runと同じ権限モデルで動きます。読み書きや通信をするテストには--allow-readなどを付けます。testタスクに必要な分だけ足します --deny-*は--allow-*に勝つ:--allow-read --deny-read=/etcは/etcを除いて読めます。広く許可してから機密の場所だけ外す書き方もできます
同じく権限を絞る考え方は、ほかの言語やCLIにも使えます。デプロイ系CLIの権限設計はClaude CodeでCloudflare Workersを開発する、Goの設定例はClaude CodeでGoアプリを開発する手順、型検査で確かめる流れはClaude CodeでNuxtアプリを開発するに書いています。
まとめ
Denoの権限は deno.json のタスクに固定し、Claude Codeには deno task の名前だけを渡す。これが二重設計の骨格です。弱点は2つあり、タスク自体の書き換えと、--allow-run や --allow-ffi の混入です。
前者は deno.json への Edit を ask にして防ぎ、後者は差分レビューで拾います。コマンド文字列のルールで防げない領域が気になる場合は、サンドボックスを3層目に足します。