chore(agents): implement v2 hardening and enforcement-ready QA
This commit is contained in:
138
tests/Architecture/SecurityRemoteAssetContractTest.php
Normal file
138
tests/Architecture/SecurityRemoteAssetContractTest.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* GR-SEC-004: No external CDN/remote assets in app templates/pages/styles.
|
||||
*/
|
||||
final class SecurityRemoteAssetContractTest extends TestCase
|
||||
{
|
||||
use ProjectFileAssertionSupport;
|
||||
|
||||
public function testNoRemoteScriptOrStylesheetTagsInAppTemplatesAndPages(): void
|
||||
{
|
||||
$files = array_merge(
|
||||
$this->collectFilesByExtension('pages', ['php', 'phtml']),
|
||||
$this->collectFilesByExtension('templates', ['php', 'phtml']),
|
||||
$this->collectModuleFilesByExtension('pages', ['php', 'phtml']),
|
||||
$this->collectModuleFilesByExtension('templates', ['php', 'phtml'])
|
||||
);
|
||||
|
||||
$pattern = '/<(?:script|link)\b[^>]*\b(?:src|href)\s*=\s*["\']\s*(?:https?:)?\/\/(?!localhost|127\.0\.0\.1)/i';
|
||||
$violations = $this->findPatternViolations($files, $pattern);
|
||||
|
||||
self::assertSame(
|
||||
[],
|
||||
$violations,
|
||||
"Remote script/link asset references found (GR-SEC-004):\n" . implode("\n", $violations)
|
||||
);
|
||||
}
|
||||
|
||||
public function testNoRemoteCssImportsOrFontUrls(): void
|
||||
{
|
||||
$files = array_merge(
|
||||
$this->collectFilesByExtension('web/css', ['css']),
|
||||
$this->collectModuleFilesByExtension('web/css', ['css'])
|
||||
);
|
||||
|
||||
$importPattern = '/@import\s+(?:url\()?["\']?\s*(?:https?:)?\/\/(?!localhost|127\.0\.0\.1)/i';
|
||||
$fontPattern = '/url\(\s*["\']?\s*(?:https?:)?\/\/(?!localhost|127\.0\.0\.1).*?\.(?:woff2?|woff|ttf|otf|eot)\b/i';
|
||||
|
||||
$violations = array_merge(
|
||||
$this->findPatternViolations($files, $importPattern),
|
||||
$this->findPatternViolations($files, $fontPattern)
|
||||
);
|
||||
$violations = array_values(array_unique($violations));
|
||||
sort($violations);
|
||||
|
||||
self::assertSame(
|
||||
[],
|
||||
$violations,
|
||||
"Remote CSS imports/font URLs found (GR-SEC-004):\n" . implode("\n", $violations)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $extensions
|
||||
* @return list<string>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $extensions
|
||||
* @return list<string>
|
||||
*/
|
||||
private function collectModuleFilesByExtension(string $moduleSubDir, array $extensions): array
|
||||
{
|
||||
$root = $this->projectRootPath();
|
||||
$modulesDir = $root . '/modules';
|
||||
if (!is_dir($modulesDir)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
$entries = scandir($modulesDir) ?: [];
|
||||
foreach ($entries as $entry) {
|
||||
if ($entry === '.' || $entry === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dir = $modulesDir . '/' . $entry . '/' . $moduleSubDir;
|
||||
if (!is_dir($dir)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = array_merge($result, $this->collectFilesByExtension('modules/' . $entry . '/' . $moduleSubDir, $extensions));
|
||||
}
|
||||
|
||||
$result = array_values(array_unique($result));
|
||||
sort($result);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $files
|
||||
* @return list<string>
|
||||
*/
|
||||
private function findPatternViolations(array $files, string $pattern): array
|
||||
{
|
||||
$violations = [];
|
||||
foreach ($files as $relativePath) {
|
||||
$content = $this->readProjectFile($relativePath);
|
||||
if (preg_match($pattern, $content)) {
|
||||
$violations[] = $relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
sort($violations);
|
||||
return $violations;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user