pypa/pip · critical · InstallationError
Invalid script entry point name {entry.name!r}: the script w
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 when a console/gui script entry point name contains path separators or '..' components that would cause the generated wrapper to be written outside the scripts directory. At wheel.py:408-414, _raise_for_invalid_entrypoint joins entry.name onto scripts_dir and checks both that it doesn't resolve exactly to scripts_dir and that it stays within is_within_directory(scripts_dir, dest).
Source
Thrown at src/pip/_internal/operations/install/wheel.py:411
"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 d7d0d0a394)
Solutions
- Inspect the wheel's entry_points.txt for any name containing '/', '\\', or '..'.
- Fix the script name to be a plain filename (no path separators, no relative components).
- Rebuild and reinstall the corrected wheel.
- If the wheel came from an untrusted source, treat this as a potential supply-chain attack and audit the file.
Example fix
# before (entry_points.txt) [console_scripts] ../payload = mypkg.payload:run # after [console_scripts] payload = mypkg.payload:run
Defensive patterns
Strategy: validation
Validate before calling
import os, re, configparser, zipfile
def validate_script_names(whl, scripts_dir):
bad = []
with zipfile.ZipFile(whl) as z:
for n in z.namelist():
if n.endswith("entry_points.txt"):
cp = configparser.ConfigParser()
cp.read_string(z.read(n).decode())
for sec in ("console_scripts", "gui_scripts"):
for name in cp[sec] if sec in cp else {}:
dest = os.path.join(scripts_dir, name)
if os.path.abspath(dest) == os.path.abspath(scripts_dir) or \
not dest.startswith(os.path.abspath(scripts_dir) + os.sep):
bad.append(name)
if bad:
raise ValueError(f"unsafe entry point names: {bad}") Type guard
import os
def is_safe_script_name(name: str, scripts_dir: str) -> bool:
dest = os.path.join(scripts_dir, name)
if os.path.abspath(dest) == os.path.abspath(scripts_dir):
return False
return os.path.abspath(dest).startswith(os.path.abspath(scripts_dir) + os.sep) Prevention
- Reject entry point names containing path separators or '..' at build time.
- Run a wheel-validation step in CI before publishing.
- Treat any name with '/' or '\\' as a packaging defect or security issue.
When it happens
Trigger: An entry point whose name is something like '../evil', 'sub/dir/cmd', or an empty/relative name, so os.path.abspath(os.path.join(scripts_dir, entry.name)) escapes scripts_dir. This is a path-traversal guard on entry point names.
Common situations: Malicious or malformed wheel with a crafted entry point name attempting to write outside the scripts dir. Also a buggy build config that accidentally includes a slash in a script name.
Related errors
- The wheel {wheel_path!r} has a file {target_path!r} trying t
- Invalid script entry point: {entry_point} - A callable suffi
- Unexpected file in {wheel_path}: {record_path!r}. .data dire
- Unknown scheme key used in {wheel_path}: {scheme_key} (for f
- For req: {req_description}. {e.args[0]}
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/5032ec375c6a120e.json.
Report an issue: GitHub.