at backyard

Color my life with the chaos of trouble.

Node.jsのコアモジュール名についている【node:】プレフィックスについて

ちょっと気になったので備忘録として書き残しておく。

Node.jsにある、node:というprefix

Node.jsでライブラリをimport/requireする際に node:というprefixをつけることが可能になっている。
これは Version 14.18.0 からついているもので、以下のように2種類の書き方ができるようになっている。

それぞれ記述は異なるが、コード自体は同じ内容であるため、どちらを使っても構わない。

# CJS
const fs = require("fs");
const fs = require("node:fs");
# ESM
import fs from "fs";
import fs from "node:fs";

node:testモジュールについて

ちなみにv18から入った node:test モジュールは node:をつけなければならない。
これは node: プレフィックスが必須となる最初のコアモジュールとなる。
(ちなみに node: をつけなかった場合、Node.js は代わりに test という名前のモジュールをユーザランドからロードしようとする)

nodejs.org

nodejs.org

node:プレフィックスがついたわけ

今回 node: プレフィックスがついた理由について調べていたところ、どうやら単純にコアモジュール側とユーザーランドの間の明確な境界を提供すること、というのが一番の理由だそうだ。
(間違いありましたらコメントください)

またname spaceとして node: を使うことで、新しいコアモジュールがより魅力的な名前で導入されることが期待される。

プレフィックスのみのコアモジュールが生まれることに対する懸念点

タイポスクワッティング攻撃(typosquatting)というのがある。 例えば

npm install <パッケージ名>

でインストールする際のパッケージ名をtypoすることを見越してtypoされることを狙ったモジュールを公開し、そこに悪意のあるコードを仕込んでおくという手法である。
(結構端折って説明しているので、正確な情報ではないかもしれません)

具体的な例としては、意図的にスペルを間違えた悪意のある expres パッケージを公開することで、 express をインストールしようとしているユーザをターゲットにすることなどが想定される。
ただ、npmにはタイポスクワッティングに対する保護機能が組み込まれており、このようなモジュールが公開されないように自動的にブロックされるようになっている。

まだNode.jsのコアモジュールの数は比較的少ないが、悪意のあるユーザーが将来的に node: プレフィックスに対して同様の攻撃を試みることは容易に想像できる。
Node.jsのユーザは、コアモジュールがnodeバイナリにコンパイルされているにもかかわらず、インストールしようとすることがあるという事実はすでにあるらしく、例えばnpm上の fs モジュールは、何の機能も含んでいないものの、毎週90万回以上ダウンロードされているらしい。

www.npmjs.com

(こうやって上記リンクから実際のダウンロード数を目の当たりにすると、なかなかに現実味が湧いてくる...)

Denoのnode:プレフィックスはまた別の話?

ところでDenoでもnode:プレフィックスを付けたモジュールのimportが可能である。

// main.mjs
import fs from 'node:fs'
console.log(fs);

実行は下記で可能。

deno run --compat --unstable main.mjs

この node: は先程上に書いたNode.jsと何か関係があるのか?と最初考えていたが、関係あるも何もDeno側からNode.js側のnode: プレフィックスがついているものを呼び出しているだけの話になるかと思われる。
(DenoではNode.jsとの互換モード的なものがあり、これはNode.js用に作成されたプログラムをDenoで実行できるようにするためにある。詳細は下記の公式ドキュメントを参照)

deno.land

あとは、上のドキュメントの下記あたりが node: に関連した挙動に影響ある箇所かしら。
(ここについては特に未検証です)

Import maps are supported in the same way if you used Deno without compatibility mode. When resolving "bare" specifiers Deno will first try to resolve them using import map (if one is provided using --import-map flag). Bare specifiers starting with node: prefix are extempt from this rule.

DenoでもこうやってNode.js互換が進められているのは嬉しい限りですね。

node互換のソースは下記から見えます。

github.com

ちょっと半端な知識で一部書いているところもあると思うので、間違い等ありましたらコメントくださいm( )m