Skip to content
13 changes: 13 additions & 0 deletions src/Tokens/FileTokenRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,19 @@ class FileTokenRepository extends TokenRepository
{
public function make(?string $token, string $handler, array $data = []): TokenContract
{
if ($token && ! $this->isValidTokenName($token)) {
throw new \InvalidArgumentException("Invalid token name [{$token}].");
}

return app()->makeWith(TokenContract::class, compact('token', 'handler', 'data'));
}

public function find(string $token): ?TokenContract
{
if (! $this->isValidTokenName($token)) {
return null;
}

$path = storage_path('statamic/tokens/'.$token.'.yaml');

if (! File::exists($path)) {
Expand Down Expand Up @@ -55,6 +63,11 @@ public function collectGarbage(): void
->each->delete();
}

private function isValidTokenName(string $token): bool
{
return (bool) preg_match('/^[A-Za-z0-9_-]+\z/', $token);
}

private function makeFromPath(string $path): FileToken
{
$yaml = YAML::file($path)->parse();
Expand Down
32 changes: 32 additions & 0 deletions tests/Tokens/TokenRepositoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Facades\Statamic\Tokens\Generator;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Contracts\Tokens\Token;
use Statamic\Facades\File;
Expand Down Expand Up @@ -129,6 +130,37 @@ public function attempting_to_find_a_non_existent_token_returns_null()
$this->assertNull($this->tokens->find('missing-token'));
}

#[Test]
public function it_prevents_path_traversal_in_find()
{
File::put(storage_path('statamic/evil.yaml'), "handler: 'Handler'\nexpires_at: 9999999999\ndata: []");

$this->assertNull($this->tokens->find('../evil'));
}

#[Test]
#[DataProvider('invalidTokenNameProvider')]
public function it_throws_when_making_a_token_with_an_invalid_name($token)
{
$this->expectException(\InvalidArgumentException::class);

$this->tokens->make($token, 'Handler');
}

public static function invalidTokenNameProvider()
{
return [
'parent traversal' => ['../evil'],
'backslash traversal' => ['..\\evil'],
'nested traversal' => ['foo/../../evil'],
'forward slash' => ['foo/evil'],
'dots only' => ['..'],
'absolute path' => ['/etc/passwd'],
'windows drive' => ['C:\\evil'],
'trailing newline' => ["evil\n"],
];
}

#[Test]
public function it_deletes_expired_tokens()
{
Expand Down
Loading