diff --git a/.dockerignore b/.dockerignore index b227ce1c..a95b0736 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,8 @@ vendor .idea -.circleci \ No newline at end of file +.circleci + +# Local secrets and debug endpoints must not be copied into images. +.env +.env.* +info.php diff --git a/common/tests/unit/security/PublicDebugEndpointTest.php b/common/tests/unit/security/PublicDebugEndpointTest.php new file mode 100644 index 00000000..ddc0ccfd --- /dev/null +++ b/common/tests/unit/security/PublicDebugEndpointTest.php @@ -0,0 +1,72 @@ +collectPhpFiles($publicPaths) as $file) { + $source = file_get_contents($file); + + $this->assertDoesNotMatchRegularExpression( + '/\bphpinfo\s*\(/i', + $source, + $file . ' must not expose phpinfo output from a public document root.' + ); + } + } + + public function testDockerBuildContextExcludesLocalSecrets() + { + $root = dirname(__DIR__, 4); + $entries = array_map('trim', file($root . '/.dockerignore', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)); + $entries = array_values(array_filter($entries, static function ($entry) { + return $entry !== '' && strpos($entry, '#') !== 0; + })); + + $this->assertContains('.env', $entries); + $this->assertContains('.env.*', $entries); + $this->assertContains('info.php', $entries); + } + + private function collectPhpFiles(array $paths): array + { + $files = []; + + foreach ($paths as $path) { + if (is_file($path)) { + $files[] = $path; + continue; + } + + if (!is_dir($path)) { + continue; + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS) + ); + + foreach ($iterator as $file) { + if ($file->isFile() && strtolower($file->getExtension()) === 'php') { + $files[] = $file->getPathname(); + } + } + } + + return $files; + } +} diff --git a/info.php b/info.php deleted file mode 100644 index f6cefd0e..00000000 --- a/info.php +++ /dev/null @@ -1,2 +0,0 @@ -