pypa/pip · error · InstallationError
Invalid script entry point name
Error message
Invalid script entry point name {entry.name!r}: the script would be installed outside the scripts directory ({scripts_dir}). What it means
Raised as InstallationError by _raise_for_invalid_entrypoint when a console_scripts entry point name contains path separators or '..' components such that os.path.join(scripts_dir, entry.name) resolves outside the scripts directory (or exactly to it). This is a path-traversal guard preventing a malicious/buggy wheel from writing a script outside the scripts dir.
Solutions
- Remove path separators and '..' from the console_scripts entry name.
- Rebuild the wheel with a plain, single-component entry name.
- Do not install wheels from untrusted sources that exhibit this.
Example fix
# before [console_scripts] ../evil = pkg.mod:main # after [console_scripts] evil = pkg.mod:main
Defensive patterns
Strategy: validation
Validate before calling
import os, re
def safe_entry_name(name: str) -> bool:
# must be a single path component, no separators, no traversal
return bool(re.fullmatch(r"[^/\\]+", name)) and not name.startswith(".")
# assert safe_entry_name("mycli")
# assert not safe_entry_name("../evil") Prevention
- Use single-component, plain entry-point names for console_scripts.
- Reject path separators or '..' in entry names during wheel builds.
- Do not install wheels from untrusted sources.
When it happens
Trigger: entry_points.txt defines a name like '../bin/x', '/abs/path', or 'sub/dir/x'; is_within_directory(scripts_dir, dest) is false, so the install is aborted before distlib writes anything.
Common situations: Malicious wheel attempting directory escape; malformed build config producing entry-point names with slashes; non-standard packaging tools.
Related errors
- The wheel has a file trying to install outside the target…
- Invalid member in the tar file
- Invalid script entry point
- path outside destination: %r
- Path in pylock file obtained from a URL resolves outside…
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/5032ec375c6a120e.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_internal/operations/install/wheel.py:417
"information."
)
def _raise_for_invalid_entrypoint(specification: str, scripts_dir: str) -> None:
entry = get_export_entry(specification)
if entry is None:
return
if entry.suffix is None:
raise MissingCallableSuffix(str(entry))
# distlib joins the entry point name onto the scripts directory, so a name
# with path separators or ``..`` components can resolve elsewhere. The script
# must resolve to a path strictly inside the scripts directory.
dest = os.path.join(scripts_dir, entry.name)
resolves_to_scripts_dir = os.path.abspath(dest) == os.path.abspath(scripts_dir)
if resolves_to_scripts_dir or not is_within_directory(scripts_dir, dest):
raise InstallationError(
f"Invalid script entry point name {entry.name!r}: the script "
f"would be installed outside the scripts directory ({scripts_dir})."
)
class PipScriptMaker(ScriptMaker):
# Override distlib's default script template with one that
# doesn't import `re` module, allowing scripts to load faster.
script_template = textwrap.dedent("""\
import sys
from %(module)s import %(import_name)s
if __name__ == '__main__':
sys.argv[0] = sys.argv[0].removesuffix('.exe')
sys.exit(%(func)s())
""")
def make(
self, specification: str, options: dict[str, Any] | None = NoneView on GitHub (pinned to f399c37189)