The previous split (MediaAttacher + MediaDownloader + UrlSafetyGuard interface + DnsUrlSafetyGuard impl + provider binding) was over-engineered for one production implementation. Roll it all back into a single MediaAttacher class and delegate to the existing `Workspace::addMediaFromPath` helper for storage + Media row creation (the same path the web upload flow uses). What's gone: - app/Services/Post/MediaDownloader.php - app/Services/Post/UrlSafetyGuard.php (interface) - app/Services/Post/DnsUrlSafetyGuard.php (impl) - tests/Unit/Services/Post/DnsUrlSafetyGuardTest.php - The provider bind in AppServiceProvider What's still defended: - Streaming download via Http::sink with a Guzzle progress callback that aborts mid-stream once MAX_BYTES is exceeded (no body buffered in PHP). - Redirects disabled so a 200→302 trick can't pivot to internal hosts. - IP-literal SSRF guard: rejects loopback / private / link-local / reserved ranges via FILTER_FLAG_NO_PRIV_RANGE | NO_RES_RANGE. DNS hostnames are accepted — finer SSRF (DNS rebinding etc.) is left to network-level egress controls. This is a deliberate simplification: the prior DNS resolution defense added a class + interface + provider binding for marginal gain when the realistic attack surface is hard-coded internal IPs. - Strict MIME allowlist (no SVG, no PDF, no application/*). - Lock-then-merge into post.media[] to avoid lost-update races. Tests bypass the SSRF check via the static `MediaAttacher::fakeUrlSafety()` called once in tests/TestCase::setUp — same idiom as Mail::fake / Bus::fake.
31 lines
646 B
PHP
31 lines
646 B
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests;
|
|
|
|
use App\Services\Post\MediaAttacher;
|
|
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
|
|
|
abstract class TestCase extends BaseTestCase
|
|
{
|
|
use CreatesApplication;
|
|
|
|
/**
|
|
* Indicates whether the default seeder should run before each test.
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $seed = true;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->withoutVite();
|
|
|
|
// Bypass the SSRF check during tests so Http::fake() with
|
|
// synthetic hosts like cdn.example.com isn't rejected.
|
|
MediaAttacher::fakeUrlSafety();
|
|
}
|
|
}
|