nextcloud/server · warning · OCSBadRequestException
Untrusted media source
Error message
Untrusted media source
What it means
OCSBadRequestException (HTTP 400) thrown by DiscoverController::mediaImage when checkCanDownloadMedia($fileName) returns false. The discover-media proxy only fetches remote media from trusted sources, so a media URL outside the allowlist is rejected before any HTTP request is made — this is a deliberate SSRF guard, logged as a warning ('Tried to load media files ... from untrusted source').
Source
Thrown at apps/appstore/lib/Controller/DiscoverController.php:127
if ($file === false) {
$user = $session->getUser();
// this route is not public thus we can assume a user is logged-in
assert($user !== null);
// Register a user request to throttle fetching external data
// this will prevent using the server for DoS of other systems.
$limiter->registerUserRequest(
'settings-discover-media',
// allow up to 24 media requests per hour
// this should be a sane default when a completely new section is loaded
// keep in mind browsers request all files from a source-set
24,
60 * 60,
$user,
);
if (!$this->checkCanDownloadMedia($fileName)) {
$this->logger->warning('Tried to load media files for app discover section from untrusted source');
throw new OCSBadRequestException('Untrusted media source');
}
try {
$client = $this->clientService->newClient();
$fileResponse = $client->get($fileName);
$contentType = $fileResponse->getHeader('Content-Type');
$extension = $info['extension'] ?? '';
$file = $folder->newFile($hashName . '.' . base64_encode($contentType) . '.' . $extension, $fileResponse->getBody());
} catch (\Throwable $e) {
$this->logger->warning('Could not load media file for app discover section', ['media_src' => $fileName, 'exception' => $e]);
throw new OCSNotFoundException('Media file not found');
}
} else {
// File was found so we can get the content type from the file name
$contentType = base64_decode(explode('.', $file->getName())[1] ?? '');
}
$response = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => $contentType]);View on GitHub (pinned to ecdeb153ff)
Solutions
- Use media URLs that come from the official app store data (they are inside the trusted source list)
- If you operate a custom app store, make sure its media domain is included in the server's trusted source configuration for discover media
- As an app publisher, host release media on the appstore-provided locations rather than arbitrary hosts
- Do not attempt to use this endpoint as a generic image proxy — by design it will 400
Defensive patterns
Strategy: validation
Validate before calling
// Sender/publisher side: only reference trusted appstore media hosts
const TRUSTED = ['cdn.nextcloud.com', 'apps.nextcloud.com']
const host = new URL(mediaSrc).hostname
if (!TRUSTED.includes(host)) {
// do not use this URL as media_src — the server will reject it with 'Untrusted media source'
} Try / catch
try {
const blob = await axios.get(mediaProxyUrl, { params: { fileName: mediaSrc } })
} catch (e) {
if (e?.response?.status === 400) {
// 'Untrusted media source' — SSRF guard: use a trusted appstore URL or adjust the server's trusted-source config
} else throw e
} Prevention
- Never use the discover media endpoint as a generic proxy — it validates sources by design
- App publishers: host release media where the appstore serves it from
- Custom app store operators: ensure your media domain is in the server's trusted sources
When it happens
Trigger: GET the discover media endpoint with a media_src whose host is not in the trusted set: an app release advertising screenshots on a custom/renamed CDN, a self-hosted or third-party app store whose domain is not allowlisted, or a manipulated request passing an arbitrary URL as fileName.
Common situations: Third-party appstore deployments whose media hosts are unknown to the server; app developers pointing media_src at their own servers; Nextcloud version changes tightening the allowlist after an upgrade.
Related errors
- Media file not found
- could not enable app
- could not disable app
- could not remove app
- could not update app
AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17).
Data as JSON: /api/errors/e0dcb71d0da58c00.
Report an issue: GitHub.