Skip to content

Allow app media downloads from custom app store domains #62726

Description

@zomtec2311

Tip

Help move this idea forward

  • Use the 👍 reaction to show support 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), 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:

"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

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status
    To triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions