mirror of
https://github.com/R0m1k3/podcastic.git
synced 2026-10-24 15:38:03 +02:00
security: SSRF protection on user-supplied RSS URLs
Validate every RSS URL submitted via /api/podcasts/subscribe before fetching. Rejects non-http(s), localhost, private IPv4/IPv6 ranges, link-local, and cloud metadata endpoints (169.254.169.254). Resolves hostnames via DNS and re-checks every returned address. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9bf95b4ac9
commit
5497c89a99
2 files changed
+88
No files matched your search
@@ -5,6 +5,7 @@ import { UserSubscription } from '../models/UserSubscription';
|
||||
import { Episode } from '../models/Episode';
|
||||
import { podcastIndexService } from '../services/podcastIndexService';
|
||||
import { rssParserService } from '../services/rssParserService';
|
||||
import { validateExternalUrl } from '../utils/urlValidator';
|
||||
import { z } from 'zod';
|
||||
|
||||
const subscribeSchema = z.object({
|
||||
@@ -52,6 +53,13 @@ export const subscribe = async (req: Request, res: Response) => {
|
||||
|
||||
const { rssUrl } = subscribeSchema.parse(req.body);
|
||||
|
||||
// SSRF protection — block internal/private hosts
|
||||
try {
|
||||
await validateExternalUrl(rssUrl);
|
||||
} catch (err: any) {
|
||||
return res.status(400).json({ message: `URL refusée : ${err.message}` });
|
||||
}
|
||||
|
||||
// Check if podcast exists
|
||||
let podcast = await Podcast.findOne({ rssUrl });
|
||||
|
||||
|
||||
Reference in new issue
Block a user