家計簿サービス Zaim と自作 Cloudflare Workers (claude-code-zaim) の連携には「access_token」(アクセストークン: サービスにログイン代行する電子的な合鍵)を使ってる。Zaim の access_token は OAuth 1.0a (オーオース1.0a: 鍵の交換規格の一種) で発行され、明示的に無効化しない限り永久に有効という設計。
これは便利な反面、万が一トークンが漏れた場合 (例: 公開リポジトリへの誤コミット、PCの紛失) に攻撃者が無期限でZaimの家計データを読み書きできてしまう。インシデント発生時の被害は時間とともに大きくなる。
そこで「定期的に鍵を交換する」運用ルール (rotate: 計画的な鍵交換) を整備した。漏洩窓 (鍵が無防備にさらされる期間) を 最大6ヶ月 に圧縮する。
3つの成果物 + 1つの拡張で rotate 運用を仕組み化した。
~/ghq/github.com/ramenumaiwhy/claude-code-zaim/docs/zaim-token-rotate.md
手動でも実行できるよう、step-by-step で全コマンドと期待結果を明記。失敗時のリカバリ表もここに集約。
~/secretary-state/zaim-token-rotate.sh
手順書の内容をスクリプト化。各ステップで確認プロンプトを出し、Ctrl-C でいつでも安全に中断できる。後述の「安全装置」が組み込まれている。
~/plan-viewer/2026-05-07_zaim-token-rotate.html
スマホから手順とチェックリストを確認できるようにした。実行当日に手元で見ながら進めるための表示用。
claude-code-zaim/scripts/oauth-pin-flow.ts に verify サブコマンドを実装。
既存の認証フロー実装ファイルを 1 サブコマンド分だけ拡張。新トークンが本当に Zaim API で動くかを /v2/home/user/verify (Zaim 標準のトークン検証エンドポイント) で確認するためのもの。新トークン上書き前の最終チェックに使う。
| 用途 | 周期 | 理由 |
|---|---|---|
| 通常運用 (推奨) | 6ヶ月 | OAuth 1.0a の永続トークンは expire しない (期限切れにならない) ので、運用負荷と漏洩窓のバランス点。家計簿1個人運用ではこの粒度で十分 |
| 高セキュリティ要件時 | 3ヶ月 | 端末紛失や dotfiles 公開リポジトリへの事故などが懸念される時期は短縮 |
| インシデント時 | 即時 | 漏洩疑い・誤コミット発覚時はこの手順書を即実行する |
bun (高速 JavaScript ランタイム), npx wrangler (Cloudflare の CLI ツール), .dev.vars ファイル (consumer_key などを保存した環境変数ファイル) の存在を確認。1つでも欠けていたらここで停止する。/v2/home/user/verify エンドポイント (トークン有効性を確認するための専用窓口) を叩く。200 OK + ユーザー ID が返れば正常。失敗した場合はここで abort し、Workers Secret は変更されない (旧トークン無傷)。wrangler secret put コマンドで Cloudflare Workers の環境変数 ZAIM_ACCESS_TOKEN と ZAIM_ACCESS_TOKEN_SECRET を新値に置き換える。確認プロンプトに「y」で進める。/api/zaim/accounts) を実際に 1 回叩いて、新トークンで本番が動くことを確認する。~/secretary-state/logs/zaim-token-rotate.log に rotate 完了日時とトークン先頭 8 文字を追記。次回 rotate 期日 (6ヶ月後) を Apple Reminders「中長期」リストに手動で登録する。| 症状 | 原因候補 | 対応 |
|---|---|---|
request で 401 | consumer_key/secret 間違い | .dev.vars を再確認 |
access で 401 | verifier 入力ミス・期限切れ | request から再実行 |
verify で 401 | access フローでトークン取得失敗 / consumer 不一致 | request から再実行 |
| Workers Secret 更新後に本番 401 | wrangler secret put の値に余分な改行が混入 | echo -n または printf '%s' で改行除去して再投入 |
| 旧 app 連携解除後も旧トークンで 200 | Zaim 側のキャッシュ反映待ち | 数分待って再確認、それでも継続なら Zaim サポートに問い合わせ |
見送った選択肢: 自動 rotate (cron で半年に1回スクリプト発火) は採用しなかった。理由は (a) ブラウザでの認可操作 (verifier コピー) が必須で完全自動化できない、(b) 失敗時に深夜だと気づけずサービス停止の可能性、の2点。手動運用 + Reminders 通知の方が安全。
Zaim API の制約: OAuth 1.0a は OAuth 2.0 と違って refresh_token (期限切れ時に自動更新するための予備トークン) が無く、毎回ゼロから認可フローを回す必要がある。これは Zaim 側の実装制約なので、自分側で工夫してもこの手順を短縮できない。
新旧並存期間の検出方法: Step 7 で「どれが新セッションか分からない」場合は、解除前にもう一度 verify でアクセスし、Zaim 連携アプリ画面の「最終アクセス」時刻を見比べる。新しい方を残す。
初回 rotate: この設計を作った段階ではまだ実 rotate は未実施。次のアクション = 健人と一緒に初回 rotate を流して挙動を確認する。Step 4 (verify) と Step 5 (wrangler secret put) の確認プロンプトは特に注意して見ること。