ファイルに含まれるAPIトークンやパスワードなどの機密情報を見つけるSecretlint v6をリリースしました。

Secretlintは secretlint "**/*"のようにglobやファイルを指定して実行すると、ファイルに含まれる機密情報を見つけて標準出力に表示します。 一方で、SecretlintはESLintやtextlintなどと違って、--fixといった自動修正はできません。 なぜなら、APIトークンなどを見つけて消しても、ただ単に壊れるだけだからです。

そのため、見つけた機密情報は報告して、ユーザーがそれを手動で修正する必要があります。 たとえば、ソースコードにハードコードするのではなく、環境変数などで受け取るように変更するといった修正をします。

多くの場合、機密情報を見つけても自動修正はできないのですが、.bash_history.zsh_historyに残ってしまった機密情報は自動的に削除したりマスキングしても問題ないことに気づきました。 ヒストリーファイルに残ったAPIトークンを自動修正するために、Secretlint v6では --format=mask-result というフォーマッターが追加されています。

使い方: .zsh_historyの機密情報をマスキングする

secretlintパッケージ自体にはルールは含まれていませんが、@secretlint/quick-startパッケージには推奨ルールが同梱されているので、@secretlint/quick-startを使ったやり方を紹介します。 推奨ルールには、GitHub、Stripe、Slack(webhookも含む)、AWS、GCP、npm、SendGrid、秘密鍵、Basic認証などの検出ルールが含まれています。

どんなものが検知できるかは、ブラウザで試せるページがあります。

Dockerでも利用できますが、HOMEディレクトリのファイルはDockerでは扱いにくいので、Node.js版を利用します。

次のコマンドで.zsh_historyに機密情報が含まれているかをチェックできます。 (.bash_historyもファイル名を変更するだけで、チェックできます)

$ npx @secretlint/quick-start ~/.zsh_history

~/.zsh_history
  9178:25  error  [GITHUB_TOKEN] found GitHub Token(GitHub personal access tokens): ghp_wWPw5k4aXcaT4fNP0UcnZwJUVFk6LO0pTEST  @secretlint/secretlint-rule-preset-recommend > @secretlint/secretlint-rule-github

✖ 1 problem (1 error, 0 warnings)

もし含まれていたら、エラーが報告されます。 この機密情報をマスキングする前に、念のために .zsh_history のバックアップを取ります。

$ cp ~/.zsh_history ~/.zsh_history.bak

--format=mask-resultを使って、~/.zsh_historyの機密情報をマスキングした結果を標準出力に出して確認できます。

$ npx @secretlint/quick-start ~/.zsh_history --format=mask-result

...
: 1672748457:0;GITHUB_TOKEN="****************************************" gh issue list
...

問題なかったら、--outputオプションを使って、マスキングした結果で~/.zsh_historyを上書きします。 (<command> > ~/.zsh_historyでの上書きはしないように)

$ npx @secretlint/quick-start ~/.zsh_history --format=mask-result --output ~/.zsh_history

最後に、バックアップしておいた~/.zsh_history.bakを削除します。

$ rm ~/.zsh_history.bak

モチベーション

先月、PyTorchのPyTorch-nightlyに対してDependency Confusionを使ったサプライチェーン攻撃がありました。

pipは社内リポジトリ(レジストリ)とPyPIに同じパッケージ名があった場合に、PyPIを優先してインストールします(厳密にはindex-urlextra-index-urlよりも優先する)。 そのため、PyTorch-nightlyが依存していてPyPIにはまだなかったパッケージと同じ名前で、悪意あるコード仕込んでPyPIにアップロードしておくと、PyTorch-nightlyをインストール時にそのマルウェアを自動でインストールさせられる問題です。 この問題は、Dependency Confusionと呼ばれています。

npmなどでも同様の問題があります。

JFrog ArtifactoryなどのPrivateなレジストリとPublicなレジストリを併用すると、名前は同じだけどレジストリによって中身が異なるものが発生しやすいです。このDependency Confusionへの対応は次の記事にまとまっていますが、レジストリを混在させない、パッケージ名に名前空間を使う、ロックファイルを使うなどの対策を組み合わせる話があります。

今回のPyTorch-nightlyでのマルウェアには、環境変数やシステム情報、/etc/hosts/etc/passwd.ssh/などに加えて、$HOME/*にあるファイルをアップロードして盗み出すという処理が入っていたようです。

.ssh/ディレクトリは、1PasswordなどでSSHキーを管理することで、ファイルを読み取る権限だけでは盗めなくなります。

~/.config/*~/.awsなどにcredentialが生のテキストとして保存されてることが多いです。 また、HOMEディレクトリではありませんが、.envには機密情報が入ってる可能性が高いので、こういったマルウェアには狙われやすいと思います。 これらは1Password Shell Pluginsop runzenvenvchainなどを使って、ファイルに生のトークンなどを保存しないようにできます(これについてはまた今後書きます) 。

それ以外にも、HOMEディレクトリにcredentialが入りやすいものとして、.zsh_history.bash_historyなどのシェルの履歴ファイルがあります。 履歴ファイルを全部消すのは抵抗があったり、一方で目視チェックするのも面倒なので、secretlintを使って自動でチェックするようにすれば良さそうと思ったのがきっかけです。

Secretlintには--fixのような自動修正の仕組みはありませんが、エラー結果を表示するフォーマッターの実装を工夫すれば、疑似的な自動修正が実装できるなと思って--format=mask-resultというフォーマッターを実装しました。

実際にやってみたら何個か見つかったので、マスキングして履歴から消しました。