1
0
Fork 0

feat: validate script mapping paths

This commit is contained in:
thibaud-leclere 2026-05-05 09:50:55 +02:00
parent bca325cc07
commit 4dc27cb86f
2 changed files with 92 additions and 0 deletions

View file

@ -0,0 +1,44 @@
<?php
namespace App\Service;
use InvalidArgumentException;
final class PathNormalizer
{
public function publicPath(string $path): string
{
$normalized = $this->relativePath($path);
if (!str_ends_with($normalized, '.sh')) {
throw new InvalidArgumentException('Public path must end with .sh.');
}
return $normalized;
}
public function repositoryPath(string $path): string
{
return $this->relativePath($path);
}
private function relativePath(string $path): string
{
$path = trim(str_replace('\\', '/', $path));
$path = ltrim($path, '/');
if ($path === '') {
throw new InvalidArgumentException('Path cannot be empty.');
}
$segments = explode('/', $path);
foreach ($segments as $segment) {
if ($segment === '' || $segment === '.' || $segment === '..') {
throw new InvalidArgumentException('Path contains an invalid segment.');
}
}
return implode('/', $segments);
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace App\Tests\Service;
use App\Service\PathNormalizer;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
final class PathNormalizerTest extends TestCase
{
private PathNormalizer $normalizer;
protected function setUp(): void
{
$this->normalizer = new PathNormalizer();
}
public function testPublicPathIsNormalized(): void
{
self::assertSame('mcp/graylog/install.sh', $this->normalizer->publicPath('/mcp/graylog/install.sh'));
}
public function testPublicPathMustEndWithShellExtension(): void
{
$this->expectException(InvalidArgumentException::class);
$this->normalizer->publicPath('mcp/graylog/install.txt');
}
public function testPublicPathRejectsTraversal(): void
{
$this->expectException(InvalidArgumentException::class);
$this->normalizer->publicPath('mcp/../install.sh');
}
public function testRepositoryPathAllowsNestedFileWithoutShellRequirement(): void
{
self::assertSame('scripts/install', $this->normalizer->repositoryPath('/scripts/install'));
}
public function testRepositoryPathRejectsTraversal(): void
{
$this->expectException(InvalidArgumentException::class);
$this->normalizer->repositoryPath('../install.sh');
}
}