Claude Media
Claude CodeでDenoを開発する — deno taskと--allowの二重の権限設計

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-fast

2行目以降は deno test を直接呼ぶ形です。許可ルールに deno task test しか入れていない場合は、確認が入ります。承認する前に、付いているフラグを見ます。

使い分けの目安

場面Claude Code側Deno側
lint・型チェック・テストClaude Code側deno task を allowDeno側タスクに --no-prompt と絞った --allow-*
外部APIを叩くスクリプトClaude Code側個別タスク名を allowDeno側--allow-net=ホスト名 と --allow-env=変数名
その場限りの deno runClaude Code側ask で毎回確認Deno側確認画面でフラグを読む
deno.json の変更Claude Code側Edit を askDeno側差分で権限フラグの増減を見る
信頼できないコードの実行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層目に足します。

この記事を共有:XはてブLinkedIn