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 = None

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Inspect the wheel's entry_points.txt for any name containing '/', '\\', or '..'.
  2. Fix the script name to be a plain filename (no path separators, no relative components).
  3. Rebuild and reinstall the corrected wheel.
  4. 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

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


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/5032ec375c6a120e.json. Report an issue: GitHub.