sops の暗号文・鍵・平文はどこに残るか

結論 sops のファイルは、値の暗号文を 1 つだけ持つ。受け取り手 (age の公開鍵) ごとの封筒と MAC は、同じファイルの sops メタデータに入る .sops.yaml が効くのは新規の暗号化だけ。既存ファイルの受け取り手は sops updatekeys を実行するまで変わらない 暗号化されるのは値とコメントで、キー名は平文で残る。MAC は既定で、暗号化していない値も覆う 復号鍵は 1 か所を選んで読むのではない。環境変数と ~/.config/sops/age/keys.txt など、見つかった鍵を全部足して試す 復号した平文は exec-env / exec-file で子プロセスへ渡せる。リポジトリに平文は残らないが、direnv_load は一時ファイルを経由し、同じユーザーのプロセスからは読める 以下は sops 3.13.3 / age v1.3.2 / direnv 2.37.1 で確かめた。 ソースの参照先は github.com/getsops/sops/v3@v3.13.3。 前提: 値はデータ鍵で 1 回だけ暗号化し、データ鍵を人数分の封筒に入れる sops はファイルを新しく暗号化するときにランダムなデータ鍵を 1 つ作り、値をそのデータ鍵で AES-256-GCM 暗号化する。 データ鍵は受け取り手それぞれの公開鍵で暗号化し (封筒)、ファイル末尾のメタデータに recipient (公開鍵) と enc (封筒) の組で並べる。 復号する側は、自分の秘密鍵で開けられる封筒を探してデータ鍵を取り出し、値を復号する。 データ鍵はファイルごとに 1 つで、sops edit で保存しても作り直さない。 編集の前後で封筒 (enc) は変わらず、触っていない値の暗号文も 1 文字も変わらなかった。変わるのは書き換えた値の暗号文だけ。 データ鍵を作り直すのは sops rotate だけ ( 後述 )。 ...

2026-10-06 ·  2026-10-06 · 8 分 · 1611 文字

gin の binding タグをすり抜ける値と経路

結論 gin が実行時に読む検証タグは binding だけ。validate タグはコンパイルも lint も通り、検証は一度も走らない 検証は ShouldBind* の中でだけ、デコード直後の値に掛かる。c.JSON はレスポンスを検証せず、ハンドラで trim した後の値も検証しない required は「ゼロ値でない」の検査で、型によって意味が変わる。値型は "" / 0 / false を弾き、ポインタとスライスは nil だけを弾く (&"" と [] は通る) タグは左から評価され、最初に落ちたタグ 1 つだけが返る。required,min=1 の int に 0 を送ると、返るのは min でなく required required は値の形式を見ない。URL なら net/url で分解して、scheme と host を許可リストと完全一致で比べる 以下は Go 1.26.4 / gin v1.12.0 / go-playground/validator v10.30.1 で実測した。 前提: gin は go-playground/validator をタグ名 binding で呼ぶ gin の ShouldBindJSON / ShouldBindQuery は、デコードした struct を go-playground/validator に渡す。 validator 本体の既定タグ名は validate だが、gin は初期化時にタグ名を binding へ差し替えている。 ...

2026-10-05 ·  2026-10-05 · 5 分 · 972 文字

Go の Firestore SDK で消える項目と通らない値

結論 Go の Firestore SDK は、struct とドキュメントの間で項目を消しても、読み飛ばしてもエラーを返さない 書く側: Set は文書を丸ごと置き換える。serverTimestamp の項目は非ゼロだと書かれないので、読んで詰め直した Set で作成日時が消える。MergeAll は map 専用で、map の中の struct は丸ごと置き換わる。omitempty は false / 0 / ゼロ時刻を消し、struct は消さない 読む側: DataTo は struct に無いキーを捨てる。null を string や int に読んでも何もしない。int には整数で表せる double は入り、文字列は入らない 通らない値: ID・日時・クエリの値の規則を、SDK は一部しか検査しない。ID の規則違反は読みと書きで違うエラーになり、不正な UTF-8 を where に渡すと期限まで返ってこない 書き込みの経路ごとに、エミュレータで実際の文書を読んで確かめる 以下は cloud.google.com/go/firestore v1.26.0 / Firestore エミュレータ v1.22.0 / Go 1.26.4 で実測した。 本番の Firestore では試していない。 前提: 書き込み API ごとに書く範囲が違う ...

2026-10-03 ·  2026-10-03 · 7 分 · 1463 文字

tmux サーバが握り続ける状態とエラーなしに通るコマンド

結論 tmux のキーバインド・オプション・@ 変数・環境変数はサーバのメモリにある。.tmux.conf はサーバ起動時に 1 回流すコマンド列で、行を消して source-file しても登録済みのものは消えない ペイン・popup・run-shell・if-shell のプロセスは、操作中のシェルではなくサーバの環境を継承する。後から足した PATH は届かず、if-shell 'command -v ...' は偽になってブロックを警告なしに捨てる popup とコマンド付きのペインのシェルは非ログイン・非対話で、zsh なら .zshenv しか読まない 存在しない -c のパス、解決できない display-message -t、. や : を含むセッション名は、どれもその場ではエラーにならない (終了コード 0)。ずれは後で別の場所に出る 確かめるときは -L <名前> -f /dev/null で隔離したサーバを使う。-L だけだと ~/.tmux.conf を読む 以下は tmux 3.7c / zsh 5.9.2 で実測した。コード例はすべて -L demo の使い捨てサーバに対して打つので、普段のサーバには触れない。 前提: tmux コマンドはサーバへの依頼でしかない tmux はクライアント・サーバ型になっている。端末で打つ tmux ... はクライアントで、ソケット越しにサーバへコマンドを送って終わる。 キーバインド・オプション・セッション・環境変数はサーバのメモリにあり、サーバが終わるまで残る。 .tmux.conf を読むのはサーバの起動時だけ。新しいセッションを作っても読み直さない。source-file は書かれたコマンドをもう 1 度流すだけ ペイン・popup・run-shell・if-shell のプロセスを起動するのはサーバ。操作中のシェルの子ではない 以下の罠は、どれもこの 2 点のどちらかから来る。 ...

2026-10-02 ·  2026-10-02 · 6 分 · 1271 文字

Go の JSON が警告なしに潰す null と欠落

結論 JSON の「キー欠落」「null」「[]」「0」を、Go の型は全部は区別できない。encoding/json は区別が消えてもエラーを返さない 書く側: nil スライスは null になる。*T に omitempty を付けると null を出せない。同じ階層に同じタグがあると両方消える。NaN が 1 つあると Marshal そのものが失敗する 読む側: キーは大文字小文字を無視して照合し、構造体に無いキーは捨てる。null はポインタ・スライス・map・interface 以外には何もしない。値型は 0 と欠落を、ポインタは null と欠落を区別できない 区別が契約なら、型に頼らず map[string]json.RawMessage でキー単位に見る。テストも構造体ではなく生の JSON で固定する 以下は Go 1.26.4 / testify v1.12.1 / go-playground/validator v10.30.5 で実測した。 前提: 区別を持てるのは Go の型の側だけ JSON の 1 項目は「キーが無い」「null」「空 ([] / "")」「ゼロ (0 / false)」「値あり」のどれかになる。 Go の値型が持てる状態は「ゼロ値」か「値あり」の 2 つで、ポインタ・スライス・map は nil の分だけ 1 つ増える。 JSON 側の状態を Go の型へ写すと、どこかの区別が消える。 ...

2026-10-01 ·  2026-10-01 · 5 分 · 1027 文字