Fess 15.5に向けたセキュリティ強化

Fess 15.4のリリースに向けて、セキュリティ面でのさまざまな強化を行いました。より安全な運用を実現するための改善点をまとめて紹介します。

暗号化関連の改善

SAML署名アルゴリズムの更新

SAML認証で使用する署名アルゴリズムのデフォルトをRSA-SHA256に更新しました。Azure AD、Okta、OneLoginなど主要なIdPはSHA-256を標準としてサポートしており、より堅牢な認証連携が可能になります。

暗号化アルゴリズムの設定警告

DES、Blowfish、MD5などレガシーな暗号化アルゴリズムが設定されている場合に、起動時に警告ログを出力するようにしました。AESやSHA-256への移行を促すメッセージが表示され、より安全なアルゴリズムへの移行を支援します。

設定プロパティ名の統一

セキュリティという感じではないですが、暗号化アルゴリズム関連の設定プロパティ名を app.cipher.algorithmapp.digest.algorithm に統一しました。

認証・認可の強化

パスワードポリシーの設定

パスワードポリシーを設定できるようになりました。以下の項目を fess_config.properties で設定可能です。

  • password.min.length: 最小文字数(デフォルト: 8)
  • password.require.uppercase: 大文字を必須にするか
  • password.require.lowercase: 小文字を必須にするか
  • password.require.digit: 数字を必須にするか
  • password.require.special.char: 特殊文字を必須にするか

管理画面でのユーザー作成、プロフィールでのパスワード変更、パスワードリセット時にポリシーが適用されます。

LDAPエスケープ処理の追加

LDAP認証で使用するフィルタやプリンシパルの構築時に、RFC 4515に準拠したエスケープ処理を行うようにしました。バックスラッシュ、アスタリスク、括弧などの特殊文字が適切に処理されます。

メモリ上のパスワードクリア

ユーザー作成・更新処理の完了後、Userエンティティに一時的に保持された平文パスワードをメモリからクリアする処理を追加しました。

ファイル操作の改善

アトミックファイル操作

サムネイル生成処理で、Java NIOのアトミック操作を使用するように改善しました。Files.deleteIfExists()Files.createDirectories() を使用することで、マルチスレッド環境でのファイル操作がより堅牢になります。

パストラバーサル対策

管理画面のデザインファイルアップロード、JSP編集、ログファイルダウンロードにおいて、パスの検証を強化しました。.. シーケンスの除去、パス区切り文字の正規化、ホワイトスペースの除去などを行い、意図しないディレクトリへのアクセスを防止します。

監査・ログ機能の強化

スクリプト実行の監査ログ

Groovyスクリプトの実行を監査ログに記録する機能を追加しました。スクリプトの種類、内容、実行元、ユーザー、実行結果がログに記録されます。script.audit.log.enabled 設定で有効/無効を切り替えられます。

機密情報のマスキング

デバッグログ出力時に、環境変数やシステムプロパティの中で機密性の高い値(PASSWORD、SECRET、TOKEN、KEYなどを含むキー)を自動的にマスキングするようにしました。パターンは app.log.sensitive.property.pattern で設定可能です。

SAMLログの機密情報軽減

SAMLログアウト時の警告ログで、SamlUserオブジェクト全体ではなくユーザー名のみを出力するように変更しました。

シリアライズの安全性向上

Kryoシリアライザで、クラス登録を必須とする設定に変更しました。プリミティブ型、コレクション、日付などの許可されたクラスのみがデシリアライズ可能となり、ガジェットチェーン攻撃のリスクを軽減します。

まとめ

これらの改善により、Fessはより堅牢なセキュリティを備えた検索プラットフォームになります。特にエンタープライズ環境での運用において、監査ログやパスワードポリシーなどの機能が有用です。

Agentic Design Patternのまとめ

Agentic Design Patternsの内容を元に、AIエージェントの設計パターンについて整理しておく。AIエージェントを構築する際の基本的な考え方と、実践で使える設計パターンを整理しています。

AIエージェントとは

AIエージェントとは、自律的に環境を認識し、目標達成のために行動を取るシステムです。従来のチャットボット(質問に答えるだけ)とは異なり、エージェントは以下の5ステップのループで動作します。

  1. ミッション受領
  2. 環境スキャン
  3. 計画立案
  4. 行動実行
  5. 学習・改善(そして1に戻る)

エージェントの進化レベル

AIエージェントは、その能力によって4つのレベルに分類できます。

レベル名称特徴
Level 0コア推論エンジンLLMのみ。外部ツールなし単純なチャットボット
Level 1接続された問題解決者外部ツール(検索、API等)を使用可能天気を調べて答えるBot
Level 2戦略的問題解決者計画立案、プロアクティブ支援、自己改善複雑なタスクを分解して実行
Level 3協調的マルチエージェント専門化されたエージェントのチームが協力開発チーム全体を模倣

各レベル間の違いを理解することが重要です。

  • Level 0→1:外部世界と対話できるかどうか
  • Level 1→2:自分で計画を立てられるかどうか
  • Level 2→3:複数のエージェントが協力できるかどうか

基本パターン

エージェントを構築する上で最も基礎となるパターンを紹介します。

1. Prompt Chaining(プロンプト連鎖)

複雑なタスクを小さなステップに分解し、順番に処理するパターンです。

単一の複雑なプロンプトでLLMに指示すると、指示の見落とし、文脈の喪失、エラーの増幅、幻覚(Hallucination)といった問題が発生します。タスクを分割することでこれらを軽減できます。

例:ブログ記事の作成
1. トピックからアウトラインを生成
2. アウトラインから各セクションを執筆
3. 全体を編集・校正

2. Routing(ルーティング)

入力内容に応じて、適切な処理経路を選択するパターンです。

例えば、顧客の問い合わせを受け取ったら、ルーターが内容を分析して技術サポート担当、注文状況担当、製品情報担当など適切なエージェントに振り分けます。

ルーティングの方法には以下があります。

  • LLMベース:LLMが入力を分析して判断(柔軟性が必要な場合)
  • 埋め込みベース:意味的類似性で判断(大量のカテゴリがある場合)
  • ルールベース:キーワードで判断(明確なルールがある場合)
  • MLモデルベース:訓練された分類器で判断(高精度が必要な場合)

3. Tool Use(ツール使用)

LLMが外部のAPI、データベース、サービスと対話できるようにするパターンです。

LLMだけではリアルタイム情報の取得(天気、株価など)、計算の実行(正確な数値計算が保証されない)、外部システムへのアクション(メール送信、予約など)ができません。Tool Useパターンでこれらを可能にします。

処理の流れは以下のようになります。

  1. ツール定義(どんなツールが使えるかをLLMに教える)
  2. LLMが判断(ツールが必要か?どのツールを使うか?)
  3. ツール呼び出し(LLMがJSON形式で指示を出力)
  4. ツール実行(システムが実際にAPIを呼び出す)
  5. 結果をLLMに返す
  6. LLMが最終応答を生成

4. Parallelization(並列化)

独立したタスクを同時に実行し、処理時間を短縮するパターンです。

例えば、旅行計画であればフライト検索、ホテル検索、レストラン検索を同時に実行できます。ただし、並列化できるのは独立したタスクのみで、タスクBがタスクAの結果を必要とする場合は順次処理が必要です。

5. Reflection(リフレクション)

エージェントが自分の出力を評価し、改善するフィードバックループです。

  1. 実行:初期出力を生成
  2. 評価:出力を基準に照らして分析
  3. 改善:批評に基づいて出力を修正
  4. 反復:満足のいく結果になるまで繰り返す

より効果的な方法として、Producer(生産者)とCritic(批評者)に役割を分離します。同じLLMが作成と評価を両方行うと自分のミスに気づきにくいため、別のペルソナを与えることで異なる観点からの疑似的な批評が可能になります。

6. Planning(計画立案)

高レベルの目標を受け取り、達成するためのステップを自律的に生成するパターンです。

Prompt Chainingとの違いは、ステップの決定方法です。Prompt Chainingでは開発者が事前に設計しますが、Planningではエージェントが自律的に生成します。状況に応じて計画を変化させる柔軟性があり、解決策が不明なタスクに適しています。

GoogleのDeepResearch機能はPlanningパターンの実例です。ユーザーの複雑なトピックを受け取り、多段階の研究計画に分解し、反復的に検索・分析を実行して構造化されたレポートを生成します。

7. Multi-Agent Collaboration(マルチエージェント協調)

複数のエージェントが協力して目標を達成するパターンです。

単一エージェントの限界(単一の視点・単一の推論トレースに依存するため、専門性の分離や自己検証が難しい)をマルチエージェントで解決します。

協調の形態には以下があります。

  • Sequential Handoffs:順次引き継ぎ(ライター→編集者→デザイナー)
  • Parallel Processing:並列処理(競合分析、顧客調査、トレンド分析を同時に)
  • Debate and Consensus:議論と合意形成(強気派と弱気派が議論)
  • Hierarchical Structures:階層構造(マネージャーがタスクを分配)
  • Expert Teams:専門家チーム(フロントエンド、バックエンド、DB担当が協力)
  • Critic-Reviewer:批評・レビュー(開発者が書き、レビュアーがチェック)

高度なパターン

Memory Management(メモリ管理)

エージェントに「記憶」を与えるパターンです。これがないとエージェントは毎回の会話で「初対面」の状態になります。

メモリには2つのタイプがあります。

  • Short-Term Memory(短期メモリ):現在の会話内の情報。LLMのコンテキストウィンドウ内に存在し、セッション終了で消失
  • Long-Term Memory(長期メモリ):外部ストレージに保存された情報。セッション間で永続

長期メモリには3つの種類があります。

  • Semantic Memory(意味記憶):事実と概念の知識(ユーザーの好み、設定)
  • Episodic Memory(エピソード記憶):過去の出来事の記憶(過去の会話履歴)
  • Procedural Memory(手続き記憶):成功した作業手順やワークフローを外部に保存したもの

RAG(Retrieval Augmented Generation)

エージェントに「外部知識」を与えるパターンです。LLMの知識の古さ(訓練データ以降の情報を知らない)と知識の範囲(社内文書など非公開情報を知らない)という限界を解決します。

RAGの基本プロセスは以下の通りです。

  1. ユーザーがクエリを送信
  2. 外部知識ベースから関連情報を検索
  3. 最も関連性の高い部分を抽出
  4. 元のプロンプトに追加(拡張)
  5. LLMが取得した情報に基づいて回答

コア技術はEmbeddings(埋め込み)です。テキストをベクトル(数値の配列)に変換し、意味的な近さを計算します。「車の値段」と「自動車の価格」は同じ意味ですが、キーワード検索ではヒットしません。Embeddingsによる意味検索で解決できます。

RAGの進化形として、Graph RAG(ナレッジグラフを使用)やAgentic RAG(エージェントが検索結果を評価して再検索)があります。

通信規格

MCP(Model Context Protocol)

LLMが外部リソースと対話するための標準化されたインターフェースです。Anthropic社が提唱したオープンスタンダードで、「どのLLMでも、どの外部システムにも接続できる」ことを目指しています。

従来のTool Function Callingはベンダー固有で再利用が困難でしたが、MCPにより標準化されたプロトコルで接続できます。

MCPサーバーが提供する3つの要素:

  • Resources:静的データ(ファイル内容、DB情報)
  • Tools:実行可能な関数(検索、計算、API呼び出し)
  • Prompts:対話テンプレート(定型的な質問形式)

A2A(Agent to Agent)

異なるフレームワークで構築されたエージェント同士が通信するための標準規格です。Google主導で開発され、Salesforce、SAP、Microsoft等が参加しています。

MCPがエージェントとツール・データの接続なのに対し、A2Aはエージェント同士の接続です。両者は補完関係にあります。

A2Aのコア概念:

  • Agent Card:エージェントの「名刺」。名前、エンドポイント、スキル、認証方法をJSON形式で記述
  • エージェント発見:Well-Known URI、レジストリ、直接設定などの方法で他のエージェントを発見
  • 通信方式:同期、非同期ポーリング、ストリーミング(SSE)、Webhook

実用パターン

Exception Handling(例外処理と回復)

予期しないエラーが発生しても、システムが適切に対応するパターンです。

エージェントは現実世界と対話するため、APIエラー、無効なデータ、タイムアウト、LLMの誤動作など様々な障害が発生します。

例外処理の3つのフェーズ:

  1. Error Detection(検出):エラーを発見
  2. Error Handling(処理):Logging、Retry、Fallback、Graceful Degradation、Notification
  3. Recovery(回復):State Rollback、Diagnosis、Self-Correction、Escalation

Human-in-the-Loop(人間の介入)

AIの処理に人間の判断を組み込むパターンです。

エージェントは万能ではなく、曖昧な状況での判断、倫理的判断、未知の状況への対応、責任を負うことが難しいです。

HITLの形態:

  • Human Oversight(監視):人間がリアルタイムでエージェントの動作を監視
  • Intervention and Correction(介入と修正):エージェントが不確実な場合、人間に判断を仰ぐ
  • Human Feedback for Learning(学習用フィードバック):RLHF等に活用
  • Decision Augmentation(意思決定支援):AIは情報提供、人間が最終決定
  • Escalation(エスカレーション):複雑/重要なケースは人間にエスカレート

Human-on-the-Loop(HOTL)という変形パターンもあります。人間が事前にルールを設定し、AIが自律的に実行します。異常時のみ人間に通知します。

Goal Setting and Monitoring(目標設定と監視)

エージェントに明確な目標を与え、達成度を追跡するパターンです。

PlanningパターンとはGoal Setting & Monitoringは「達成できたか」を評価する点で異なります。両者は補完関係にあり、目標設定→計画立案→実行→進捗追跡→未達成なら計画修正、というサイクルで動作します。

注意点として、目標の曖昧さ、自己評価の限界、無限ループの危険があります。役割分離、最大反復回数の設定、人間によるレビューで対処します。

高度な最適化パターン

Learning and Adaptation(学習と適応)

エージェントが経験から学び、時間とともに改善するパターンです。

学習の種類:

  • 強化学習:報酬/ペナルティから学ぶ
  • 教師あり学習:正解例から学ぶ
  • 教師なし学習:パターンを発見
  • Few-shot学習:少数の例から学ぶ
  • オンライン学習:メモリ・方針・ルーティング等の継続的な更新
  • メモリベース学習:過去の経験を参照

重要なアルゴリズムとして、PPO(Proximal Policy Optimization:慎重に少しずつ改善する強化学習)とDPO(Direct Preference Optimization:人間の好みを直接学習)があります。

実例として、SICA(Self-Improving Coding Agent:自分自身のコードを修正して性能を向上)やAlphaEvolve(Google:LLM+進化的アルゴリズムでアルゴリズムを発見・最適化)があります。

Resource-Aware Optimization(リソース認識最適化)

コスト、時間、計算リソースを考慮して動的に判断するパターンです。

「最高の結果」だけでなく「制約内で最適な結果」を目指す必要があります。

主な最適化戦略:

  • モデルの使い分け:簡単な質問は軽量モデル、複雑な質問は高性能モデル
  • Router Agent:クエリを分析し、最適なモデルに振り分け
  • 階層的エージェント:高レベル計画は高性能モデル、個別タスクは軽量モデル
  • Fallback:主要サービスが使えない場合の代替
  • Critique Agent:応答品質を評価し、ルーティングを改善

設計パターン一覧

最後に、全体をまとめます。

基本パターン(7つ)

パターン一言で言うと解決する問題
Prompt Chainingタスクを順番に分解複雑な指示の失敗
Routing適切な経路を選択単一処理の限界
Tool Use外部ツールと連携LLMの知識・能力の限界
Parallelization同時に実行処理時間の長さ
Reflection自己評価と改善出力品質の問題
Planning自律的に計画立案未知のタスクへの対応
Multi-Agent複数が協力単一エージェントの限界

高度なパターン(4つ)

パターン一言で言うと解決する問題
Memory Management過去を記憶文脈の喪失
Learning & Adaptation経験から学習静的な能力の限界
MCPツールとの標準接続ベンダー依存
Goal Setting & Monitoring目標達成を追跡成果の不確実性

実用パターン(3つ)

パターン一言で言うと解決する問題
Exception Handlingエラーから回復システム障害
Human-in-the-Loop人間が介入AIの判断限界
RAG外部知識を活用知識の古さ・範囲

エンタープライズパターン(2つ)

パターン一言で言うと解決する問題
A2Aエージェント間通信フレームワーク間の壁
Resource-Aware Optimizationリソースを最適化コスト・時間の制約

実践のポイント

  • パターンは「部品」である:単独で使うより、組み合わせてより強力なシステムを構築する
  • 完璧を求めない:エラーは必ず起きる。Exception HandlingとHuman-in-the-Loopで対処
  • コストを意識する:「最高のモデル」ではなく「制約内で最適なモデル」を選ぶ
  • 学習し続ける:静的なシステムは陳腐化する。Learning and Adaptationで進化し続ける
  • 標準化の重要性:MCPとA2Aにより、エコシステム全体で協調できる

WordPressのエクスポートでTypeErrorになる

PHP 8.3にバージョンアップしたところ、WordPressのコンテンツデータのエクスポートがエラーになった。

エラー内容

エクスポートを実行すると以下のようなFatal errorが発生する。

PHP Fatal error:  Uncaught TypeError: wp_is_valid_utf8(): Argument #1 ($bytes) must be of type string, null given called in wp-admin/includes/export.php on line 246

原因

PHP 8.3で型チェックが厳格化されたため、wxr_cdata()関数にNULLが渡された場合にFatal errorとなる。PHP 8.2まではwarningで済んでいたが、8.3で致命的エラーになった。

対処方法

wp-admin/includes/export.phpwxr_cdata()関数を修正し、NULLが渡された場合に空文字列として扱うようにする。

245行目付近のwxr_cdata()関数の先頭に以下のチェックを追加する。

function wxr_cdata( $str ) {
    if ( $str === null ) {
        $str = '';
    }

Docker ComposeでWordpressを利用している場合

Docker Composeで運用している場合は、以下のコマンドで対象のコンテナ(ここではwordpressというコンテナ名)に対して修正を適用できる。

docker compose exec wordpress sh -c '
sed -i "
/function wxr_cdata( \$str ) {/a\\
\\
    // PHP 8.3 compatibility fix\\
    if ( ! is_string( \$str ) ) {\\
        \$str = \"\";\\
    }\\
" /var/www/html/wp-admin/includes/export.php
'

注意点

WordPressのアップデート時にこの修正は上書きされるため、アップデート後に再発するようであれば、再度適用が必要になる。WordPress本体で修正されるまでの暫定対応として利用する。