npmjs.com で著名ソフトウェアによく似た名前のマルウェアが大量に発見された

Malicious packages in npm. Here’s what to do | Ivan Akulov’s blog

People found malicious packages in npm that work like real ones, are named similarly real ones, but collect and send your process environment to a third-party server when you install them

訳:

悪意のあるパッケージがnpmで発見された。それらは、実際のパッケージによく似た名前で同じように動くが、パッケージのインストール時にプロセスの環境変数を外部のサーバに送信する。

発見されたパッケージの一覧は元エントリをどうぞ。このようなマルウェアである偽パッケージの一例をあげると、 babelcli, d3.js, ffmepg, jquery.js, mysql.js, openssl.js などです。これらのパッケージが依存関係にある場合、tokenなどの秘匿値を再生成すべきとのこと。

package.jsonに書いた直接の依存関係だけでなく、 依存ツリーのすべて を調べる必要があります。つまり、 あなただけでなく依存ツリー中の誰かがミスって偽パッケージを使っただけであなたの環境が攻撃対象になる ということです。依存関係は npm ls などで調べられます。

ライブラリ開発者としては、 scoped packages を使うのがよさそうだと元エントリでは述べています。

所感

オンラインでコードを配布・使用する以上、どんなパッケージリポジトリでもマルウェアを配布することはできます。つまり、npmjs.com (npm) だけの問題ではありません。

一方で、ある機能を実現するための別のパッケージが非常に沢山あります。たとえば、 zstdのnode binding だけでもパッと見て4つくらいあります。これは、npmのユーザー(=JSの開発者)が他の言語にくらべて桁違いに多いため、車輪の再発明が頻繁におきるからではないかと想像しています。

また、しばしば同じライブラリに対応する複数の配布パッケージがあります。一例をあげると、lodash は単体で lodash (commonjs modules版) と lodash-es (ES modules版)という配布パッケージがあり、またlodashの提供する関数ごとに配布パッケージが提供されています (たとえば lodash.merge packageなど)。このような状況だと、たとえば react-es というパッケージを作って「ReactのES modules版です!」というdescriptionを設定すると、いかにもそれっぽく見せかけられます。つまり、著名ソフトウェアに便乗してマルウェアを配布することが簡単にできてしまいます。

この問題はなかなか難しくて対処療法ではない本質的な改善をどうしたらいいのかは分からないのですが、さしあたり自分の管理するライブラリに関しては、 scoped packages にして配布するしかないかなという気がします。

追記

npmjs.org の配布パッケージの中身を公式ウェブインターフェイスで見ることができない、というのもnpmの問題のひとつと言えるかもしれません。

…できないですよね? 例:

たとえば metacpan.org は配布パッケージの中身をウェブで確認できます。例:

rubygems.org も配布パッケージの中身をウェブでは確認できませんね。例:

追記(2)

  • 元エントリに確認用ワンライナがあり、今回発見された分に関していえば確認することはできます
    • 元エントリが更新される可能性を考慮してここには引用しません
  • 公式見解でました: npm Blog Archive: `crossenv` malware on the npm registry

ghq+pecoでターミナル作業が捗るぞ

便利なソフトウェアを定期的に掘り起こすぞ活動です。

ghq は「GitHub repoのclone先を統一することでいろいろ便利にできるコマンド」で、github repoのclone先を、カレントディレクトリに依存せず ~/.ghq/github.com/$owner/$repo/ にします。

使い方:

ghq get -p --shallow $URL

peco は、テキストのリストをgrepしてそれに対してなにかコマンドを起動するみたいなやつで、ほかのツールと組み合わせて使います。

ghq + peco

.zshrc などにエイリアスを作っておきます。

alias g='cd $(ghq list --full-path | peco)'
alias b='hub browse $(ghq list | peco | cut -d "/" -f 2,3)'
alias v='code $(ghq list --full-path | peco)'

これで、 g でghqでcloneした任意のrepoにcdできます。

bv は同様に、ターゲットのリポジトリをブラウザで開いたりvscodeを立ち上げたりできます。

See Also

ActionArgsが素晴らしい件 #Rails

github.com

Railsのcontrollerで違和感があるのって

  • actionのinputに params というインスタンスメソッド経由でアクセスすること
    • しかも params はviewからアクセスできる!
  • actionのoutputが controller のインスタンス変数への代入であること
    • しかもそのインスタンス変数はviewからアクセスできる!

というところだと思うんですよ。

なぜなら我々は「メソッドの引数でinputを受け取りメソッドの戻り値をoutputとすべし」ということを是としてコードを書いてるわけじゃないですか。リーダブルコードを読むまでもなく、変数のスコープは狭ければ狭いほどメンテナンスしやすいリーダブルなコードだというベストプラクティスを正しいものとしてコードを書いているわけじゃないですか。

そういうベストプラクティスに真っ向から反しているのが現在のRailsのcontrollerのあり方なのです。controllerとviewで params から自由自在にパラメータを引き出して、特定のcontrollerのアクションがどういうパラメータを取りうるかさっぱりわからないということになりがちです。 この params のスコープの広さはまるでグローバル変数のようです。

ActionArgsは、リクエストパラメータのうち params でアクセスするパラメータをRubyのメソッドシグネチャで表現できるようにするgemです。

ActionArgsのもとでは、controllerはたとえばこんな感じになります:

class UsersController < ApplicationController
  def index(page: '1', per: '20')
    @users = User.page(page).per(per)
  end

  def show(id:)
    @user = User.find(id)
  end
end

#index が任意パラメータとしてpageper をとり、それらのデフォルト値がそれぞれ1と20であること、show が必須パラメータとして id をとるということが、メソッドのシグネチャをみるだけで一目でわかりますね。すばらしい。もっとも、 #create#update の場合はパラメータのデータ構造がネストするのでそれほど自明ではありませんが、それでもないよりはずっとマシです。スコープの広すぎる params を使わなくて済むのですから。

個人的にはActionArgsがRails組み込みでほしいですが、まずは誰もが使う標準的なgemにならないとなと思ってこのエントリを書きました。

実際の使い方は action_args/README を読むのがよいと思います。ただし、常にキーワード引数を使うべきです。READMEでも触れられていますが、非キーワード引数の場合デフォルト引数の扱いに制約があるからです。

ActionArgs では達成できないことも多いんですが、その場合でも「メソッドの引数でinputを受け取りメソッドの戻り値をoutputとすべし」ということを念頭に置きつつcontrollerを実装するようにしています。

RailsのURL helpersをTypeScriptとしてexportするts_routesを書いた

カッとなって作りました。後悔はしてません。

github.com

/entries/:id を生成するためにRailsのviewで entry_path(42) などとしますが、それをTypeScriptからも Routes.entryPath(42) などとして使えるようにするためのgemです。

READMEにあるように rake task を定義して適当なパスに routes.ts を生成するという想定です。sprocketsには対応してません。

Rails URL heplersのJSへのエクスポートというとrailsware/js-routes があって、これをずっと使っていてしばらくは十分だったのですが、js-routesの生成するコードには型情報がありません。フロントエンドのコードベースをTypeScript化したいまとなっては、開発中にしばしばtypoして実行時エラーになるのが不満でした。

ts_routesであれば生成されるコードがTypeScriptなので型情報があり、ヘルパー名の補完も効き、TypeScriptらしい快適さでRailsのURL helpersを使えます。

RubyMineでローカル変数やブロック引数にYARD型アノテーションをつける

RubyMine 2017.1 Help :: Using Annotations にあるとおりなんですが、

local variables:

# @type [String]
my_var = magic_method

# @type my_var [String]
my_var = magic_method

# @type [String] my_var
my_var = magic_method

# @type [String] my_var Add some documentation here
my_var = magic_method

block parameters:

method_with_block do
# @type [String] param1
# @type [Range] param2
| param1, param2 |
# some code...
end

ということです。

これでローカル変数でもコード補完がうごく!!

f:id:gfx:20170614142942p:plain

とはいえプロダクションコードに残すかどうかは微妙です。開発中の助けにはなりますが、真面目に指定するとさすがに煩雑すぎるので。

メソッドの引数や戻り値はYARDの型アノテーションを指定する価値があると思いますけどね。

markdownの拡張記法の現状

Kibelaは次のようにいくつかmarkdownを拡張しています。

そして、今後もそういう拡張は増えていくと思われます。

PlantUML

KibelaのPlantUML記法はこういうやつです。

    ```plantuml
    Bob -> Alice : hello
    Alice -> Bob : Go Away
    ```

GitLabも同じ構文でPlantUMLをサポートしていますね。

PlantUML & GitLab - GitLab Documentation

crowiも最近PlantUML記法をサポートしはじめましたね。構文はKibelaとおなじです。

Support PlantUML by sotarok · Pull Request #231 · crowi/crowi · GitHub

esa.io もUML記法という名前でPlantUMLをサポートしていて、こちらは plantuml ではなく uml というキーワードです。

ReleaseNotes/2016/10/22/UML記法に対応しました - docs.esa.io

問題としては、fenced code blocksでいいの?といいうところです。「markdownとして解釈されずにコードを表示する」というfenced code blocksのセマンティクスを完全に無視していますからね。

具体的な弊害としても、PlantUMLそのもののソースコードをsyntax highlightを効かせて埋め込むことができないというものがあります。

MathJax / TeX

こちらは様々なmarkdown処理系が思いおもいに拡張していて、 だいたい Math in MarkDown · cben/mathdown Wiki · GitHub にまとまっています。

CommonMarkでも2014年くらいから議論されていますが進捗は芳しくなさそうです。

MathJax extension for LaTeX equations - Extensions - CommonMark Discussion

ざっくりまとめると、fenced code blocksを使うスタイルと $$ ... $$\( ... \) などの独自記法を定義するスタイルがあります。

fenced code blocksの問題はPlantUMLとおなじです。つまり、セマンティクスを無視していいのかという一貫性の問題と、syntax highlightとの衝突の問題です。 もっとも、TeX記法に関していうと math キーワードを使う事が多いようなので、TeX (LaTeX) とのsyntax highlightの衝突はそれほど問題ではありません。

独自記法は大きな問題です。 $$ はshellやPerlのpidを示す特殊変数なので、普通に文中に出現する可能性があります。また、markdownは \ をメタ文字として扱っているので、 \(\[ というトークンはmarkdown parserレベルで ([ にしてしまいます。つまり、真面目にやるならmarkdown parser自体を拡張するか、markdown parserに与える前に処理をする必要があります。これらはいずれにしても実装コストが高いので避けたいところです。github/cmarkのtable拡張 をみても、パーサの拡張はわりとしんどい感じがします。

R Markdown

R Markdown というものがあって、ツールとしてはJupiter Notebookみたいなものだと認識していますが、markdown中にRを書けて実行もできるというものです。

構文がちょっとおもしろくて、fenced code blocksなんですが明らかに拡張だなとわかります。

    ```{r}
     1 + 1
    ```

RだけじゃなくPythonやbashなどもいけるようですね。

{r INCLUDE = FALSE} のように、code blockに対して引数を与えることもできるようです。

個人的には、この構文はわりと使いやすいようにみえます。つまり

  • fenced code blocksの拡張だがそれほどセマンティクスを侵害していないようにみえる(主観)
  • foo に対する {foo} で「実行」を示すので、syntax highlightと衝突しない
  • fenced code blocksの中はmarkdownの干渉をうけないのでmarkdown parserの拡張が不要

もちろんデメリットもあって

  • インライン記法がないことで、文中にLaTeXで数式を書きたいというニーズにこたえられない
  • Kibelaに限って考えるとしても、これまでやってきた拡張と互換性がない
  • CommonMarkとしてのコンセンサスはない

という感じです。CommonMark的にどうかというのは気持ちの問題なので無視してもいいですが、他のデメリットもそこそこ大きいんですよね。

Generic Directives /Plugins in CommonMark

汎用的な拡張記法の提案で、CommonMarkで議論中のものです。

Generic directives/plugins syntax - Extensions - CommonMark Discussion

ざっとしか眺めてませんが、あまり芳しくなさそうです。スレ主の提案はこんな感じです。

::: name [inline-content] {key=val}
contents, which are sometimes further block elements
:::

悪くなさそうにみえますが、2014年の議論でいままとまっていないということは…。

あまりまとまりはないですが、とりあえず現状のメモでした。

See Also

gfx.hatenablog.com

TypeScriptのDefinitelyTypedは「ダメでもともと、うまく使えればラッキー」くらいの距離感がよい

自前でTypeScript型定義ファイル(dts)を用意していないJSライブラリのための型定義ファイル集があります。

https://github.com/DefinitelyTyped/DefinitelyTyped

npmで @types/react みたいなのがそうです。

これは便利なものですが、ライブラリ作者ではない第三者が作っていることがほとんど(作者にやる気があればライブラリ本体で管理するほうが圧倒的によいので!)なので、型定義が間違ってることが普通にあります。

直近だと、 @types/react-intl の型定義が間違っているので、これを導入するとコンパイルが通らなくなるみたいな事情がありました。

一応PRは送りましたが、これがマージされても別に使う必要ないんじゃないかと思ってます。

というのも「第三者が型定義ファイルを作り気合で追従していく」という仕組み上、どうやってもTypeScriptで書いたライブラリと比較して型情報の精度は落ちるからです。DefinitelyTypedはあまり信頼できないものと割り切るしかありません。

そして、現在は TypeScript compilerの --allowJs フラグ(あるいは tsconfig.jsonallowJs option)によって *.js をimportできるようになっています。

おそらくもう少し時間がたつと の --allowJs が賢くなり、素のJSでもある程度型を推論してよしなにしてくれるようになると思われます。

つまり、

  • DefinitelyTypedは過渡期の技術であり、平均的な品質は低く、将来的には廃れると思われる
  • @types/* にこだわる必要はなく、便利そうなら使ってもいいが型定義なしでもまったく問題ない

くらいの気持ちで付き合うと気が楽でいいかなと思います。

rougeのlexer開発ワークフロー

github.com

digdag の構文を追加するにあたって、Ruby製syntax highlighterであるところのRougeのlexerの開発方法を調べました。

基本

Rouge lexerはDSLで定義します: https://github.com/jneen/rouge#using-the-lexer-dsl

lexerはステートマシンとして振る舞うので、ただのキーワードマッチより遥かに強力で文脈を考慮した字句解析を行えます。

tokenの種類は token.rb に定義されているので、実際の出力はこれを見ながらふさわしいtoken typeを当てていくことになります。

また、lexerは継承もできます。たとえばdigdagファイルの構文はYAMLとほぼ互換性があるので、YAML lexerを継承することにしました。

そのなかで、カスタマイズしたいstateに prependappend でルールを追加することでlexerの拡張ができます。

例: https://github.com/jneen/rouge/pull/674/files#diff-801af56a286b5cd63a0e58c8cdad9867R64

テスト実行

lexerについて試行錯誤するときは、bin/rougify highlight でコンソールでハイライトを見れます。

たとえば今回であれば、以下のコマンドで確認しつつ開発をすすめました。

bundle exec bin/rougify highlight -m application/x-digdag -L debug=0 lib/rouge/demos/digdag

結果:

f:id:gfx:20170603153140p:plain

デバッグ実行

-L debug=1 を渡すと、デバッグモードで実行できます。ステートの遷移やどのルールにマッチしてどういうtokenを生成したかという情報を見れます。

digdag run (local mode) で並列実行数を制御する

とある分散バッチシステムでdigdagを導入してみています。

www.digdag.io

スケジューラ機能などは使っておらず、タスクを良い感じに並列実行するためのmakeよりちょっと便利なツール、くらいの感じで使ってます。

digdagは下記のようにloopなどを _parallel: true を指定するだけで並列実行できるのがいいですね。

timezone: UTC

+repeat:
  loop>: 3
  _parallel: true
  _do:
    +run:
      sh>: "echo ${i}"

+teardown:
  echo>: finish ${session_time}

ところで、このこの並列実行数を制御する方法がわからなかったのでコマンドラインオプションで渡せるようにpull-requestしました。 digdag v0.9.13 からたぶん使えます。

add --max-task-threads to `digdag run` by gfx · Pull Request #572 · treasure-data/digdag

digdag run --max-task-threads N foo.dig などとしてつかえます。まあ、このPRを出したあとに -X agent.max-task-threads=N でも指定できるとわかったのでちょっと冗長ですが、他のサブコマンド(server, scheduler)との一貫性もあるので存在意義はあるでしょう。

max-task-threadsの数によってloop taskの全体の結果はかわりません。たとえば --max-task-threads 1 のときにloopの最初のtaskが失敗すると、loopの他のタスクがすべて実行されて、loop taskそれ自体は失敗します。

検証コード:

timezone: UTC

+repeat:
  loop>: 3
  _parallel: true
  _do:
    +run:
      sh>: "if [ ${i} == 1 ] ; then false; else echo ${i}; fi"

+teardown:
  echo>: finish ${session_time}

結果:

$ digdag run --rerun mydag.dig
2017-05-28 10:35:31 +0900: Digdag v0.9.10
2017-05-28 10:35:32 +0900 [WARN] (main): Reusing the last session time 2017-05-28T01:00:00+00:00.
2017-05-28 10:35:32 +0900 [INFO] (main): Using session /Users/gfx/repo/mydag/.digdag/status/20170528T010000+0000.
2017-05-28 10:35:32 +0900 [INFO] (main): Starting a new session project id=1 workflow name=mydag session_time=2017-05-28T01:00:00+00:00
2017-05-28 10:35:33 +0900 [INFO] (0017@[0:default]+mydag+repeat): loop>: 3
2017-05-28 10:35:34 +0900 [INFO] (0017@[0:default]+mydag+repeat^sub+loop-0+run): sh>: if [ 0 == 1 ] ; then false; else echo 0; fi
2017-05-28 10:35:34 +0900 [INFO] (0020@[0:default]+mydag+repeat^sub+loop-1+run): sh>: if [ 1 == 1 ] ; then false; else echo 1; fi
2017-05-28 10:35:34 +0900 [ERROR] (0020@[0:default]+mydag+repeat^sub+loop-1+run): Task failed with unexpected error: Command failed with code 1
(snip)
0
2017-05-28 10:35:34 +0900 [INFO] (0021@[0:default]+mydag+repeat^sub+loop-2+run): sh>: if [ 2 == 1 ] ; then false; else echo 2; fi
2
2017-05-28 10:35:35 +0900 [INFO] (0021@[0:default]+mydag^failure-alert): type: notify
error:
  * +mydag+repeat^sub+loop-1+run:
    Command failed with code 1 (runtime)

なので、 max-task-threads の数は純粋にリソースの都合で調整すればよい、ということになります。

なぜTypeScript推しなのか

KibelaのフロントエンドをES2015からTypeScriptに絶賛移行中です。

www.typescriptlang.org

で、なぜ flow じゃないくてTSなのかって話です。

flow vs typescriptである理由は、どちらもJSのスーパーセットをうたう静的型付きのaltJSだからです。この時代にあえてaltJSを導入する理由としては静的型があるというのが必須で、かつ学習コストを考えるとJSのスーパーセットであるのが望ましいでしょう。

言語仕様

言語仕様の点から言うと、決定的な差はないと思っています。

メリットもだいたい同じで

  • 生産性: エディタの補完をJSよりも賢くできるので、より少ない脳のワーキングメモリでコードを書ける
  • 堅牢性: コンパイル時に(=多くのケースではエディタで)typoなどの間違いを検出できるのでバグを減らせる
  • 学習コスト: JSをベースにしており、実行もほぼJSなのでほぼモジュールシステムと型システムだけを学べばよい

検索しやすさ・コミュニケーションのしやすさ

検索しやすさは圧倒的にTypeScriptです。

flowは検索しにくいのはもちろんのこと、その名前の元になったと思われるflow analysis との混同が激しく、flowやTypeScriptの文脈で口頭で「flow解析が云々」といってもだいたいflowのことだと勘違いされて通じません。

ファイルタイプに対する誠実さ

flowの拡張子は .js、TypeScriptの拡張子は .ts です。

拡張子でファイルタイプを判断することの多いこの世界線においては、flowがJavaScriptと同じ拡張子で互換性のない言語として書かせるのは、ファイルタイプに対する誠実さに欠けているのではないかと思っています。

もちろんマーケティング的に「flowは言語ではない。flowはJavaScriptである」と主張することは、心理的な敷居を下げるという意味では一定の効果があるかもしれません。しかし、その辺にあるコードをJSだと思って眺めているとコードハイライトエンジンやエディタが構文エラーを指摘してくる、というのは気持ちのいい体験ではありません。

開発言語

flowはOCamlです。

TypeScriptはTypeScriptです(つまりセルフホスティング)。

TypeScript処理系をJS(あるいはTS)からライブラリとして使用できるというのは大きなメリットです。

まとめ

以上です。言語としては決定的な差はないものの、それをとりまく環境はTypeScriptのほうが好ましいと考えています。ゆえに私はTypeScriptを推します。