TL;DR
Railsのcredentialsファイルに"0"や"0x"から始まる数字を設定すると、その値が取り出す際に異なる数字に変わってしまう問題が発生する。この原因はYAMLのフォーマットに由来しており、対策としては適切なエスケープを行う必要がある。
環境
Rails 7.1.3.2
ruby 3.3.0
何があったか
ある日、Railsのcredentialsファイルに次のような設定を行いました。
# credentials
service:
password: 01234
その後、この値を渡しても何故か認証が通らない。
理由を探るために、Railsコンソール(IRB)でこの値を取り出すと、以下のような結果が得られました:
irb(main)> Rails.application.credentials.service[:password]
=> 668
・・・?
設定したpasswordの値は01234でしたが、取り出すと668という異なる値になっています。
原因
わかる人にはすぐ分かる原因だと思うのですが、
この問題の主な原因は、Railsのcredentialsが内部的にYAMLフォーマットを使用していることにあります。YAMLでは、特定の文字列パターンを異なる進数として解釈します。
-
8進数
YAMLでは、"0"から始まる数字を8進数として解釈します。例えば、01234は8進数として解釈され、10進数では668になります。数値のみだったのも原因の一つです、0から始まっても数値以外の文字が含まれる場合は文字列として解釈されます。 -
16進数
また、YAMLは"0x"から始まる数字を16進数として解釈します。これは数値以外の文字を含んでいる場合も発生します。
例えば、以下の設定を行った場合
# credentials
service2:
password: 0x1A
この値を取り出すと、16進数の0x1Aは10進数の26として解釈されます。
irb(main)> Rails.application.credentials.service2[:password]
=> 26
対策
この問題を回避するためには以下のような対策が考えられます。
文字列として保存する"0"から始まる数字を保存する際には、必ず文字列として保存するようにします。例えば、01234を"01234"のようにダブルクォーテーションで囲みます。これにより、YAMLはこれを文字列として扱うため、数字の変換が発生しません。
# credentials
test:
password: "01234"
irb(main)> Rails.application.credentials.test[:password]
=> "01234"
まとめ
credencialsに限らずYAMLだったら発生するので知らないとびっくりしそう。