読者です 読者をやめる 読者になる 読者になる

ヤルキデナイズド

Unclassified Articles on Software and IT

npm パッケージの unpublish に関するゴタゴタの大まかなまとめ

(最終更新:3月24日16:50ごろ)

事件の流れ

何が問題だったのか

npm の仕様

  • パッケージの所有者がパッケージを unpublish できるようになっていること
    • メジャーなパッケージマネージャの多くは unpublish に相当する操作をユーザーに提供しない
    • 代わりとして unlist (NuGet)yank (Cargo) といった操作を提供することがある
    • unlist/yank されたパッケージはリポジトリの一覧には表示されなくなるが、依存解決中にそのパッケージをダウンロードすることは可能
    • 本当の緊急時のみリポジトリ管理者の手でパッケージが削除されることはある
  • unpublish されたパッケージのネームスペースを他人が乗っ取れるようになっていること
    • 同一バージョンの再 publish はできないものの、別のバージョンで悪意のあるコードを publish すると依存元パッケージを攻撃できる可能性がある

人的要因

  • 代理人と作者のやりとりにおいてお互いの態度がよろしくなかったこと
  • 法務関係の事態とはいえ、 npm 運営が作者の許可を得ずパッケージを unpublish したパッケージの所有権を移し替えたこと
    • npm のパッケージ名に関する紛争解決ポリシーによれば、ユーザー間で問題が解決できなければ運営が調停するとされる
  • 抗議のためとはいえ、作者が多数のパッケージを一度に unpublish したこと
    • 混乱を招くことは承知していたようだが、 left-pad に依存する babel まで壊れることは予測していなかったと思う
  • npm 運営が社内の合意もおろそかに unpublish の取り消しを実行したこと

関連記事

余談:

  • 一般的なパッケージマネージャのつくりに興味がある人にはこの記事がおすすめ。 Go のパッケージマネージャを新規設計する視点から問題領域などを分析してる:So you want to write a package manager — Medium
    • “パッケージ管理は厄介な領域だ。本当に厄介だ。表面上は純粋に技術的に解決可能と見える。そう考えて取り組んだ者は皆、避けがたい次なる結論に至る:ソフトウェアはおそろしい。人々はおそろしい。シナリオは無数にあり、何ひとつまともには動かない。証明可能なことに、何ひとつまともには動かない。人生とは逆巻くカオスとエントロピーの渦の中の無意味な摂動。”

感想

  • JS 界隈に単機能モジュールを多数組み合わせて使う風潮があるため被害が広がったなという感じ
  • unpublish 操作はユーザーに開放しないほうがいいと思う
    • npm は他のパッケージリポジトリと比べてパッケージ数が桁違いに多いため、 npm 社としては unpublish を依頼制にしたくないんだろうけど
    • パッケージ作者に無断で管理者がパッケージを削除することは、法に反するコンテンツを含む場合などはやむを得ないだろうが、今回のケースは微妙
  • Objective-C/Swift のパッケージマネージャ CocoaPods は Pods ディレクトリ(依存先パッケージのソースコードを集約するディレクトリ)をバージョン管理することを推奨している。こうすると万が一依存先パッケージが削除されても影響を受けない。デメリットもあるがよいプラクティスだと思う

ブコメ返信

id:teppeis

今回のケースではunpublishが禁止されても乗っ取りは防げるけどエコシステムの破壊は免れなかった。iOS詳しく知らないけどCocoaPodsはプラットフォーム限定できてビルド不要という違いがあるのでは。

ユーザーによる unpublish が禁止されていたとしたら、管理者権限での kik の unpublish によって依存関係が壊れることこそ免れないものの、その他多数のパッケージの突然の unpublish は為されず、被害は比較的小さく済んだはず。

CocoaPods で Pods ディレクトリのバージョン管理が現実的なのは、 Pods ディレクトリにパッケージのソースコードだけが保存されるから。ビルドは必要だが生成物は別のディレクトリに出力される。

ソースコードとプラットフォーム依存のバイナリを同じディレクトリに保存する npm や Ruby の Bundler などでは真似できなそう。

Mac の Safari のキャッシュから画像その他を取り出すメモ

(以下の内容は Safari 8.0.6 現在のもの)

~/Library/Caches/com.apple.Safari/fsCachedData に blob が保存されている。 file コマンドで画像かどうか判別して適当に拡張子をつけてやれば復元できる。

% file fsCachedData/000DFAD8-B02E-47A2-AFA0-36D985C47D20
(略)/000DFAD8-B02E-47A2-AFA0-36D985C47D20: PNG image data, 64 x 64, 8-bit/color RGBA, non-interlaced

Finder で blob ファイルを選ぶと上手いことプレビューしてくれるのでそれでもいい。

f:id:uasi:20150619104639p:plain

その他レスポンスデータなどは ~/Library/Caches/com.apple.Safari/Cache.db に記録されている。これはただの SQLite3 データベースファイル。 BLOB カラムには binary plist らしきものが入っている。

% sqlite3 Cache.db 'SELECT * FROM cfurl_cache_schema_version;'
104

% sqlite3 Cache.db .schema
CREATE TABLE cfurl_cache_schema_version(schema_version INTEGER);
CREATE TABLE cfurl_cache_response(entry_ID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,     version INTEGER, hash_value INTEGER, storage_policy INTEGER, request_key TEXT UNIQUE,   time_stamp NOT NULL DEFAULT CURRENT_TIMESTAMP, partition TEXT);
CREATE TABLE cfurl_cache_blob_data(entry_ID INTEGER PRIMARY KEY, response_object BLOB, request_object BLOB,               proto_props BLOB, user_info BLOB);
CREATE TABLE cfurl_cache_receiver_data(entry_ID INTEGER PRIMARY KEY, isDataOnFS INTEGER, receiver_data BLOB);
CREATE INDEX request_key_index ON cfurl_cache_response(request_key);
CREATE INDEX time_stamp_index ON cfurl_cache_response(time_stamp);
CREATE INDEX proto_props_index ON cfurl_cache_blob_data(entry_ID);
CREATE INDEX receiver_data_index ON cfurl_cache_receiver_data(entry_ID);

cfurl_cache_response.request_key は URL の文字列。ただし末尾に '[' + request_host + ']' が付属している場合がある。

cfurl_cache_receiver_data.receiver_data は UUID の文字列(大文字ハイフン区切りの形式)。 fsCachedData ディレクトリに UUID を名前とするバイナリファイルが格納されている。

はてなブログのプロフィールに Qiita と Twitter のリンクを追加する

サイドバーのプロフィールを便利にしたかったのでしてみた。サイドバーには任意のリンクを表示する項目も追加できるが、場所を取るので今回は使わなかった。

f:id:uasi:20150331193721p:plain

このようにはてなIDの横に Qiita と Twitter のIDを並べることにした。プロフィール部分はユーザーがカスタマイズできるようになっていないため、 JavaScript でガッと書き換える。

はてなブログのダッシュボードを開き、「設定>詳細設定>headに要素を追加」に以下のコードを書いた。

<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet">
<script type="text/javascript" src="//code.jquery.com/jquery-2.1.3.min.js"></script>
<script type="text/javascript">
// <!--
jQuery.noConflict();
jQuery(function() {
  jQuery('.hatena-module-profile a.hatena-id-link').after(
    ' / <a href="http://qiita.com/uasi">' +
    '<span class="fa-stack" style="font-size: 50%">' +
    '<i class="fa fa-square fa-stack-2x"></i>' +
    '<i class="fa fa-search fa-stack-1x fa-inverse fa-2x"></i>' +
    '</span>' +
    'uasi</a>' +
    ' / <a href="https://twitter.com/uasi">' +
    '<i class="fa fa-twitter"></i>' +
    'uasi</a>'
  );
});
// Qiita icon is adapted from http://qiita.com/hkusu/items/fda8d8178dd693f95f3c
// -->
</script>

うまくいったので満足。

なお Font Awesome の既存アイコンを重ねて Qiita のロゴ風にする技はFontAwesome - Font Awesome で Qiitaロゴっぽいアイコンを表現 - Qiitaを参考にさせていただいた。

Safari に Flash Player をインストールできないときはオフラインインストーラを使うとよさそう

SafariFlash Player プラグインをアップデートしろとせっつくのでインストールしようとしたが、何度やってもエラーが出る。

f:id:uasi:20150130103810p:plain

いろいろ試してもダメだったので別のページからインストーラをダウンロードしたらあっさりインストールできた。

Adobe Flash Playerの配布 | Adobe

↑このページの “DMGインストーラーのダウンロード”(.dmg 直リンク)からダウンロードした。

今回インストールできなかったのはバージョン16だったが、バージョン12か13のときもインストールできず、その次のバージョンのベータ版をインストールした覚えがある。

Adobe のフォーラムのスレッドを見るに、管理者権限のないユーザーがオンラインインストーラを使うと発生する問題のようだ。ベータ版はオフラインインストーラなのでうまくいったということか。


環境:

いろいろ試してダメだったこと:

  • Safari 以外のアプリケーションもすべて終了
  • Flash Player プラグインをアンインストール
  • ディスクユーティリティでアクセス権を修復
  • インストーラをディスクイメージから取り出してデスクトップに置く(→実行するとダイアログが空になる不具合が出た)

インストールに失敗したときのログ( ~/Library/Logs/FlashPlayerInstallManager.log ):

2015-01-30 10:35:23 +0900 [I]  IM: ---------- log start ----------
2015-01-30 10:35:23 +0900 [I]  IM: All install checks pass
2015-01-30 10:35:23 +0900 [W]  IM: Unexpected umask value for process: 0
2015-01-30 10:35:23 +0900 [I]  IM: User does not have any processes that need to be closed.
2015-01-30 10:35:23 +0900 [I]  IM: [install started]
2015-01-30 10:35:23 +0900 [E]  RA: Unable to launch FPInstallHelper. Error = 'launch path not accessible'

2015-01-30 10:35:23 +0900 [E]  RA: 
Attributes for '/var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/T/FPInstallHelper':
 {
    NSFileCreationDate = "2015-01-23 21:28:30 +0000";
    NSFileExtensionHidden = 0;
    NSFileGroupOwnerAccountID = 0;
    NSFileGroupOwnerAccountName = wheel;
    NSFileHFSCreatorCode = 0;
    NSFileHFSTypeCode = 0;
    NSFileModificationDate = "2015-01-23 21:28:30 +0000";
    NSFileOwnerAccountID = 0;
    NSFileOwnerAccountName = root;
    NSFilePosixPermissions = 2541;
    NSFileReferenceCount = 1;
    NSFileSize = 22784;
    NSFileSystemFileNumber = 31520882;
    NSFileSystemNumber = 16777220;
    NSFileType = NSFileTypeRegular;
}

2015-01-30 10:35:23 +0900 [E]  RA: 
Attributes for '/var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/T':
 {
    NSFileCreationDate = "2014-10-05 06:07:38 +0000";
    NSFileExtensionHidden = 0;
    NSFileGroupOwnerAccountID = 0;
    NSFileGroupOwnerAccountName = wheel;
    NSFileModificationDate = "2015-01-30 01:35:23 +0000";
    NSFileOwnerAccountID = 0;
    NSFileOwnerAccountName = root;
    NSFilePosixPermissions = 449;
    NSFileReferenceCount = 19;
    NSFileSize = 646;
    NSFileSystemFileNumber = 19103629;
    NSFileSystemNumber = 16777220;
    NSFileType = NSFileTypeDirectory;
}

2015-01-30 10:35:24 +0900 [E]  IM: [install failed: 30]
2015-01-30 10:35:24 +0900 [E]  IM: Install failed with error code: 30.
2015-01-30 10:35:24 +0900 [I]  IM: ----------  log end  ----------

.dev で終わる hostname のとき gem install が(ある条件で)失敗するようになった

追記:うまい要約を見つけたので貼っておく。


最近 .dev TLD が新設された影響か、 hostname が .dev で終わる手元のマシンで gem install するとコケるようになった:

$ hostname
foo.dev
$ sudo /opt/rbenv/versions/2.2.0/bin/gem install bundler --no-document
ERROR:  While executing gem ... (Gem::RemoteFetcher::FetchError)
    Errno::ECONNREFUSED: Connection refused - connect(2) for "your-dns-needs-immediate-attention.dev" port 443 (https://your-dns-needs-immediate-attention.dev/quick/Marshal.4.8/bundler-1.7.11.gemspec.rz)

具体的には次のいずれかの条件を満たす環境で再現する:

  • hostname が .dev で終わり、かつ /etc/resolv.conf の search フィールドが設定されていない
  • /etc/resolv.conf の search フィールドが dev を含む

1番目の条件は Ruby の Resolv ライブラリの挙動に由来する。 /etc/resolv.conf に search が設定されていないとき、 Resolv は hostname の TLD を search の値として扱うようになっている(resolv.rb の1010行目あたりを参照)。また RubyGems の名前解決の挙動とも関係する(この記事のコメントを参照)。

この問題を回避するには /etc/resolv.conf に適当な search hoge fuga を設定すればよい。ただし hoge fuga は dev を含まないこと。

search を設定できない事情があるなら hostname を変える。 TLD を含まないようにするかテスト用の TLD を使う。 RFC 2606 によれば、テスト用途に使える TLD は予約済みの4つのうち .test.example の2つである。このどちらかを使えばよい。

あるいは .x などの1文字の TLD が使えるかもしれない。仕様上は正しいが、これまでに1文字の TLD が登録されことはない。

参考:


ブコメ返信

(gem の source を FQDN にすればいいという指摘について) id:happy_siro こういうこと?

$ sudo /opt/rbenv/versions/2.2.0/bin/gem sources list
*** CURRENT SOURCES ***

https://rubygems.org./

だめっぽい。

$ sudo /opt/rbenv/versions/2.2.0/bin/gem install bundler --no-document
ERROR:  While executing gem ... (Gem::RemoteFetcher::FetchError)
    Errno::ECONNREFUSED: Connection refused - connect(2) for "your-dns-needs-immediate-attention.dev" port 443 (https://your-dns-needs-immediate-attention.dev/quick/Marshal.4.8/bundler-1.7.11.gemspec.rz)

Streem 言語の雑感

matz が Streem なるスクリプト言語をデザインしているということで、パッと見で思いついたことをメモしておく:

  • UNIX の pipe っぽいストリーム操作を中心に据えた言語
  • catSTDIN | STDOUT と書けるらしい
  • 型が欲しい
  • (Functional?) Reactive Programming と相性が良さそう
    • 時間的に変化するイベントを取り扱える?
  • 型が欲しい
  • 例外状態の伝搬をどう扱う?
    • UNIX pipe みたいに繋いだプロセスのどれかが異常終了したら全体の処理が失敗するのでは困る
  • 文字列とかいうリニアな記号表現で分岐したストリームを表現するのがまず難しい疑惑
  • いっそ Haskellneedle みたいに ASCII アートで書くのはどうだ
{-# LANGUAGE QuasiQuotes #-}

fNeedle :: (Int, Int, Int) -> (Int, Int, Int, Int)
fNeedle = [nd|
    }=={(+1)}=\==========================>
              \
    }===\     \             /============>
        \     \             /
    }=) \ (==={uncurry div}=/={negate}===>
        \
        \=={(*2)}========================>
|]

型が欲しい。

Yosemite で AquaSKK の入力モードが切り替わり続ける問題を適当に直した

追記:この修正が取り込まれたバージョンのバイナリはここから入手できる↓

AquaSKK(OS X yosemite対応版) - みずぴー日記


YosemiteAquaSKK を使うと、 Safari などに入力するとき入力モードが高速で切り替わり続けるバグが出ることがある。

理屈は分かってないがそれっぽい箇所を適当に直したらバグが直ったっぽいので一応メモしておく。他への影響は追っていないので知らない。無保証。

修正済みのフォーク:uasi/aquaskk · GitHub

修正点:platform/mac/src/server/SKKInputController.mm#L159-L169あたりを if (activated_) { activated_ = NO; ... } で囲む。

    } else {
        // 個々の入力モードを選択している場合
        SKKEvent param;

        // ex) "com.apple.inputmethod.Roman" => SKK_ASCII_MODE
        param.id = [menu_ convertIdToEventId:(NSString*)value];

        if(param.id != InvalidInputMode) {
            session_->HandleEvent(param);

            modeIcon_->SelectInputMode([menu_ convertIdToInputMode:(NSString*)value]);
        }
    }

    } else {
        if (activated_) {
            activated_ = NO;
            // 個々の入力モードを選択している場合
            SKKEvent param;

            // ex) "com.apple.inputmethod.Roman" => SKK_ASCII_MODE
            param.id = [menu_ convertIdToEventId:(NSString*)value];

            if(param.id != InvalidInputMode) {
                session_->HandleEvent(param);

                modeIcon_->SelectInputMode([menu_ convertIdToInputMode:(NSString*)value]);
            }
        }
    }