apache/cordova-android · error · CordovaError

File "${src}" is located outside the plugin directory "${plu

Error message

File "${src}" is located outside the plugin directory "${plugin_dir}"

What it means

A security check in copyFile: it resolves both the source file and the plugin directory through fs.realpathSync (following symlinks) and requires the file to be inside the plugin directory via isPathInside. It throws when a plugin tries to copy a file that really lives outside its own tree, blocking path-traversal-style plugin content.

Source

Thrown at lib/pluginHandlers.js:215

    events.emit('verbose', '<' + type + '> is not supported for android plugins');
};

module.exports.getUninstaller = function (type) {
    if (handlers[type] && handlers[type].uninstall) {
        return handlers[type].uninstall;
    }

    events.emit('verbose', '<' + type + '> is not supported for android plugins');
};

function copyFile (plugin_dir, src, project_dir, dest, link) {
    src = path.resolve(plugin_dir, src);
    if (!fs.existsSync(src)) throw new CordovaError('"' + src + '" not found!');

    // check that src path is inside plugin directory
    const real_path = fs.realpathSync(src);
    const real_plugin_path = fs.realpathSync(plugin_dir);
    if (!isPathInside(real_path, real_plugin_path)) { throw new CordovaError('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"'); }

    dest = path.resolve(project_dir, dest);

    // check that dest path is located in project directory
    if (!isPathInside(dest, project_dir)) { throw new CordovaError('Destination "' + dest + '" for source file "' + src + '" is located outside the project'); }

    fs.mkdirSync(path.dirname(dest), { recursive: true });
    if (link) {
        symlinkFileOrDirTree(src, dest);
    } else {
        fs.cpSync(src, dest, { recursive: true });
    }
}

// Same as copy file but throws error if target exists
function copyNewFile (plugin_dir, src, project_dir, dest, link) {
    const target_path = path.resolve(project_dir, dest);
    if (fs.existsSync(target_path)) { throw new CordovaError('"' + target_path + '" already exists!'); }

View on GitHub (pinned to 7c1e190064)

Solutions

  1. Move or copy the referenced file physically inside the plugin directory and use a src relative to the plugin root without ../ segments
  2. For local development, remove symlinks: uninstall the plugin, then `cordova plugin add /abs/path/to/plugin` (real path) instead of npm link
  3. If shared code is needed across plugins, publish it as its own plugin and declare it as a <dependency> instead of reaching outside

Example fix

# before
<resource-file src="../../shared/assets/logo.png" target="res/drawable/logo.png" />

# after (copy shared/assets/logo.png into the plugin first)
<resource-file src="src/shared/logo.png" target="res/drawable/logo.png" />
Defensive patterns

Strategy: validation

Validate before calling

// before install: flag src paths that escape the plugin root
const path = require('path');
const escapes = srcAttr.split('/').filter(p => p === '..').length > 0;
if (escapes) throw new Error('plugin.xml src escapes the plugin dir and will be rejected');

Try / catch

try { await cordova.plugin('add', p); } catch (e) {
  if (/located outside the plugin directory/.test(e.message)) { /* inline the file into the plugin or use a <dependency> plugin */ }
}

Prevention

When it happens

Trigger: A plugin.xml src containing ../ escapes (e.g. <resource-file src="../../shared/logo.png"/>), or a file inside the plugin that is a symlink pointing outside (e.g. npm link-ed local plugins, or a symlinked node_modules dependency file). The realpath check sees through the symlink and rejects it.

Common situations: Developing plugins with `npm link` or a symlinked plugins/<id> directory during local testing; monorepo plugins referencing shared assets via ../; a repo checked out with symlinked submodules.

Related errors


AI-assisted analysis of apache/cordova-android@7c1e190064 (2026-08-22). Data as JSON: /api/errors/bfda9d0fbeadc4b4. Report an issue: GitHub.