Skip to content

ProtectedAssetAdapter::findRoot() bypasses parent's relative path resolution when using SS_PROTECTED_ASSETS_PATH env var #706

@micschk

Description

@micschk

Description

When configuring protected assets via the SS_PROTECTED_ASSETS_PATH environment variable with a relative path (e.g. ../restricted_assets), the path is used as-is without resolving it relative to BASE_PATH. This causes the protected assets directory to end up in an unexpected location because PHP resolves the relative path against its current working directory (cwd), which differs between web requests (public/) and CLI (project root or wherever the command was run from).

The parent class AssetAdapter::findRoot() has logic to handle relative paths (./BASE_PATH, ../dirname(BASE_PATH)), but ProtectedAssetAdapter::findRoot() bypasses this when the env var is set.

Steps to reproduce

  1. Set SS_PROTECTED_ASSETS_PATH="../restricted_assets" in .env
  2. Upload a file via the CMS
  3. The protected assets directory is created relative to PHP's cwd, not relative to BASE_PATH
    • Web request: resolves relative to public/ → ends up at project root (public/../restricted_assets)
    • CLI: resolves relative to wherever the command was run from

Expected behaviour

Relative paths in SS_PROTECTED_ASSETS_PATH should resolve relative to BASE_PATH, consistent with:

  • The AssetAdapter::findRoot() parent logic (which handles ./ and ../ substitution)
  • The YAML Injector configuration approach (which passes root through parent::findRoot() and works correctly)
  • The original implementation examples in Store protected assets outside of webroot silverstripe-framework#7710 which show relative paths like root: './assets'

Cause

In ProtectedAssetAdapter::findRoot(), when SS_PROTECTED_ASSETS_PATH is set, the method returns the raw env var value without calling parent::findRoot():

protected function findRoot($root)
{
    $path = Environment::getEnv('SS_PROTECTED_ASSETS_PATH');
    if ($path) {
        return $path;  // ← Returns raw value, bypasses parent's relative path resolution
    }

    // ...
    return parent::findRoot($root); // ← Only reached when env var is NOT set
}

The parent AssetAdapter::findRoot() contains the relative path handling:

protected function findRoot($root)
{
    // ...
    if (strpos($root ?? '', './') === 0) {
        return BASE_PATH . substr($root, 1);
    }
    if (strpos($root ?? '', '../') === 0) {
        return dirname(BASE_PATH) . substr($root, 2);
    }
    // ...
}

When using YAML Injector config (constructor arg), $root is passed through parent::findRoot() and relative paths resolve correctly. Only the env var code path has this issue.

Suggested fix

Pass the env var value through parent::findRoot() instead of returning it directly:

protected function findRoot($root)
{
    $path = Environment::getEnv('SS_PROTECTED_ASSETS_PATH');
    if ($path) {
        return parent::findRoot($path);
    }

    // ...
}

Workaround

Use an absolute path in SS_PROTECTED_ASSETS_PATH:

SS_PROTECTED_ASSETS_PATH="/var/www/mysite/restricted_assets"

Affects

All versions since SS4 (when protected assets were introduced via silverstripe/silverstripe-framework#7710).

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions