- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
272 lines
9.1 KiB
PHP
272 lines
9.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Framework\Waf\Feedback;
|
|
|
|
use App\Framework\Logging\Logger;
|
|
use App\Framework\Waf\DetectionCategory;
|
|
use App\Framework\Waf\DetectionSeverity;
|
|
use App\Framework\Waf\Feedback\FeedbackLearningService;
|
|
use App\Framework\Waf\Feedback\FeedbackService;
|
|
use App\Framework\Waf\MachineLearning\ValueObjects\ModelAdjustment;
|
|
use App\Framework\Waf\ValueObjects\Detection;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Integration tests for the WAF feedback system
|
|
*/
|
|
class FeedbackIntegrationTest extends TestCase
|
|
{
|
|
private TestClock $clock;
|
|
|
|
private TestMachineLearningEngine $mlEngine;
|
|
|
|
private InMemoryFeedbackRepository $repository;
|
|
|
|
private Logger $logger;
|
|
|
|
private FeedbackService $feedbackService;
|
|
|
|
private FeedbackLearningService $learningService;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->clock = new TestClock('2025-08-04 18:39:00');
|
|
$this->mlEngine = new TestMachineLearningEngine();
|
|
$this->repository = new InMemoryFeedbackRepository();
|
|
$this->logger = $this->createMock(Logger::class);
|
|
|
|
$this->feedbackService = new FeedbackService(
|
|
$this->repository,
|
|
$this->clock,
|
|
$this->logger
|
|
);
|
|
|
|
$this->learningService = new FeedbackLearningService(
|
|
$this->repository,
|
|
$this->mlEngine,
|
|
$this->clock,
|
|
$this->logger,
|
|
3, // Lower threshold for testing
|
|
0.5 // Higher learning rate for testing
|
|
);
|
|
|
|
// Set up ML engine to return success for adjustments
|
|
$this->mlEngine->withApplyFeedbackAdjustmentsResult([
|
|
'success' => true,
|
|
'applied_count' => 0,
|
|
'failed_count' => 0,
|
|
'results' => [],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Test the complete feedback loop:
|
|
* 1. Submit feedback
|
|
* 2. Learn from feedback
|
|
* 3. Apply model adjustments
|
|
*/
|
|
public function testCompleteFeedbackLoop(): void
|
|
{
|
|
// 1. Submit feedback
|
|
$this->submitTestFeedback();
|
|
|
|
// Verify feedback was stored
|
|
$this->assertCount(5, $this->repository->getAllFeedback());
|
|
|
|
// 2. Learn from feedback
|
|
$learningResult = $this->learningService->learnFromFeedback();
|
|
|
|
// Verify learning result
|
|
$this->assertTrue($learningResult['success']);
|
|
$this->assertGreaterThan(0, $learningResult['total_adjustments_applied']);
|
|
|
|
// 3. Verify model adjustments were applied
|
|
$receivedAdjustments = $this->mlEngine->getReceivedAdjustments();
|
|
$this->assertNotEmpty($receivedAdjustments);
|
|
|
|
// Verify SQL_INJECTION adjustments
|
|
$sqlInjectionAdjustment = $this->findAdjustmentForCategory($receivedAdjustments, DetectionCategory::SQL_INJECTION);
|
|
$this->assertNotNull($sqlInjectionAdjustment);
|
|
|
|
// For false positives, threshold should be increased (positive adjustment)
|
|
$this->assertGreaterThan(0, $sqlInjectionAdjustment->thresholdAdjustment->getValue());
|
|
|
|
// For false positives, confidence should be decreased (negative adjustment)
|
|
$this->assertLessThan(0, $sqlInjectionAdjustment->confidenceAdjustment->getValue());
|
|
}
|
|
|
|
/**
|
|
* Test that severity adjustments are properly processed
|
|
*/
|
|
public function testSeverityAdjustments(): void
|
|
{
|
|
// Submit severity adjustment feedback
|
|
$this->feedbackService->submitSeverityAdjustment(
|
|
'detection1',
|
|
'test-user',
|
|
'This should be higher severity',
|
|
DetectionCategory::PATH_TRAVERSAL,
|
|
DetectionSeverity::MEDIUM,
|
|
DetectionSeverity::HIGH,
|
|
['test' => true]
|
|
);
|
|
|
|
$this->feedbackService->submitSeverityAdjustment(
|
|
'detection2',
|
|
'test-user',
|
|
'This should be higher severity',
|
|
DetectionCategory::PATH_TRAVERSAL,
|
|
DetectionSeverity::MEDIUM,
|
|
DetectionSeverity::HIGH,
|
|
['test' => true]
|
|
);
|
|
|
|
$this->feedbackService->submitSeverityAdjustment(
|
|
'detection3',
|
|
'test-user',
|
|
'This should be higher severity',
|
|
DetectionCategory::PATH_TRAVERSAL,
|
|
DetectionSeverity::MEDIUM,
|
|
DetectionSeverity::HIGH,
|
|
['test' => true]
|
|
);
|
|
|
|
// Learn from feedback
|
|
$learningResult = $this->learningService->learnFromFeedback();
|
|
|
|
// Verify learning result
|
|
$this->assertTrue($learningResult['success']);
|
|
|
|
// Verify model adjustments were applied
|
|
$receivedAdjustments = $this->mlEngine->getReceivedAdjustments();
|
|
|
|
// Verify PATH_TRAVERSAL adjustments
|
|
$pathTraversalAdjustment = $this->findAdjustmentForCategory($receivedAdjustments, DetectionCategory::PATH_TRAVERSAL);
|
|
$this->assertNotNull($pathTraversalAdjustment);
|
|
|
|
// For severity increase, confidence should be increased (positive adjustment)
|
|
$this->assertGreaterThan(0, $pathTraversalAdjustment->confidenceAdjustment->getValue());
|
|
|
|
// For severity adjustments, threshold should not be adjusted
|
|
$this->assertEquals(0, $pathTraversalAdjustment->thresholdAdjustment->getValue());
|
|
}
|
|
|
|
/**
|
|
* Test that false negative feedback is properly processed
|
|
*/
|
|
public function testFalseNegativeFeedback(): void
|
|
{
|
|
// Submit false negative feedback
|
|
$this->feedbackService->submitFalseNegative(
|
|
'detection1',
|
|
'test-user',
|
|
'This should have been detected',
|
|
DetectionCategory::COMMAND_INJECTION,
|
|
DetectionSeverity::HIGH,
|
|
['test' => true]
|
|
);
|
|
|
|
$this->feedbackService->submitFalseNegative(
|
|
'detection2',
|
|
'test-user',
|
|
'This should have been detected',
|
|
DetectionCategory::COMMAND_INJECTION,
|
|
DetectionSeverity::HIGH,
|
|
['test' => true]
|
|
);
|
|
|
|
// Learn from feedback
|
|
$learningResult = $this->learningService->learnFromFeedback();
|
|
|
|
// Verify learning result
|
|
$this->assertTrue($learningResult['success']);
|
|
|
|
// Verify model adjustments were applied
|
|
$receivedAdjustments = $this->mlEngine->getReceivedAdjustments();
|
|
|
|
// Verify COMMAND_INJECTION adjustments
|
|
$commandInjectionAdjustment = $this->findAdjustmentForCategory($receivedAdjustments, DetectionCategory::COMMAND_INJECTION);
|
|
$this->assertNotNull($commandInjectionAdjustment);
|
|
|
|
// For false negatives, threshold should be decreased (negative adjustment)
|
|
$this->assertLessThan(0, $commandInjectionAdjustment->thresholdAdjustment->getValue());
|
|
|
|
// For false negatives, confidence should be increased (positive adjustment)
|
|
$this->assertGreaterThan(0, $commandInjectionAdjustment->confidenceAdjustment->getValue());
|
|
}
|
|
|
|
/**
|
|
* Submit test feedback for various scenarios
|
|
*/
|
|
private function submitTestFeedback(): void
|
|
{
|
|
// Submit false positive feedback for SQL_INJECTION
|
|
$this->feedbackService->submitFalsePositive(
|
|
'detection1',
|
|
'test-user',
|
|
'This is a legitimate query',
|
|
DetectionCategory::SQL_INJECTION,
|
|
DetectionSeverity::HIGH,
|
|
['query' => 'SELECT * FROM users WHERE id = 1']
|
|
);
|
|
|
|
$this->feedbackService->submitFalsePositive(
|
|
'detection2',
|
|
'test-user',
|
|
'Another legitimate query',
|
|
DetectionCategory::SQL_INJECTION,
|
|
DetectionSeverity::HIGH,
|
|
['query' => 'SELECT * FROM products WHERE category_id = 2']
|
|
);
|
|
|
|
$this->feedbackService->submitFalsePositive(
|
|
'detection3',
|
|
'test-user',
|
|
'Yet another legitimate query',
|
|
DetectionCategory::SQL_INJECTION,
|
|
DetectionSeverity::HIGH,
|
|
['query' => 'SELECT * FROM orders WHERE customer_id = 3']
|
|
);
|
|
|
|
// Submit correct detection feedback
|
|
$this->feedbackService->submitCorrectDetection(
|
|
'detection4',
|
|
'test-user',
|
|
'This is indeed an XSS attempt',
|
|
DetectionCategory::XSS,
|
|
DetectionSeverity::CRITICAL,
|
|
['payload' => '<script>alert("XSS")</script>']
|
|
);
|
|
|
|
// Submit false negative feedback
|
|
$this->feedbackService->submitFalseNegative(
|
|
'detection5',
|
|
'test-user',
|
|
'This should have been detected as XSS',
|
|
DetectionCategory::XSS,
|
|
DetectionSeverity::HIGH,
|
|
['payload' => '<img src="x" onerror="alert(\'XSS\')">']
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Find an adjustment for a specific category in an array of adjustments
|
|
*
|
|
* @param array<string, ModelAdjustment> $adjustments Array of adjustments
|
|
* @param DetectionCategory $category Category to find
|
|
* @return ModelAdjustment|null The found adjustment or null
|
|
*/
|
|
private function findAdjustmentForCategory(array $adjustments, DetectionCategory $category): ?ModelAdjustment
|
|
{
|
|
foreach ($adjustments as $adjustment) {
|
|
if ($adjustment->category === $category) {
|
|
return $adjustment;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|