collectFilesByExtension('pages/api/v1', ['php']); self::assertNotSame([], $apiFiles, 'Expected API actions under pages/api/v1.'); $patterns = [ '/\bSession::/' => 'Session::*', '/\bGuard::/' => 'Guard::*', '/\bsetcookie\s*\(/i' => 'setcookie(...)', '/\bsession_start\s*\(/i' => 'session_start(...)', '/remember[-_ ]?me/i' => 'remember-me', '/session[_ ]?timeout/i' => 'session-timeout', ]; $violations = []; foreach ($apiFiles as $relativePath) { $content = $this->readProjectFile($relativePath); foreach ($patterns as $regex => $label) { if (preg_match($regex, $content)) { $violations[] = "{$relativePath} uses {$label}"; } } } sort($violations); self::assertSame( [], $violations, "API action files contain web-session flow references (GR-SEC-007):\n" . implode("\n", $violations) ); } public function testApiBootstrapDoesNotStartSessionOrRememberMeFlow(): void { $content = $this->readProjectFile('lib/Http/ApiBootstrap.php'); self::assertStringNotContainsString('Session::start(', $content); self::assertStringNotContainsString('RememberMe', $content); self::assertStringNotContainsString('CookieStore', $content); self::assertStringNotContainsString('session timeout', strtolower($content)); } /** * @param list $extensions * @return list */ private function collectFilesByExtension(string $relativeDir, array $extensions): array { $root = $this->projectRootPath(); $dir = $root . '/' . $relativeDir; if (!is_dir($dir)) { return []; } $result = []; $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS) ); foreach ($iterator as $file) { if (!$file->isFile()) { continue; } if (!in_array(strtolower($file->getExtension()), $extensions, true)) { continue; } $result[] = str_replace($root . '/', '', $file->getPathname()); } sort($result); return $result; } }