Denoのパーミッション指定の勘所

DenoはNode.jsの後継JavaScriptランタイムです。

https://deno.land/

Denoの特徴の一つに、実行時の **--allow-xxxフラグを使ったパーミッション制御**があります。
具体的な例を挙げると、以下のようなものです。

- `--allow-read=/path/to/dir/`：指定したディレクトリを読む許可を与える
- `--allow-write=/path/to/dir/`：指定したディレクトリに書き込む許可を与える
- `--allow-net`：外部とHTTP通信する許可を与える
- `--allow-run=deno`：指定した子プロセスを実行する許可を与える
- `--allow-all` / `-A`：全て許可する

などなど。

これを実行時にコマンドラインで指定することで、プログラムに与える権限を指定することができます。

```shell
> deno run --allow-net ./main.ts
```

しかし、実行時に毎回こうしたフラグを指定するのは面倒という声も聞かれます。だからと言って`-A`フラグを毎回使うのは危険です。
この記事では、筆者がいつも使っているパーミッション指定をご紹介します。

## 安全な（だと筆者が考える）パーミッション

Denoのパーミッションのうち、筆者が普段から指定しているのは以下の3つです。

- `--allow-net`：ネットワークアクセスを許可
- `--allow-read=.`：カレントディレクトリ以下のファイルの読み取りを許可
- `--allow-write=.`：カレントディレクトリ以下のファイルへの書き込みを許可

`--allow-net`については、`fetch`でAPIを叩いたりサーバーを建てたりするときに必要です。`--allow-net=domain1,domain2,...`とすればHTTP通信する対象を制限することもできますが、開発環境では単に`--allow-net`のみ指定することが多いです。
※後段のread/write権限を絞っておけば、シークレット情報が読まれてどこかのサーバーに送信されることもないだろうという考え

`--allow-read=.`と指定すると、カレントディレクトリ以下のみに読み取り権限を与えることができます。カレントディレクトリはほとんどの場合プロジェクトルートになっており、プロジェクトフォルダ内のファイルさえ読めれば大半のユースケースがカバーできます。
こうしておけば`/etc/password/`や`~/.ssh/`が読み取られることもないですし、基本的に安全だと考えています。

`--allow-write=.`も同様に、カレントディレクトリ以下のファイルのみ書き込み権限を与えます。プロジェクトフォルダの外に書き込むことはそうそう無いはずで、これで事足ります。
もし悪意のあるプログラムがディスクに何かを書き込んだ場合でも、プロジェクトフォルダを消して`git clone`しなおせば済む話です。

もちろん実行するにあたって書き込み権限が必要ないときに、わざわざ`--allow-write=.`を指定する必要はありません（readやnetも同様）。必要最小限のパーミッションを与えます。


## 危険な（と筆者が考える）パーミッション

逆に筆者が極力指定することを避けているパーミッションもあります。

- `--allow-run`：子プロセス（＝サブコマンド）の実行を許可する
- `--allow-ffi`：FFIの実行を許可する
- `--allow-all` / -A`：すべての権限を与える

これらのパーミッションは危険なものです。というのも、これらのパーミッションは特権昇格される可能性があるからです。

詳しくは[マニュアル](https://deno.land/manual/getting_started/permissions)に書かれている通りです。

> Subprocesses are very powerful, and can be a little scary: they access system resources regardless of the permissions you granted to the Deno process that spawns them. The cat program on unix systems can be used to read files from disk. If you start this program through the Deno.run API it will be able to read files from disk even if the parent Deno process can not read the files directly. This is often referred to as privilege escalation.

> サブプロセスは非常に強力で、少し怖い場合があります。サブプロセスは、それらを生成するDenoプロセスに付与したアクセス許可に関係なく、システムリソースにアクセスします。 UNIXシステムのcatプログラムを使用して、ディスクからファイルを読み取ることができます。 Deno.run APIを介してこのプログラムを起動すると、親Denoプロセスがファイルを直接読み取ることができない場合でも、ディスクからファイルを読み取ることができます。 これは、特権昇格と呼ばれることがよくあります。

つまり、`--allow-read`フラグが与えられていなくても、catコマンドをサブプロセスで実行する許可さえあればファイルが読み取れてしまうという事です。もちろん書き込みや環境変数読み取りなどのあらゆる操作が可能になります。
これは`--allow-ffi`フラグでも同様です。

このように、`--allow-run` / `--allow-ffi` / `--allow-all`はプログラムに必要以上の権限を付与してしまうため、実行するプログラムが本当に信頼できるものである場合にのみ指定しています。

## deno.jsonでパーミッション管理

Denoには組み込みタスクランナーが存在します。ここにパーミッション指定を書いておけば、長いコマンドを書く必要はありません。

タスク定義は`deno.json`または`deno.jsonc`というファイルを作ってその中に書きます。
（拡張子がjsoncだとコメントが書けます。）

```jsonc:deno.jsonc
{
  "tasks": {
    // deno task serve
    "serve": "deno run --allow-net --allow-read=. ./serve.ts",
    // deno task test
    "test": "deno test --allow-net --allow-read=. --allow-write=."
  }
}
```

実行は`deno task <タスク名>`コマンドで行うことができます。

筆者の場合、例えばテスト用のコマンドは`deno task test`で走るように設定しておき、CIでも同じコマンドを叩くようにしています。

## まとめ

- 日常的に指定するパーミッション
  - `--allow-net`
  - `--allow-read=.`
  - `--allow-write=.`
- 信頼できるコードにのみ使うパーミッション
  - `--allow-run`
  - `--allow-ffi`
  - `--allow-all`
- `deno task`にパーミッションを含めたコマンドを設定しておくと便利

ところで、Node.jsで実行する場合は全てが`--allow-all`相当で実行されています。任意のディレクトリを読み書きし放題、つまりパスワード盗み放題なわけですが…

https://news.mynavi.jp/techplus/article/20211109-2181690/

気にせずNode.jsを使い続けている人も多いわけで、これは結局慣れの問題なのかもしれませんね。