朝は京橋川を散歩させて、シャワーを浴びて五日市の病院に、帰りに元宇品の森でキクを散歩させていたらVFKさんから電話、昼は近くの『はる』でお好み焼きを食べて、晩秋には北海道へJeepでキャラバンすることで盛り上がる・・・夕方に、贈り物がしたいということなので、洋酒の酒店に行く、ウイスキーが好みだそうで2種類を購入、TAMにも一本贈り物として頂いた

建設アシスト 品質・セキュリティ改善 総括
1.目的
建設アシストは、現場マネジメントDXの実践アプリケーションで、本プロジェクトの目的は以下の3点
- 大手組織のセキュリティ審査に耐えるシステムにする 複数テナント(会社)が同居するSaaSとして、テナント間のデータ分離を構造的に保証する
- AIに投げる情報の管理と漏洩リスクの低減 工事情報・現場情報をAI APIに送信する以上、何をどう送り、何を記録するかを一元的に管理できる構造にする
- 今後の機能追加に耐える保守性の確保 一人開発で継続的に機能を追加していくため、重複コード・デッドコードを排除し、「1箇所直せば全体に効く」構造を作る
2.出発点の課題(Fable 5による全体検証の結果)
セキュリティ(フェーズ1)
- Critical 7件・High 12件。中心はIDOR(他社データへの越境アクセス)で、工程表・安全パトロール・RA・施工計画書の主要機能に集中していた
- テナント分離がコントローラの手動認可(authorizeProject)に全依存しており、
1箇所の実装漏れがそのままデータ漏洩に直結する構造だった - 当時の評価:「現時点では大手組織のセキュリティ監査に耐えられない」
保守性(フェーズ2)
- AI呼び出しが8箇所に散在し、URL・タイムアウト・エラー処理がコピペ重複
- 工程表のJS約4,100行がBladeにインラインで埋め込まれ、CSP強化の障害に
- テナント設計が未完了のモデル(company_id未設定)が多数
- デッドコード(未完成のWord/Excel出力サービス、旧版施工計画書、孤立したAIサービス等)が大量に残存。
バグ(フェーズ3)
- 確定バグ14件。空CSVで法規制マスタが全削除される致命的バグ、工程表の無限ループ、祝日計算誤りによるCPMの1日ズレ等
- トランザクション欠落が主要機能に多数(途中失敗で中途半端なデータが残る)
- 同時編集の保護が皆無(後勝ち上書きで先の編集が無警告で消える)。
3.緊急対応
Critical IDOR 8件をabort_ifによる親子一致確認で全件修正
空CSV全削除・無限ループ等の確定バグ、トランザクション欠落4件を修正
本番.envのログレベルも是正し、AIレスポンスのログ蓄積を停止した。
結果: 即時の攻撃可能な穴は全て塞がれた。ただしこの時点では
「穴を1つずつ塞いだ」状態であり、構造的な保証はまだなかった
4.テナント分離の構造化
BelongsToTenantグローバルスコープを設計し、全36モデル(company_id直持ち10・Project木経由13・多段経由13)に段階適用した
これによりModel::find($id)の段階で他社データが自動的にnullになり、コントローラで認可を忘れてもデータが漏れない構造が完成した
既存の手動認可は多層防御として残置
結果: 「テナント分離はどう担保していますか」という審査質問に「モデル層のグローバルスコープで全クエリに自動適用」と答えられる状態になった
途中の重要な学び: 適用初日にAuth::check()の無限再帰によるメモリ枯渇が発生。テスト(actingAs)では検出できず、実機経路で初めて顕在化した・・以降、全フェーズで「実機相当検証(セッション復元経由・低メモリ制限)」を標準手順に組み込んだ
5.AI呼び出しの集約
8箇所に散在していたAI呼び出しをAnthropicClientサービスに集約
cURL直書きを全廃し、用途別のモデル・タイムアウト・トークン数をconfig一元管理にした。AIレスポンスの本文ログ出力を全削除し、ログには用途と長さのみを記録する方針に統一した
移行調査の過程で、8箇所のうち3箇所は呼び出し元が消えた孤立コードと判明し
削除。品質検証では、PDF様式取込でhaikuが「令和8年→和6年」「最大積載荷重→
最大建築面積」等の危険な誤読をすることが実測で確認され、安全書類系はsonnetを使う用途別モデル切り替えの妥当性が裏付けられた
結果: AIへの送信内容・記録・コストが1箇所で管理できる構造になり、「AIに投げる情報の管理」という当初目的の技術基盤が完成した
10.今後の留意点
- テストを回帰防止網として維持する・・TenantIsolationTest(33件)とptimisticLockTest(26件)は、今後の機能追加時に必ず実行する・・新しいモデルを追加したらBelongsToTenantの分類(A/B/C/D)を判断してトレイトを適用し、テストも追加する・・・これを怠ると二重防御が一重に戻る
- 新しい保存エンドポイントには楽観ロックを最初から組み込む・・withOptimisticLockトレイトとoptimistic-lock.jsが共通基盤として存在するので、新機能では最初から使う・・特に「全削除→全挿入」型の保存は409時の保護を必ずテストする
- fetch実装時の定型ルールを守る・・Acceptヘッダー必須・応答とレコードの対応付け・保存の直列化・・楽観ロック導入で確立したこの3点は、今後のAjax実装全般の規約とする
- デプロイ手順の遵守・・git pull → migrate → キャッシュ再構築 → composer install の順序・・・特にcomposer installの省略は今回autoload起因の500を招いた・・DBパスワード等の変更時は.envとキャッシュの両方を必ず確認する
11.残課題(急がない)
- CSP unsafe-inlineの除去: JS外部化で道筋はついたが、残るインラインscriptの整理が必要CSP完全化は審査対策の最終ピース
- バー・矢線の右クリック編集統一: アノテーションで統一した操作体系を工程表の主要素にも広げる(別起票済み)
- 旧版施工計画書のテーブル最終整理: コードは削除済み
本番データ208件を含むテーブルの扱いは将来のマイグレーションで対応 - PHPUnit 12対応: doc-comment警告のattribute書式への移行(動作影響なし)
6.工程表の近代化
Bladeに埋め込まれていた工程表JS約4,300行を外部ファイル化し、CSP強化(unsafe-inline除去)への道筋をつけた。あわせてUX課題に着手した
- 自動保存(2分・変更検知)と保存ボタンの点滅フィードバック
- 工程表選択モーダルの改善(別種件数表示・キャンセルで工事管理へ)
- ネットワーク/バーチャートで別々だったモーダルUI・操作体系の統一
- アノテーション・フリー線のアーキテクチャをイベント委譲方式に統一し、
共通モジュール(schedules-common.js)に抽出
調査の過程で、バーチャート側はテキストボックスの移動もフリー線の 端点編集も未実装だったことが判明し、ネットワーク同等の機能を新規実装した
結果: 両工程表の操作が統一され、今後の機能追加は共通モジュールの
1箇所修正で両画面に反映される。重複コード約550行を削減
7.同時編集の保護
updated_atベースの楽観ロックを設計し、共通基盤(D-0)→工程表(D-1)→施工計画書(D-2)→現場帳票(D-3)→監査系(D-4)→必須化(D-5)の6フェーズで段階導入した。競合検出時は保存を一切書き込まず、競合モーダルで「最新を読み込む/編集内容を退避(JSONダウンロード)」を提示する
特に危険だった「全削除→全挿入」型の保存(環境側面・監査チェックシート)と「payload外id削除」型(監査指摘→是正処置のcascade連鎖消滅)は、409時に削除処理が一切実行されないことをテストで担保した
結果: 複数ユーザーの同時利用で編集が無警告で消える構造が解消された・・・導入中に発見した「遅延レスポンスによる別レコードのトークン汚染」というSPA特有の落とし穴も修正し、再現テストとして固定化した
8.コードベースの清掃
デッドコードを体系的に削除した・・・ompdf/Puppeteer(サーバーPDF生成・未使用)、未完成のWord/Excelサービス3本、孤立AIサービス2本、旧版施工計画書(ルート13本・コントローラ約1,000行・ビュー4本)、空モデル2本等、累計で約6,000行以上を削除README・CLAUDE.mdも実態に合わせて更新した
9.定量的な成果
| 指標 | 開始時 | 完了時 |
|---|---|---|
| Critical脆弱性 | 7件 | 0件 |
| 確定バグ | 14件 | 0件 |
| テナント分離 | 手動認可のみ | 36モデルに自動スコープ+手動認可の二重防御 |
| AI呼び出し | 8箇所散在・cURL混在 | 1サービスに集約・config一元管理 |
| 同時編集保護 | なし(後勝ち上書き) | 全主要機能で楽観ロック必須 |
| 自動テスト | 26件 | 59件(テナント分離33+楽観ロック26) |
| Blade内インラインJS | 工程表約4,300行 | 外部化完了 |
| 削除したデッドコード | — | 約6,000行以上 |



