T-CREATOR

Deno で Permission Denied が出る理由と解決手順:--allow-\* フラグ総点検

Deno で Permission Denied が出る理由と解決手順:--allow-\* フラグ総点検

Deno でプログラムを実行したとき、突然 Permission Denied エラーが表示されて戸惑った経験はありませんか?

Node.js からの移行を考えている方や、Deno を使い始めたばかりの方にとって、このエラーは最初の大きな壁になることが多いです。しかし、この挙動こそが Deno の最大の特徴であり、セキュリティを守る重要な仕組みなんです。

本記事では、Deno で Permission Denied エラーが発生する理由を丁寧に解説し、--allow-* フラグの使い方を総点検していきます。初心者の方でも安心して読み進められるよう、具体的なエラーケースと解決手順を段階的にご紹介しますね。

背景

Deno のセキュリティファーストな設計思想

Deno は Ryan Dahl 氏(Node.js の生みの親)が、Node.js の設計上の反省点を踏まえて開発した、新しい JavaScript / TypeScript ランタイムです。

その中でも最も重要な設計思想の一つが「セキュリティファースト」でしょう。Node.js では、npm パッケージをインストールすると、そのパッケージは実行時に自由にファイルシステムへアクセスしたり、ネットワーク通信を行ったりできます。これは便利な反面、悪意あるコードが紛れ込んだ場合、システム全体が危険にさらされる可能性がありました。

デフォルトで全てを拒否する Permission モデル

Deno はこの問題に対して、根本的なアプローチを採用しています。

mermaidflowchart TB
    start["Deno プログラム起動"] --> check["Permission チェック"]
    check -->|"フラグなし"| deny["❌ Permission Denied"]
    check -->|"適切なフラグ"| allow["✅ 実行許可"]

    deny --> error["エラーで停止"]
    allow --> exec["処理実行"]

    exec --> file["ファイル読み書き"]
    exec --> net["ネットワーク通信"]
    exec --> env["環境変数アクセス"]

上図のように、Deno はデフォルトで全てのシステムリソースへのアクセスを拒否します。ファイルの読み書き、ネットワーク通信、環境変数へのアクセスなど、外部リソースを利用する際には、明示的に許可を与える必要があるのです。

この仕組みにより、実行するコードがどのようなリソースにアクセスするのかを、開発者が常に把握できるようになっています。

Node.js との決定的な違い

#項目Node.jsDeno
1デフォルト権限全て許可全て拒否
2ファイルアクセス自由--allow-read / --allow-write が必要
3ネットワーク通信自由--allow-net が必要
4環境変数自由--allow-env が必要
5セキュリティ意識後付け設計段階から組み込み

この表からもわかるように、Deno は最初からセキュリティを念頭に置いた設計になっているのですね。

課題

Permission Denied エラーの典型的なケース

Deno を使い始めると、さまざまな場面で Permission Denied エラーに遭遇します。以下は、初心者が最もよく遭遇する代表的なケースです。

ケース 1:ファイル読み込み時のエラー

最もシンプルなファイル読み込みでも、権限がないとエラーになります。

typescript// example.ts
const text = await Deno.readTextFile('./data.txt');
console.log(text);

このコードを実行すると、以下のエラーが発生します。

basherror: Uncaught (in promise) PermissionDenied: Requires read access to "./data.txt", run again with the --allow-read flag
    at async Object.readTextFile (ext:deno_fs/30_fs.js:667:18)
    at async file:///path/to/example.ts:1:14

エラーコード: PermissionDenied

エラーメッセージの意味: .​/​data.txt への読み取りアクセスが必要です。--allow-read フラグを付けて再実行してください。

ケース 2:ネットワーク通信時のエラー

外部 API を呼び出す際にも、同様に権限が必要です。

typescript// fetch-example.ts
const response = await fetch(
  'https://api.example.com/data'
);
const data = await response.json();
console.log(data);

実行時のエラー:

basherror: Uncaught (in promise) PermissionDenied: Requires net access to "api.example.com", run again with the --allow-net flag
    at async mainFetch (ext:deno_fetch/26_fetch.js:170:16)
    at async file:///path/to/fetch-example.ts:1:18

エラーコード: PermissionDenied

発生条件: ネットワーク通信を行う際に --allow-net フラグが指定されていない場合

ケース 3:環境変数アクセス時のエラー

環境変数を読み取る場合も、明示的な許可が必要です。

typescript// env-example.ts
const apiKey = Deno.env.get('API_KEY');
console.log(`API Key: ${apiKey}`);

実行時のエラー:

basherror: Uncaught PermissionDenied: Requires env access to "API_KEY", run again with the --allow-env flag
    at Object.getEnv [as get] (ext:runtime/30_os.js:86:10)
    at file:///path/to/env-example.ts:1:20

エラーコード: PermissionDenied

エラーメッセージ: API_KEY 環境変数へのアクセスが必要です。--allow-env フラグを付けて再実行してください。

Permission エラーが示すセキュリティの重要性

これらのエラーは一見不便に思えますが、実は非常に重要な役割を果たしています。

mermaidflowchart LR
    malicious["悪意あるコード"] --> try_read["ファイル読み取り試行"]
    malicious --> try_net["外部通信試行"]
    malicious --> try_env["環境変数読み取り試行"]

    try_read --> blocked1["❌ ブロック"]
    try_net --> blocked2["❌ ブロック"]
    try_env --> blocked3["❌ ブロック"]

    blocked1 --> safe["システム保護"]
    blocked2 --> safe
    blocked3 --> safe

上図のように、Permission モデルは悪意あるコードからシステムを守る強固な盾となります。サードパーティのモジュールを使用する際も、そのモジュールが実際にどのようなリソースにアクセスするのかを、フラグの指定を通じて明確にできるのです。

解決策

--allow-* フラグの完全リスト

Deno では、各種リソースへのアクセスを許可するために、複数の --allow-* フラグが用意されています。ここでは、全てのフラグを詳しく解説していきましょう。

ファイルシステム関連のフラグ

#フラグ説明使用例
1--allow-readファイル読み取りを許可deno run --allow-read script.ts
2--allow-writeファイル書き込みを許可deno run --allow-write script.ts

--allow-read の詳細

ファイルやディレクトリの読み取りアクセスを許可します。

bash# 全てのファイルへの読み取りを許可
deno run --allow-read main.ts
bash# 特定のディレクトリのみ許可(推奨)
deno run --allow-read=./data,./config main.ts
bash# カンマ区切りで複数指定可能
deno run --allow-read=/etc/hosts,./local.json main.ts

--allow-write の詳細

ファイルやディレクトリへの書き込みアクセスを許可します。

bash# 全てのファイルへの書き込みを許可
deno run --allow-write main.ts
bash# 特定のディレクトリのみ許可(推奨)
deno run --allow-write=./output,./logs main.ts

ネットワーク関連のフラグ

#フラグ説明使用例
1--allow-netネットワークアクセスを許可deno run --allow-net script.ts

--allow-net の詳細

HTTP/HTTPS 通信や TCP/UDP 通信を許可します。

bash# 全てのネットワーク通信を許可
deno run --allow-net main.ts
bash# 特定のドメインのみ許可(推奨)
deno run --allow-net=api.example.com,cdn.example.com main.ts
bash# ポート番号を指定して許可
deno run --allow-net=localhost:8000 main.ts

環境変数関連のフラグ

#フラグ説明使用例
1--allow-env環境変数アクセスを許可deno run --allow-env script.ts

--allow-env の詳細

環境変数の読み取りと設定を許可します。

bash# 全ての環境変数へのアクセスを許可
deno run --allow-env main.ts
bash# 特定の環境変数のみ許可(推奨)
deno run --allow-env=API_KEY,DATABASE_URL main.ts

システム関連のフラグ

#フラグ説明使用例
1--allow-runサブプロセス実行を許可deno run --allow-run script.ts
2--allow-ffi外部関数呼び出しを許可deno run --allow-ffi script.ts
3--allow-hrtime高精度時刻測定を許可deno run --allow-hrtime script.ts

--allow-run の詳細

外部コマンドやプログラムの実行を許可します。

bash# 全てのコマンド実行を許可
deno run --allow-run main.ts
bash# 特定のコマンドのみ許可(推奨)
deno run --allow-run=git,npm main.ts

--allow-ffi の詳細

FFI(Foreign Function Interface)を使用して、C/C++ などの外部ライブラリを呼び出すことを許可します。

bash# FFI の使用を許可
deno run --allow-ffi --unstable main.ts

--allow-hrtime の詳細

performance.now() などの高精度時刻測定 API の使用を許可します。タイミング攻撃を防ぐため、デフォルトでは制限されています。

bash# 高精度時刻測定を許可
deno run --allow-hrtime main.ts

万能フラグ(本番環境では非推奨)

#フラグ説明使用例
1--allow-all または -A全ての権限を許可deno run -A script.ts
bash# 開発時の一時的な使用のみ推奨
deno run --allow-all main.ts

# 短縮形
deno run -A main.ts

注意: --allow-all は開発時のデバッグには便利ですが、本番環境では使用しないでください。必要最小限の権限のみを付与することが、セキュリティのベストプラクティスです。

フラグの組み合わせ方

複数のフラグを組み合わせることで、より柔軟な権限設定が可能になります。

bash# ファイル読み取りとネットワーク通信を同時に許可
deno run --allow-read --allow-net main.ts
bash# 特定のディレクトリとドメインのみ許可
deno run \
  --allow-read=./data,./config \
  --allow-write=./output \
  --allow-net=api.example.com \
  main.ts
bash# 環境変数とサブプロセス実行を許可
deno run --allow-env=API_KEY --allow-run=git main.ts

deno.json による権限設定

毎回コマンドラインでフラグを指定するのは面倒ですよね。Deno 1.30 以降では、deno.json または deno.jsonc ファイルで権限を設定できます。

json{
  "tasks": {
    "dev": "deno run --watch main.ts",
    "start": "deno run main.ts"
  },
  "permissions": {
    "read": ["./data", "./config"],
    "write": ["./output", "./logs"],
    "net": ["api.example.com", "cdn.example.com"],
    "env": ["API_KEY", "DATABASE_URL"]
  }
}

上記の設定で、deno task start を実行すると、自動的に指定した権限が適用されます。

bash# deno.json の権限設定を使用
deno task start

この方法なら、チーム全体で統一した権限設定を共有できますね。

具体例

例 1:ファイル操作アプリケーション

ログファイルを読み込んで、エラー行を抽出し、別ファイルに保存するプログラムを作成してみましょう。

ステップ 1:プログラムの作成

typescript// log-analyzer.ts

// ログファイルのパス
const LOG_FILE = './logs/app.log';
const ERROR_FILE = './logs/errors.log';
typescript// ログファイルを読み込む
async function readLogFile(path: string): Promise<string> {
  try {
    const content = await Deno.readTextFile(path);
    return content;
  } catch (error) {
    console.error(
      `ファイル読み込みエラー: ${error.message}`
    );
    throw error;
  }
}
typescript// エラー行を抽出する
function extractErrors(logContent: string): string[] {
  const lines = logContent.split('\n');
  return lines.filter(
    (line) =>
      line.includes('ERROR') || line.includes('FATAL')
  );
}
typescript// エラー行をファイルに書き込む
async function writeErrorFile(
  path: string,
  errors: string[]
): Promise<void> {
  try {
    const content = errors.join('\n');
    await Deno.writeTextFile(path, content);
    console.log(
      `${errors.length} 件のエラーを ${path} に保存しました`
    );
  } catch (error) {
    console.error(
      `ファイル書き込みエラー: ${error.message}`
    );
    throw error;
  }
}
typescript// メイン処理
async function main() {
  console.log('ログ解析を開始します...');

  // ログファイルを読み込む
  const logContent = await readLogFile(LOG_FILE);

  // エラー行を抽出
  const errors = extractErrors(logContent);

  // 結果をファイルに保存
  await writeErrorFile(ERROR_FILE, errors);

  console.log('解析が完了しました');
}

// プログラム実行
main();

ステップ 2:権限なしで実行(エラー発生)

bashdeno run log-analyzer.ts

実行結果

basherror: Uncaught (in promise) PermissionDenied: Requires read access to "./logs/app.log", run again with the --allow-read flag

エラーコード: PermissionDenied

原因: ファイルの読み取り権限がない

ステップ 3:読み取り権限のみで実行(再度エラー)

bashdeno run --allow-read=./logs log-analyzer.ts

実行結果

bashログ解析を開始します...
error: Uncaught (in promise) PermissionDenied: Requires write access to "./logs/errors.log", run again with the --allow-write flag

エラーコード: PermissionDenied

原因: ファイルの書き込み権限がない

ステップ 4:正しい権限で実行(成功)

bashdeno run --allow-read=./logs --allow-write=./logs log-analyzer.ts

実行結果

bashログ解析を開始します...
15 件のエラーを ./logs/errors.log に保存しました
解析が完了しました

成功しましたね!この例から、必要な権限を段階的に特定していく流れがわかります。

例 2:API データ取得と保存

外部 API からデータを取得して、JSON ファイルとして保存するプログラムを作成します。

ステップ 1:プログラムの作成

typescript// fetch-and-save.ts

// API エンドポイント
const API_URL =
  'https://jsonplaceholder.typicode.com/users';
const OUTPUT_FILE = './data/users.json';
typescript// API からデータを取得
async function fetchUsers(url: string): Promise<unknown> {
  try {
    console.log(`API からデータを取得中: ${url}`);
    const response = await fetch(url);

    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`);
    }

    return await response.json();
  } catch (error) {
    console.error(`API 取得エラー: ${error.message}`);
    throw error;
  }
}
typescript// データをファイルに保存
async function saveToFile(
  path: string,
  data: unknown
): Promise<void> {
  try {
    const jsonString = JSON.stringify(data, null, 2);
    await Deno.writeTextFile(path, jsonString);
    console.log(`データを ${path} に保存しました`);
  } catch (error) {
    console.error(`保存エラー: ${error.message}`);
    throw error;
  }
}
typescript// メイン処理
async function main() {
  // データを取得
  const users = await fetchUsers(API_URL);

  // ファイルに保存
  await saveToFile(OUTPUT_FILE, users);

  console.log('処理が完了しました');
}

main();

ステップ 2:正しい権限で実行

このプログラムには、ネットワーク通信とファイル書き込みの両方が必要です。

bashdeno run \
  --allow-net=jsonplaceholder.typicode.com \
  --allow-write=./data \
  fetch-and-save.ts

実行結果

bashAPI からデータを取得中: https://jsonplaceholder.typicode.com/users
データを ./data/users.json に保存しました
処理が完了しました

特定のドメインとディレクトリのみに権限を絞ることで、安全性を保ちながら機能を実現できていますね。

例 3:環境変数を使用した設定管理

環境変数から設定を読み込み、データベースに接続するようなケースを見てみましょう。

ステップ 1:プログラムの作成

typescript// db-config.ts

// 環境変数から設定を読み込む
interface DatabaseConfig {
  host: string;
  port: number;
  database: string;
  username: string;
  password: string;
}
typescript// 環境変数の読み込みと検証
function loadConfig(): DatabaseConfig {
  const host = Deno.env.get('DB_HOST');
  const port = Deno.env.get('DB_PORT');
  const database = Deno.env.get('DB_NAME');
  const username = Deno.env.get('DB_USER');
  const password = Deno.env.get('DB_PASSWORD');

  // 必須項目の検証
  if (
    !host ||
    !port ||
    !database ||
    !username ||
    !password
  ) {
    throw new Error('必要な環境変数が設定されていません');
  }

  return {
    host,
    port: parseInt(port, 10),
    database,
    username,
    password,
  };
}
typescript// 設定を表示(パスワードは隠す)
function displayConfig(config: DatabaseConfig): void {
  console.log('データベース設定:');
  console.log(`  ホスト: ${config.host}`);
  console.log(`  ポート: ${config.port}`);
  console.log(`  データベース: ${config.database}`);
  console.log(`  ユーザー名: ${config.username}`);
  console.log(
    `  パスワード: ${'*'.repeat(config.password.length)}`
  );
}
typescript// メイン処理
function main() {
  try {
    const config = loadConfig();
    displayConfig(config);
  } catch (error) {
    console.error(`設定エラー: ${error.message}`);
    Deno.exit(1);
  }
}

main();

ステップ 2:環境変数の設定

bashexport DB_HOST=localhost
export DB_PORT=5432
export DB_NAME=myapp
export DB_USER=admin
export DB_PASSWORD=secret123

ステップ 3:特定の環境変数のみ許可して実行

bashdeno run \
  --allow-env=DB_HOST,DB_PORT,DB_NAME,DB_USER,DB_PASSWORD \
  db-config.ts

実行結果

bashデータベース設定:
  ホスト: localhost
  ポート: 5432
  データベース: myapp
  ユーザー名: admin
  パスワード: *********

必要な環境変数のみを明示的に許可することで、意図しない環境変数の漏洩を防げます。

例 4:deno.json を活用した実践的な設定

実際のプロジェクトでは、deno.json を使って権限を管理するのが効率的です。

プロジェクト構成

bashmy-deno-project/
├── deno.json
├── src/
│   ├── main.ts
│   ├── api.ts
│   └── file.ts
├── data/
│   └── input.json
└── output/
    └── result.json

deno.json の設定

json{
  "tasks": {
    "dev": "deno run --watch src/main.ts",
    "start": "deno run src/main.ts",
    "test": "deno test --allow-read=./data"
  },
  "permissions": {
    "read": ["./data", "./src"],
    "write": ["./output"],
    "net": ["api.example.com", "cdn.example.com"],
    "env": ["API_KEY", "NODE_ENV"]
  },
  "imports": {
    "@/": "./src/"
  }
}

タスクの実行

bash# 開発モード(ファイル監視付き)
deno task dev
bash# 本番実行
deno task start
bash# テスト実行
deno task test

この設定により、コマンドラインでフラグを指定する必要がなくなり、チーム全体で一貫した権限管理が可能になります。

Permission エラーのトラブルシューティングフロー

mermaidflowchart TD
    error["Permission Denied エラー発生"] --> read_msg["エラーメッセージを確認"]

    read_msg --> identify["必要な権限を特定"]

    identify --> file_read{"ファイル読み取り?"}
    identify --> file_write{"ファイル書き込み?"}
    identify --> network{"ネットワーク通信?"}
    identify --> env_var{"環境変数?"}
    identify --> subprocess{"サブプロセス実行?"}

    file_read -->|Yes| add_read["--allow-read を追加"]
    file_write -->|Yes| add_write["--allow-write を追加"]
    network -->|Yes| add_net["--allow-net を追加"]
    env_var -->|Yes| add_env["--allow-env を追加"]
    subprocess -->|Yes| add_run["--allow-run を追加"]

    add_read --> specific1["特定のパスのみ許可?"]
    add_write --> specific2["特定のパスのみ許可?"]
    add_net --> specific3["特定のドメインのみ許可?"]
    add_env --> specific4["特定の変数のみ許可?"]
    add_run --> specific5["特定のコマンドのみ許可?"]

    specific1 -->|Yes| narrow_read["--allow-read=./path"]
    specific2 -->|Yes| narrow_write["--allow-write=./path"]
    specific3 -->|Yes| narrow_net["--allow-net=domain"]
    specific4 -->|Yes| narrow_env["--allow-env=VAR"]
    specific5 -->|Yes| narrow_run["--allow-run=cmd"]

    specific1 -->|No| broad_read["--allow-read"]
    specific2 -->|No| broad_write["--allow-write"]
    specific3 -->|No| broad_net["--allow-net"]
    specific4 -->|No| broad_env["--allow-env"]
    specific5 -->|No| broad_run["--allow-run"]

    narrow_read --> rerun["プログラムを再実行"]
    narrow_write --> rerun
    narrow_net --> rerun
    narrow_env --> rerun
    narrow_run --> rerun
    broad_read --> rerun
    broad_write --> rerun
    broad_net --> rerun
    broad_env --> rerun
    broad_run --> rerun

    rerun --> success{"成功?"}
    success -->|Yes| done_check["完了"]
    success -->|No| check_more["他の権限も必要?"]
    check_more --> identify

上図のフローに従えば、Permission エラーを体系的に解決できます。

まとめ

Deno の Permission Denied エラーは、最初は戸惑うかもしれませんが、理解すればセキュリティを保つ強力な味方になります。

重要なポイント

本記事でご紹介した内容を、以下にまとめます。

Permission モデルの基本

  • Deno はデフォルトで全てのリソースアクセスを拒否します
  • 必要な権限は --allow-* フラグで明示的に付与します
  • この仕組みにより、悪意あるコードからシステムを保護できます

主要な --allow-* フラグ

#フラグ用途ベストプラクティス
1--allow-readファイル読み取り特定ディレクトリのみ許可
2--allow-writeファイル書き込み特定ディレクトリのみ許可
3--allow-netネットワーク通信特定ドメインのみ許可
4--allow-env環境変数アクセス必要な変数のみ許可
5--allow-runサブプロセス実行特定コマンドのみ許可
6--allow-all全権限付与開発時のみ使用

実践的な使い方

  • フラグは組み合わせて使用できます
  • deno.json で権限を一元管理すると便利です
  • 権限は必要最小限に絞るのがセキュリティのベストプラクティスです

エラー解決の基本手順

  1. エラーメッセージから必要な権限を特定する
  2. 該当する --allow-* フラグを追加する
  3. 可能な限り、特定のパス・ドメイン・変数のみを許可する
  4. 動作確認後、deno.json に設定を記載する

Deno の Permission モデルは、一度慣れてしまえば、より安全なコードを書く習慣が自然と身につきます。

Node.js からの移行を検討されている方も、この仕組みを理解すれば、Deno の魅力をより深く感じられるはずです。最初は Permission Denied エラーに戸惑うかもしれませんが、それは Deno があなたのシステムを守ってくれている証なのですね。

ぜひ本記事を参考に、安全で堅牢な Deno アプリケーションを開発してみてください。

関連リンク