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. |
||
|---|---|---|
| .. | ||
| Ai | ||
| Image | ||
| Media | ||
| Social | ||
| Unsplash | ||
| InstagramPublisherTest.php | ||
| LinkedInPublisherTest.php | ||
| XPublisherTest.php | ||
| YouTubePublisherTest.php | ||