From 17a446239c63f522a173e9b6c88e37bbb15ba035 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Wed, 19 Aug 2026 15:08:49 -0400 Subject: [PATCH 1/2] feat(ui): add sensitive queue-payload redaction and recognize Yii3 queue producers for Dump, Mail, and Queue parity. --- CHANGELOG.md | 1 + src/Helper/SensitiveDataRedactor.php | 57 +++++++++++++++++++ src/Panel/Queue/QueueDriverDetector.php | 9 +++ tests/Helper/SensitiveDataRedactorTest.php | 51 +++++++++++++++++ tests/Panel/Queue/QueueDriverDetectorTest.php | 14 +++++ 5 files changed, 132 insertions(+) create mode 100644 src/Helper/SensitiveDataRedactor.php create mode 100644 tests/Helper/SensitiveDataRedactorTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 088933e..9ca5c0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,3 +21,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - feat(ui): add shared profiler normalization and Timeline rendering contracts. - feat(tests): add unit tests for panel snapshots and enhance existing test coverage. - feat(ui): share User guest and RBAC section rendering and support selecting filtered tabs. +- feat(ui): add sensitive queue-payload redaction and recognize Yii3 queue producers for Dump, Mail, and Queue parity. diff --git a/src/Helper/SensitiveDataRedactor.php b/src/Helper/SensitiveDataRedactor.php new file mode 100644 index 0000000..72612ef --- /dev/null +++ b/src/Helper/SensitiveDataRedactor.php @@ -0,0 +1,57 @@ + $value Value tree to sanitize. + * @param list $sensitiveKeys Exact key names to redact. + * + * @return array Sanitized tree with keys and non-sensitive values preserved. + */ + public static function redact(array $value, array $sensitiveKeys): array + { + $keys = array_fill_keys(array_map(strtolower(...), $sensitiveKeys), true); + + return self::walk($value, $keys); + } + + /** + * @param array $value + * @param array $sensitiveKeys + * + * @return array + */ + private static function walk(array $value, array $sensitiveKeys): array + { + $redacted = []; + + foreach ($value as $key => $item) { + if (is_string($key) && isset($sensitiveKeys[strtolower($key)])) { + $redacted[$key] = self::PLACEHOLDER; + + continue; + } + + $redacted[$key] = is_array($item) ? self::walk($item, $sensitiveKeys) : $item; + } + + return $redacted; + } +} diff --git a/src/Panel/Queue/QueueDriverDetector.php b/src/Panel/Queue/QueueDriverDetector.php index 6ebbd98..c33b4bd 100644 --- a/src/Panel/Queue/QueueDriverDetector.php +++ b/src/Panel/Queue/QueueDriverDetector.php @@ -8,6 +8,7 @@ use function count; use function explode; use function in_array; +use function str_ends_with; use function strtolower; use function ucfirst; @@ -62,6 +63,14 @@ public static function detect(string $fqcn): array return self::$cache[$fqcn]; } + if (str_ends_with($fqcn, '\\SyncQueueProducer')) { + return self::$cache[$fqcn] = ['Sync', false]; + } + + if (str_ends_with($fqcn, '\\AsyncQueueProducer')) { + return self::$cache[$fqcn] = ['Async', true]; + } + $token = self::extractDriverToken($fqcn); $name = array_key_exists($token, self::DRIVER_LABELS) diff --git a/tests/Helper/SensitiveDataRedactorTest.php b/tests/Helper/SensitiveDataRedactorTest.php new file mode 100644 index 0000000..f0e962e --- /dev/null +++ b/tests/Helper/SensitiveDataRedactorTest.php @@ -0,0 +1,51 @@ + 'first', 'user' => ['name' => 'Ada']], + SensitiveDataRedactor::redact([0 => 'first', 'user' => ['name' => 'Ada']], ['password']), + 'Unmatched values and numeric keys must remain unchanged.', + ); + } + + public function testRedactReplacesConfiguredKeysCaseInsensitivelyAtEveryDepth(): void + { + self::assertSame( + [ + 'Password' => SensitiveDataRedactor::PLACEHOLDER, + 'nested' => [ + 'accessToken' => SensitiveDataRedactor::PLACEHOLDER, + 'tokenSuffix' => 'visible', + ], + ], + SensitiveDataRedactor::redact( + [ + 'Password' => 'secret', + 'nested' => [ + 'accessToken' => 'token', + 'tokenSuffix' => 'visible', + ], + ], + ['password', 'ACCESSTOKEN'], + ), + 'Configured keys must match exactly and without case sensitivity throughout nested arrays.', + ); + } +} diff --git a/tests/Panel/Queue/QueueDriverDetectorTest.php b/tests/Panel/Queue/QueueDriverDetectorTest.php index 64956b5..e847931 100644 --- a/tests/Panel/Queue/QueueDriverDetectorTest.php +++ b/tests/Panel/Queue/QueueDriverDetectorTest.php @@ -84,6 +84,20 @@ public function testDetectFallsBackToLowercasedFqcnForSingleSegmentClass(): void ); } + public function testDetectRecognizesYii3ProducerClasses(): void + { + self::assertSame( + ['Sync', false], + QueueDriverDetector::detect('Yiisoft\\Queue\\SyncQueueProducer'), + 'Yii3 synchronous producers must use the shared Sync label and in-process flag.', + ); + self::assertSame( + ['Async', true], + QueueDriverDetector::detect('Yiisoft\\Queue\\AsyncQueueProducer'), + 'Yii3 asynchronous producers must surface their async execution model.', + ); + } + public function testDetectReturnsUnknownForEmptyFqcn(): void { self::setDetectorCache( From a756052f55a5ab2ae08db629b5284e8bd49e342d Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Wed, 19 Aug 2026 15:19:14 -0400 Subject: [PATCH 2/2] Apply fixed coderabbitai review. --- src/Panel/Queue/QueueDriverDetector.php | 5 +++-- tests/Panel/Queue/QueueDriverDetectorTest.php | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Panel/Queue/QueueDriverDetector.php b/src/Panel/Queue/QueueDriverDetector.php index c33b4bd..b39e6ca 100644 --- a/src/Panel/Queue/QueueDriverDetector.php +++ b/src/Panel/Queue/QueueDriverDetector.php @@ -9,6 +9,7 @@ use function explode; use function in_array; use function str_ends_with; +use function str_starts_with; use function strtolower; use function ucfirst; @@ -63,11 +64,11 @@ public static function detect(string $fqcn): array return self::$cache[$fqcn]; } - if (str_ends_with($fqcn, '\\SyncQueueProducer')) { + if (str_starts_with($fqcn, 'Yiisoft\\Queue\\') && str_ends_with($fqcn, '\\SyncQueueProducer')) { return self::$cache[$fqcn] = ['Sync', false]; } - if (str_ends_with($fqcn, '\\AsyncQueueProducer')) { + if (str_starts_with($fqcn, 'Yiisoft\\Queue\\') && str_ends_with($fqcn, '\\AsyncQueueProducer')) { return self::$cache[$fqcn] = ['Async', true]; } diff --git a/tests/Panel/Queue/QueueDriverDetectorTest.php b/tests/Panel/Queue/QueueDriverDetectorTest.php index e847931..7a3ed6a 100644 --- a/tests/Panel/Queue/QueueDriverDetectorTest.php +++ b/tests/Panel/Queue/QueueDriverDetectorTest.php @@ -71,6 +71,20 @@ public function testDetectClassifiesSyncDriverAsRunInProcess(): void ); } + public function testDetectDoesNotClassifySameSuffixOutsideYii3Namespace(): void + { + self::assertSame( + ['Queue', true], + QueueDriverDetector::detect('App\\Queue\\SyncQueueProducer'), + 'Same-suffix producers outside Yiisoft Queue must retain generic driver detection.', + ); + self::assertSame( + ['Other', true], + QueueDriverDetector::detect('Vendor\\Other\\AsyncQueueProducer'), + 'Unrelated async-producer class names must not be classified as Yii3 producers.', + ); + } + public function testDetectFallsBackToLowercasedFqcnForSingleSegmentClass(): void { self::setDetectorCache(