Filter
in package
Table of Contents
Constants
- MAX_SCRUB_DEPTH = 10
- 再帰スクラブの深さ上限。
Properties
- $arrayKeyExactMatchKeys : array<string|int, mixed>
- 配列のキーを完全一致でマスクする対象のリスト(scrubLogArray() 専用)。
- $headerSensitiveFields : array<string|int, mixed>
- HTTPヘッダ専用の機密フィールドのリスト(部分一致でマスクする)。
- $mask : string
- マスク文字列
- $sensitiveFields : array<string|int, mixed>
- 機密フィールドのリスト(部分一致でマスクする)
- $urlQueryExactMatchKeys : array<string|int, mixed>
- URL のクエリ文字列を完全一致でマスクする対象のリスト(scrubUrl() 専用)。
- $urlValueKeys : array<string|int, mixed>
- scrubLogArray() で値を丸ごとマスクせず scrubUrl() に通す対象のキー(大文字小文字を区別しない)。
Methods
- __construct() : mixed
- コンストラクタ
- getSafeArray() : array<string|int, mixed>
- 安全にフィルタリングされた配列を取得
- getSafeHeaders() : array<string|int, mixed>
- HTTPヘッダを安全にフィルタリングされた配列として取得する。
- getSensitiveFields() : array<string|int, mixed>
- 設定されている機密フィールド一覧を取得
- registerSensitiveKeys() : self
- マスク対象のキーを登録する(拡張アプリ・ユーザー拡張向けの公開 API)。
- scrubLogArray() : array<string|int, mixed>
- 既存レコードの読み出し・ログ出力向けに配列を再帰的にスクラブする。
- scrubUrl() : string
- URL のクエリ文字列から機密なトークン・認可コード等をスクラブし、パス上の メディアダウンロードハッシュ(無期限capability)もマスクする。
- setMask() : self
- マスク文字列を設定
- setSensitiveFields() : self
- 機密フィールドを一括設定
- unregisterSensitiveKeys() : self
- registerSensitiveKeys() で登録したキーを取り消す。
- filterArray() : array<string|int, mixed>
- 配列を再帰的にフィルタリング
- getMaskedValue() : string
- マスクされた値を取得
- getSensitiveKeyList() : array<int, string>|null
- target に対応する現在のリストを返す。未知の target なら null。
- isExactMatchKey() : bool
- キーが配列キー用の完全一致マスク対象か判定する
- isSensitiveField() : bool
- フィールド名が機密情報に該当するかチェック(部分一致)
- isUrlValueKey() : bool
- キーがURL値を持つキーか判定する
- normalizeSensitiveKeys() : array<int, string>
- 登録・取り消しで受け取ったキーを文字列のリストへ正規化する。
- scrubMediaDownloadHash() : string
- media-download/{mid}/{hash}/ のパスに含まれるハッシュ部分をマスクする。
- setSensitiveKeyList() : void
- target に対応するリストを差し替える。未知の target は何もしない。
Constants
MAX_SCRUB_DEPTH
再帰スクラブの深さ上限。
private
int
MAX_SCRUB_DEPTH
= 10
Why not: filterArray() / scrubLogArray() は自身を再帰呼び出しするため、循環参照配列 ($a['self'] = &$a; 等、拡張・プラグインからの入力を含む)を渡されると際限なく再帰し スタックオーバーフローで fatal になる。ScrubProcessor 経由で全ログレコードの context/extra が対象になった($_POST 限定ではなくなった)ため、露出面を塞ぐ意味で上限を設ける。
Properties
$arrayKeyExactMatchKeys
配列のキーを完全一致でマスクする対象のリスト(scrubLogArray() 専用)。
private
array<string|int, mixed>
$arrayKeyExactMatchKeys
= ['acms-preview-mode', 'salt', 'context', 'preview-token', 'token', 'oauth_token', 'oauth_verifier']
Why not: urlQueryExactMatchKeys とは意図的に語彙を変えている。'code' / 'key' / 'state' は URL クエリキーとしては機密(認可コード・derived key・CSRF state)だが、配列キーとしては 汎用的な識別子名として実際に使われている(user_code の警告ログ・SQLSTATE・エイリアス/ カテゴリーコード・一括変更フィールドキー・RestoreStatus の処理状態等)。同じ語をここに 含めると、それらの調査に必要な情報がまるごと 'MASKED' に潰れてしまう ($_POST 用の sensitiveFields に 'cookie' を混ぜてはいけない理由と同型の問題)。
$headerSensitiveFields
HTTPヘッダ専用の機密フィールドのリスト(部分一致でマスクする)。
private
array<string|int, mixed>
$headerSensitiveFields
= ['cookie', 'authorization', 'proxy-authorization', 'x-api-key']
Why not: cookie / authorization / proxy-authorization / x-api-key はハイフン区切りの HTTPヘッダ名(Cookie, Authorization, Proxy-Authorization, X-Api-Key)に一致させるための 表記で、$_POST 用の api_key(アンダースコア区切り)とは別に持つ必要がある。sensitiveFields 本体に混ぜると $_POST(cookie_policy_agree 等のフォーム項目)にまで cookie 等の 部分一致が及んでしまうため、getSafeHeaders() 専用の別リストとして分離している。
$mask
マスク文字列
private
string
$mask
= '***MASKED***'
$sensitiveFields
機密フィールドのリスト(部分一致でマスクする)
private
array<string|int, mixed>
$sensitiveFields
= ['password', 'passwd', 'pass']
$urlQueryExactMatchKeys
URL のクエリ文字列を完全一致でマスクする対象のリスト(scrubUrl() 専用)。
private
array<string|int, mixed>
$urlQueryExactMatchKeys
= ['acms-preview-mode', 'key', 'salt', 'context', 'preview-token', 'token', 'code', 'state', 'oauth_token', 'oauth_verifier']
Why not: 部分一致にすると key が field_key 等の識別子カラム名まで巻き込んで全滅させるため (既存の sensitiveFields に 'key' を追加しない理由と同じ)、完全一致専用のリストとして 分離している。
Why not: 'code' と 'state' は配列キー用の arrayKeyExactMatchKeys には含めない
(下記コメント参照)が、SNS ログインコールバックの ?code=…&state=…(OAuth 認可コード・
CSRF 対策の state パラメータ)をスクラブするにはクエリキーとして必要なため、こちらには残す。
Why not: 隣の $sensitiveFields / $mask は型付きプロパティ(PHP 7.4+)だが、この新規プロパティ 以降は意図的に型宣言を付けていない。このクラスは 301x(PHP 7.3 以上サポート)へ手動で バックポートされるため、新規追加分は型付きプロパティに依存しない書き方に揃えている。
$urlValueKeys
scrubLogArray() で値を丸ごとマスクせず scrubUrl() に通す対象のキー(大文字小文字を区別しない)。
private
array<string|int, mixed>
$urlValueKeys
= ['url', 'referrer', 'referer']
Methods
__construct()
コンストラクタ
public
__construct([array<string|int, mixed> $additionalFields = [] ][, string $mask = '***MASKED***' ]) : mixed
Parameters
- $additionalFields : array<string|int, mixed> = []
- $mask : string = '***MASKED***'
getSafeArray()
安全にフィルタリングされた配列を取得
public
getSafeArray(array<string|int, mixed> $data) : array<string|int, mixed>
Parameters
- $data : array<string|int, mixed>
Return values
array<string|int, mixed>getSafeHeaders()
HTTPヘッダを安全にフィルタリングされた配列として取得する。
public
getSafeHeaders(array<string|int, mixed> $headers) : array<string|int, mixed>
getSafeArray() の機密フィールドリストに headerSensitiveFields を加えた上でマスクする。
Parameters
- $headers : array<string|int, mixed>
Return values
array<string|int, mixed>getSensitiveFields()
設定されている機密フィールド一覧を取得
public
getSensitiveFields() : array<string|int, mixed>
Return values
array<string|int, mixed>registerSensitiveKeys()
マスク対象のキーを登録する(拡張アプリ・ユーザー拡張向けの公開 API)。
public
registerSensitiveKeys("field"|"header"|"urlQuery"|"arrayKey" $target, array<int, string>|string $keys) : self
既存の値を保ったまま足すため、コア側の既定リストを壊さずにプラグイン固有の
機密キーを登録できる。この Filter はコンテナに acms-logger-filter として singleton
登録され、ScrubProcessor と各ハンドラが同一インスタンスを参照するので、後から登録しても
以降のログすべて(監査ログ・アラートメール・エラーログ)に効く。
プラグインからは ServiceProvider::init() で次のように呼ぶ:
use Acms\Services\Facades\Application as App;
App::make('acms-logger-filter')
->registerSensitiveKeys('field', ['myplugin_client_secret']);
Why not: Hook(extendsLogFilter 等)の拡張ポイントは設けない。プラグインの Hook は
app テーブルを引いたあと main.php で attach されるため、LoggerServiceProvider の
登録時点では collection に入っておらず発火しても呼ばれない。プラグインの init() から
この公開メソッドを直接呼べば順序の問題が起きないので、フック機構を増やす必要がない。
Why not: setSensitiveFields() を拡張側に案内しない。あちらは配列をまるごと差し替えるため、 プラグインが呼ぶとコアの既定リスト(password 等)が消えて他の機密値が平文で残る。
Parameters
- $target : "field"|"header"|"urlQuery"|"arrayKey"
-
登録先のリスト
field: $_POST・context・extra のキーに部分一致(例:myplugin_secret)header: HTTP ヘッダ名に部分一致(ハイフン区切りで指定する。例:x-myplugin-token)urlQuery: URL のクエリキーに完全一致(部分一致にすると識別子カラム名を巻き込むため)arrayKey: 配列キーに完全一致
- $keys : array<int, string>|string
-
登録するキー(1件なら文字列でも可)
Return values
selfscrubLogArray()
既存レコードの読み出し・ログ出力向けに配列を再帰的にスクラブする。
public
scrubLogArray(array<string|int, mixed> $data[, int $depth = 0 ]) : array<string|int, mixed>
部分一致マスク(sensitiveFields)・完全一致マスク(arrayKeyExactMatchKeys)・URL値を持つ キー(urlValueKeys)への scrubUrl() 適用のすべてを、ネストした配列にも再帰的に適用する。 AcmsLogger::warning('...', ['request' => $someArray]) のように1段包んだ context を渡す 拡張・プラグインがあるため、トップレベル限定では nested な salt/context/token 等が 素通りしてしまう。
Why not: urlValueKeys の値には scrubUrl($value, false) を使う。WebProcessor が注入する extra.url / extra.referrer は htmlspecialchars 前の生の値であり、scrubUrl() の既定(再度 htmlspecialchars する)を適用すると生の表現に '&' 等の HTML エンティティが literal 混入してしまうため。
Parameters
- $data : array<string|int, mixed>
- $depth : int = 0
Return values
array<string|int, mixed>scrubUrl()
URL のクエリ文字列から機密なトークン・認可コード等をスクラブし、パス上の メディアダウンロードハッシュ(無期限capability)もマスクする。
public
scrubUrl(string|null $url[, bool $reEscape = true ]) : string
REQUEST_URL / REFERER は htmlspecialchars(ENT_QUOTES) 済みで '&' が '&' に なっているため、クエリ区切りの検出前に htmlspecialchars_decode() で戻す (Services/Preview/Engine.php の shareUrlFormat() と同じ手当て)。処理後は再度 htmlspecialchars(ENT_QUOTES) して元の表現に戻す。これを怠ると (1) DB に生の '<' '"' '&' が 保存され将来テンプレート等に生出力した時点で stored XSS になり得る、(2) 書き込み時・ 読み出し時の両方で scrubUrl() を通すため二重デコードでクエリ値中の '&' がパラメータ区切りに 化ける、という2つの問題が起きる。
機密クエリは値だけを mask に置き換え、キー自体は残す(削除しない)。削除すると 「認証URLでアクセスされた」という監査上重要な事実そのものが消えてしまうため。
Parameters
- $url : string|null
- $reEscape : bool = true
-
処理後に htmlspecialchars(ENT_QUOTES) で再エスケープするか。 REQUEST_URL / REFERER のようにすでにエスケープ済みの値を渡す場合は true(既定)。 WebProcessor が注入する extra.url / extra.referrer のようにエスケープ前の生の値を渡す場合は false にする(true のままだと生の表現に '&' 等が混入し、監査ログ詳細やプレーンテキストの エラーログファイルに HTML エンティティが literal 表示されるノイズになる)。
Return values
stringsetMask()
マスク文字列を設定
public
setMask(string $mask) : self
Parameters
- $mask : string
Return values
selfsetSensitiveFields()
機密フィールドを一括設定
public
setSensitiveFields(array<string|int, mixed> $fields) : self
Parameters
- $fields : array<string|int, mixed>
Return values
selfunregisterSensitiveKeys()
registerSensitiveKeys() で登録したキーを取り消す。
public
unregisterSensitiveKeys("field"|"header"|"urlQuery"|"arrayKey" $target, array<int, string>|string $keys) : self
自分が登録したキーを撤回するための API。コアの既定リストにあるキーも指定すれば外せるが、 外すと本来隠れていた値が平文でログに残るため、自分で登録したキー以外は渡さないこと。
Parameters
- $target : "field"|"header"|"urlQuery"|"arrayKey"
-
取り消し先のリスト(registerSensitiveKeys() 参照)
- $keys : array<int, string>|string
-
取り消すキー(1件なら文字列でも可)
Return values
selffilterArray()
配列を再帰的にフィルタリング
protected
filterArray(array<string|int, mixed> $data, array<string|int, mixed> $sensitiveFields[, int $depth = 0 ]) : array<string|int, mixed>
Parameters
- $data : array<string|int, mixed>
- $sensitiveFields : array<string|int, mixed>
- $depth : int = 0
Return values
array<string|int, mixed>getMaskedValue()
マスクされた値を取得
private
getMaskedValue() : string
Return values
stringgetSensitiveKeyList()
target に対応する現在のリストを返す。未知の target なら null。
private
getSensitiveKeyList(string $target) : array<int, string>|null
Why not: プロパティを可変名($this->$name)で触らない。対象を列挙して static に解決する ことで、想定外の target 文字列で無関係なプロパティを読み書きされる余地をなくす。
Why not: 未知の target で例外を投げない。ログ出力の設定ミスで例外を投げると、 本来記録すべきログそのものを落とすことになるため、呼び出し側で黙って捨てる。
Parameters
- $target : string
Return values
array<int, string>|nullisExactMatchKey()
キーが配列キー用の完全一致マスク対象か判定する
private
isExactMatchKey(string $key) : bool
Parameters
- $key : string
Return values
boolisSensitiveField()
フィールド名が機密情報に該当するかチェック(部分一致)
private
isSensitiveField(string|int $fieldName, array<string|int, mixed> $sensitiveFields) : bool
Parameters
- $fieldName : string|int
- $sensitiveFields : array<string|int, mixed>
Return values
boolisUrlValueKey()
キーがURL値を持つキーか判定する
private
isUrlValueKey(string $key) : bool
Parameters
- $key : string
Return values
boolnormalizeSensitiveKeys()
登録・取り消しで受け取ったキーを文字列のリストへ正規化する。
private
normalizeSensitiveKeys(array<int, string>|string $keys) : array<int, string>
Parameters
- $keys : array<int, string>|string
Return values
array<int, string>scrubMediaDownloadHash()
media-download/{mid}/{hash}/ のパスに含まれるハッシュ部分をマスクする。
private
scrubMediaDownloadHash(string $url) : string
このハッシュは $mid と固定ソルトだけから決まる無期限capabilityで、クエリ文字列では ないため scrubUrl() のクエリ用パターンでは届かない。パス専用に別途処理する。
Parameters
- $url : string
Return values
stringsetSensitiveKeyList()
target に対応するリストを差し替える。未知の target は何もしない。
private
setSensitiveKeyList(string $target, array<int, string> $keys) : void
Parameters
- $target : string
- $keys : array<int, string>