majorotherverifiedfirsthand
レシート自動取込が見積スクショを読み、家計簿に架空の7万円が入った
レシート写真を自動でOCRして家計簿の台帳に入れるパイプラインが、スクリーンショットやブラウザで保存した画像まで読み込み、架空の支出5件(計¥73,158)を台帳に書き込んでいた。
原因
4つの穴が重なっていた。ダウンロードフォルダの画像を無条件で取り込んでいた。OCRが数字さえあれば金額を返し、それを無検査で台帳に入れていた。Excelの集計が古い計算結果のまま表示されていた。重複判定がファイル名だけで、同じ画像が別名で届くと二重登録された。
結果
家計簿の台帳に、実在しない支出が5件混入した。最大の1件は運営者自身のダッシュボードの見積スクショに書かれた¥66,000だった。修正コミットはその後約2ヶ月、リモートにpushされないまま残っていた。
対策
AirDropで届いた画像だけを通す判定、EXIFの撮影機材によるカメラ写真判定、読み込み時の強制再計算、画像のSHA-256による重複判定を入れた。
何が起きたか
iPhoneでレシートを撮ってAirDropで送ると、Macのダウンロードフォルダに落ち、 launchdのジョブがそれを専用フォルダへ移し、OCRして家計簿の台帳CSVに追記し、 Excelの集計を更新して通知を出す。全自動のレシート取り込みパイプラインである。
きっかけは「うまく動作してない気がする」という一言だった。調べてみると、 launchdは正常に動いていた。正常に動いたうえで、間違ったものを取り込んでいた。 台帳には実在しない支出が5件、合計¥73,158入っていた。
現場の混乱
最大の1件は¥66,000。出どころは、運営者自身のダッシュボードの見積画面を撮った スクリーンショットだった。そこには「合計:66,000円(税込)」と書かれていた。 本物のレシートと同じ言葉を持つので、キーワード検査では区別できなかった。
穴は1つではなかった。
- 移動スクリプトが、ダウンロードフォルダの画像を出どころを問わずすべて運んでいた。 スクショもブラウザで保存した画像も、レシートとして扱われた。
- OCRは画像に数字があれば金額を返す。その金額が何の検査もなく台帳に入った。
- 台帳からExcelを更新する処理はセルの値だけを書き換えていた。Excelは保存済みの 数式結果を信用するため、サマリーが古い数字のまま表示されていた。
- 重複判定がファイル名ベースだったため、同じ画像が別名で2回届くと二重に登録された。
修正は即日コミットされた。ところがそのコミットは、約2ヶ月後に別件の修正と まとめてpushされるまで、ローカルにだけ存在していた。
原因
入口を疑っていなかった。「ダウンロードフォルダに来た画像はレシートだ」という 前提と、「OCRが金額を返したならレシートだ」という前提が、どちらも検査されずに つながっていた。入口を疑わない自動化は、ゴミを正確に帳簿へ運ぶ。
対策
- AirDrop判定: 実測すると、AirDropで届いた画像は
com.apple.quarantineの エージェントがsharingd、ブラウザのダウンロードは別の値、スクショは属性なしだった。これで入口を絞った。- カメラ写真判定: キーワード検査は見積スクショに負けた。決め手はEXIFの撮影機材で、
手元の実データ13件では実レシートの写真だけが
Make=Appleを持ち、他はすべて外れた。
- カメラ写真判定: キーワード検査は見積スクショに負けた。決め手はEXIFの撮影機材で、
手元の実データ13件では実レシートの写真だけが
- 強制再計算: 読み込み時に全再計算させるフラグ
fullCalcOnLoad="1"を立て、 計算チェーンを削除した。フラグがファイルに入ったことは確認済みだが、 実際にExcelで開いての再計算確認はしていない。 - 重複判定: 台帳に
image_sha256列を足し、ファイル名ではなく中身で判定するようにした。
台帳は51件になり、テストは74本から96本に増えた。
出典
- 運営者自身が起こした事故の一次記録です。外部に公開された元記事はありません。