Nextcloud allows you to host your own app store and then integrate it into your Nextcloud instance.
To do this, it is necessary to set 2 parameters in the config file. Your own app store must meet some requirements: There must be a apps.json, a categories.json and ideally a discover.json. These JSON files must be structured according to a certain scheme.
I have already successfully tested 2 of my own app stores - one static and one with a “real” app store installed as Docker. Both work perfectly, except for the fact that pictures of the discover.json are not displayed.
After some research, I found out why it was. It is due to the hard-coded ban on media files as soon as they do not come from the nextcloud domain, or from the nextcloud github domain.
However, this security measure limits the use of your own app store extremely, as the Discover page must be displayed purely text-based and without images/videos.
My opinion:
If the custom app store is already defined in appstoreurl, the administrator of the instance has explicitly trusted it to serve executable code (app packages). Trusting the same domain for static media assets does not expand the attack surface.
If you already have experience with a custom app store and have the same problem, I’d like to bring my feature request to your attention and ask you to put a thumbs up there to show support for this feature.
offen 11:09AM - 31 Jul 26 UTC
enhancement
0. Needs triage
feature: apps management
> [!TIP]
> ### Help move this idea forward
> * Use the 👍 reaction to show suppor… t for this feature.
> * Avoid commenting unless you have relevant information to add; unnecessary comments create noise for subscribers.
> * Subscribe to receive notifications about status changes and new comments.
---
**Is your feature request related to a problem? Please describe.**
When using a custom app store via the official appstoreurl configuration parameter (as documented in the [Nextcloud Admin Manual](https://docs.nextcloud.com/server/stable/admin_manual/apps_management.html)), app media assets (icons, banners, screenshots) fail to load with a 400 Bad Request.
This happens because
| Nextcloud < 34 | Nextcloud >= 34 |
|-----------|-----------|
| OCA\Settings\Controller\AppServiceController::checkCanDownloadMedia | OCA\Appstore\Controller\DiscoverController:: checkCanDownloadMedia |
| App settings | App appstore |
the method checkCanDownloadMedia(string $filename) enforces a hardcoded whitelist that only allows nextcloud.com and specific path patterns on github.com / raw.githubusercontent.com.
As a result, custom app stores hosted on other domains or even on the same host as Nextcloud itself cannot render any visual assets in the Nextcloud Management UI.
**Describe the solution you'd like**
checkCanDownloadMedia() should dynamically allow media downloads from valid custom app store sources.
Possible approaches:
Automatically trust the host from appstoreurl: Parse appstoreurl from system config and add its host/domain to the allowed list.
Or support a config parameter: Introduce a system config option (e.g., appstore.allowed_media_hosts or appstore_allowed_hosts) in config.php that admins can populate with trusted domains.
Allow same-host media: Allow media loading from the instance's own domain or relative URLs.
**Describe alternatives you've considered**
Local core patching: Manually editing checkCanDownloadMedia() to add custom hostnames. This is unreliable as changes are overwritten on every Nextcloud update.
Hosting assets on GitHub under /nextcloud/ path: Not viable for private/enterprise app stores or self-hosted Git instances (e.g., Forgejo/Gitea/GitLab).
**Additional context**
According to the [official Nextcloud Admin Manual](https://docs.nextcloud.com/server/stable/admin_manual/apps_management.html):
> "Using a self hosted apps store: Enables the installation of apps from a self hosted apps store. [...] Set the appstoreurl to the URL of your Nextcloud apps store."
Since hosting a custom app store is an officially supported feature, the UI media proxy should seamlessly support custom domains specified by the administrator.
Affected code location:
| for Nextcloud < 34 | for Nextcloud >= 34 |
|-----------|-----------|
| apps/settings/lib/Controller/AppServiceController.php inside checkCanDownloadMedia() | apps/appstore/lib/Controller/DiscoverController.php inside checkCanDownloadMedia() |
AppServiceController:
```
private function checkCanDownloadMedia(string $filename): bool {
$urlInfo = parse_url($filename);
if (!isset($urlInfo['host']) || !isset($urlInfo['path'])) {
return false;
}
// Always allowed hosts
if ($urlInfo['host'] === 'nextcloud.com') {
return true;
}
// Hosts that need further verification
// Github is only allowed if from our organization
$ALLOWED_HOSTS = ['github.com', 'raw.githubusercontent.com'];
if (!in_array($urlInfo['host'], $ALLOWED_HOSTS)) {
return false;
}
if (str_starts_with($urlInfo['path'], '/nextcloud/') || str_starts_with($urlInfo['path'], '/nextcloud-gmbh/')) {
return true;
}
return false;
}
```
DiscoverController:
```
private function checkCanDownloadMedia(string $filename): bool {
$urlInfo = parse_url($filename);
if (!isset($urlInfo['host']) || !isset($urlInfo['path'])) {
return false;
}
// Always allowed hosts
if ($urlInfo['host'] === 'nextcloud.com') {
return true;
}
// Hosts that need further verification
// Github is only allowed if from our organization
$ALLOWED_HOSTS = ['github.com', 'raw.githubusercontent.com'];
if (!in_array($urlInfo['host'], $ALLOWED_HOSTS, true)) {
return false;
}
return str_starts_with($urlInfo['path'], '/nextcloud/') || str_starts_with($urlInfo['path'], '/nextcloud-gmbh/');
}
```
could be changed to:
```
private function checkCanDownloadMedia(string $filename): bool {
$urlInfo = parse_url($filename);
if (!isset($urlInfo['host']) || !isset($urlInfo['path'])) {
return false;
}
// Always allowed hosts
if ($urlInfo['host'] === 'nextcloud.com') {
return true;
}
// this is NEW
// if this method is in DiscoverController:
// you have to use OCP\IConfig; within the Controller's Import-Statements
// and you have to add private IConfig $config, to the __constuct method's parameters
$appstoreurl = $this->config->getSystemValueString('appstoreurl', 'https://apps.nextcloud.com/api/v1');
$appstoreurlInfo = parse_url($appstoreurl);
$appstoreurlHost = $appstoreurlInfo['host'] ?? null;
if ($appstoreurlHost !== null && $urlInfo['host'] === $appstoreurlHost) {
return true;
}
// this is NEW
// Hosts that need further verification
// Github is only allowed if from our organization
$ALLOWED_HOSTS = ['github.com', 'raw.githubusercontent.com'];
if (!in_array($urlInfo['host'], $ALLOWED_HOSTS, true)) {
return false;
}
return str_starts_with($urlInfo['path'], '/nextcloud/') || str_starts_with($urlInfo['path'], '/nextcloud-gmbh/');
}
```
**Security considerations / Clarification:**
"Allowing custom media hosts does not introduce new SSRF or security risks if aligned with existing admin trust:
1. If the host is already defined in appstoreurl, the administrator has explicitly trusted it to serve executable code (app packages). Trusting the same domain for static media assets does not expand the attack surface.
2. If implemented via an explicit config.php parameter (e.g., appstore.allowed_media_hosts), only instance administrators with write access to the server's configuration can whitelist additional domains. Since administrators already hold full control over the system, this does not grant unauthorized external actors any control over the proxy."
Best regards
zomtec2311