DenoにはURLを使用して直接外部ライブラリを取り込める機能が備わっています。
import { DB } from "https://deno.land/x/sqlite/mod.ts";
確かにこのコードだけを見ると、
- URLが変わったら使えなくなりそう
- 悪意のあるコードが紛れ込みそう
- Go言語の悪しき文化を引き継いでしまった
- 書きづらそう
という意見が出てきそうです(Twitterで見た)。
しかし、https://github.com/denoland/deno/blob/main/SECURITY.md で解説されている「Denoのセキュリティモデル」を読むと、どうやらそうでもないようです。
結局、ネットワークからコードをダウンロードして実行するのは他と変わらない
上で紹介したコードを見て、「ネットワークからダウンロードしたコードをそのまま実行するなんて危険だ」と思った方もいらっしゃるかもしれません。
しかし考えてみると、npmだろうが、他の言語のパッケージマネージャだろうが、ネットワークからダウンロードしたコードをそのまま実行していることには変わりないわけです。
npm i <packagename>
コマンドを打つと、npmのレジストリからコードがダウンロードされます。deno cache <url>
コマンドを打つと、URLからコードがダウンロードされます。両者は同じことをやっています。
また、npmは中央集権的なレジストリです。npmが改ざんされた場合、全てのパッケージに影響が出ます。一方Denoは分散レジストリ(=どんなURLでもimport可能)ですから、レジストリ改ざんの影響を最小限に抑えることができます。
つまり、DenoのURL importはnpmより安全です。
ブラウザと同じセキュリティモデル
Denoには実行時に権限を指定する仕組みが備わっています。例えば実行時に--allow-read=.
フラグを付与するとファイル読み込みが許可されます。
また、先ほど紹介した「Denoのセキュリティモデル」のページでは、「Denoで実行される全てのコードは信頼できないものとみなされる」とあります。
「信頼できないインターネット上のコードを、サンドボックス上で実行する」というのは、ブラウザと全く同じセキュリティモデルになっています。
つまり、Denoでコードを実行するのは、ブラウザでWebページを開きJavaScriptを実行するのと同じことです。(もしこれがセキュリティ上危険だというなら、あなたはブラウザを閉じてWebページの閲覧をやめる必要があります。)
依存関係が深くなりがちなJavaScriptでは特に、このメリットが大きいです。
同じプログラムをNode.jsとDenoの両方で実行するとします。
Node.jsの場合はプログラムが環境変数、ローカルファイル、ネットワークにアクセスし放題です。もしパッケージが改ざんされてしまったら、パスワードが盗まれ放題です。
一方Denoの場合は権限を制限することができます。悪意のあるコードが紛れ込んでも、読み書きできるディレクトリを制限したり、接続できるネットワークを制限しておけば、被害を最小限に抑えることができます。
このように、URL importしたコードは、「実行するコードは信頼できないものとみなす」「必要最小限の権限を指定する」という2つの条件で、安全に実行することができます。
逆に、--allow-all
を指定して全ての権限を許可して実行するのは大変危険です。ブラウザでWebページを閲覧する時に、ローカルファイルや環境変数へのアクセスを開放しているようなものです。絶対やめたほうがいいと思います。
deno.landに公開されたモジュールは不変
left-pad問題
npmでは過去にleft-pad問題が発生しました。1つのパッケージがnpmから削除されたため、それに依存していた多くのパッケージが壊れたという事件です。
事の発端はnpmの管理者がパッケージを勝手に削除したことでした。
このような事態を防ぐため、Denoでは「deno.landに一度公開されたモジュールは何があっても削除されない」という決まりになっています。
Can I edit or remove a module on deno.land/x?
Module versions are persistent and immutable. It is thus not possible to edit or delete a module (or version), to prevent breaking programs that rely on this module. Modules may be removed if there is a legal reason to do (for example copyright infringement).
(翻訳)deno.land/xのモジュールを編集または削除できますか?
モジュールのバージョンは永続的で不変です。したがって、このモジュールに依存するプログラムの破損を防ぐために、モジュール(またはバージョン)を編集または削除することはできません。法的な理由(著作権侵害など)がある場合は、モジュールを削除できます。
https://deno.land/x
つまり、deno.landに公開されているモジュールを使っておけば、
- 管理者が既存のモジュールを削除したり
- 作者がキレてモジュールを公開停止したり
といった事が起こる可能性は、ありません。
URLがバージョン管理されている
deno.landを始め、ほぼ全てのレジストリがバージョン管理をサポートしています。
import { DB } from "https://deno.land/x/sqlite@v3.3.0/mod.ts";
// ^^^^^^^
そもそもDenoはnode_modulesの肥大化問題とは無縁なため、semverを使ってバージョンを解決する必要はありません。特定のバージョンをピンポイントでimportすることができます。
このため、colors.js, faker.js事件のような問題が発生するのを防ぐことができます。
colors.js, faker.js事件
npmで過去に起こった事件として、colors.js, faker.js事件も紹介します。
これは、悪意のあるコードを仕込んだライブラリを作者が意図的にパッチリリースし、そのライブラリに依存している多数のコードが壊れたという事件です。
この問題で仇となったのはsemverによるバージョン解決です。作者が悪意のあるコードをパッチリリースでリリースしたため、自動でsemverによるアップデートが行われ、動作確認されていないバージョンのコードが紛れ込んだのです。
そもそもsemverを使っていなければ、悪意のあるバージョンが勝手に依存関係に入り込むこともなかったでしょう。
Denoの場合はURLがバージョン管理されており、semverを使わず特定のバージョンを指し示すことができるため、動作未確認のバージョンが勝手に入ってしまうことはありません。
import文は補完が効く
Denoのlanguage serverを使うと、画像のように、import文のURLに対して謎のパワーで補完が効きます。
ターミナルに行ってnpm install
するよりも手軽であり、URLの入力が面倒だと感じたことはありません。
(補完用のAPIサーバーが建っており、裏側で上手いこと処理してくれるらしいです。)
まとめ
- 「ネットワークからコードをダウンロードして実行する」という挙動は他のパッケージマネージャと一緒
- 分散レジストリで改ざん防止に繋がる
- ブラウザと同様のセキュリティサンドボックス
- もしレジストリが改ざんされたとしても、権限指定で被害を最小限に
- deno.landに公開されたモジュールは不変
- 「もしURLの中身が変わったらどうするの?」→変わりません
- URLがバージョン管理されている
- semverによって勝手にアップデートされることがない
- URL importに補完が効く
- 便利
以上から、DenoのURL importは安全だと言えます。
ただし、正しい権限指定(--allow-xxx
フラグ)は必要です。権限指定を間違えると、危険な状態でコードが実行されることがあります。
追記:なぜDenoはURL importを選択したか
ブラウザがURL importを使っているからです。
詳しいことはこのissueで解説されています。
Denoの思想として、可能な限りブラウザと挙動を合わせるというものがあります。
かつてNode.jsにrequire
を導入したことでブラウザのimport
と構文レベルで互換性の問題が生じ、今日まで混乱が起こっていることを、作者は発表で「後悔」のうちの一つだと言っています。
そういう経緯もあり、DenoはWeb標準から外れる構文や機能については一切導入しないというスタンスを取っているようです。