OpenBMB/ChatDev · error · ValueError

unsafe characters detected in package spec {pkg}

Error message

unsafe characters detected in package spec {pkg}

What it means

Package specs are matched against a safety allowlist regex (_SAFE_PACKAGE_RE) to prevent shell/option injection into the uv command line. Specs containing characters outside the allowed set (spaces, quotes, semicolons, pipes, etc.) are rejected with the offending spec included in the message. Version pinning syntax like 'numpy>=1.26' is allowed only insofar as the regex permits; anything fancier fails.

Source

Thrown at functions/function_calling/uv_related.py:73

    def resolve_under_workspace(self, relative_path: str | Path) -> Path:
        candidate = Path(relative_path)
        absolute = candidate if candidate.is_absolute() else self.workspace_root / candidate
        absolute = absolute.expanduser().resolve()
        if self.workspace_root not in absolute.parents and absolute != self.workspace_root:
            raise ValueError("script path is outside workspace root")
        return absolute


def _validate_packages(packages: Sequence[str]) -> List[str]:
    normalized: List[str] = []
    for pkg in packages:
        if not isinstance(pkg, str):
            raise ValueError("package entries must be strings")
        stripped = pkg.strip()
        if not stripped:
            raise ValueError("package names cannot be empty")
        if not _SAFE_PACKAGE_RE.match(stripped):
            raise ValueError(f"unsafe characters detected in package spec {pkg}")
        if stripped.startswith("-"):
            raise ValueError(f"flags are not allowed in packages list: {pkg}")
        normalized.append(stripped)
    if not normalized:
        raise ValueError("at least one package is required")
    return normalized


def _coerce_timeout_seconds(timeout_seconds: Any) -> float | None:
    if timeout_seconds is None:
        return None
    if isinstance(timeout_seconds, bool):
        raise ValueError("timeout_seconds must be a number")
    if isinstance(timeout_seconds, (int, float)):
        value = float(timeout_seconds)
    elif isinstance(timeout_seconds, str):
        raw = timeout_seconds.strip()
        if not raw:

View on GitHub (pinned to 4fb2db0ea9)

Solutions

  1. Simplify the spec to plain name (optionally with a simple version constraint) and handle markers/URLs yourself
  2. Sanitize user input to the allowed character set before passing it
  3. Move exotic requirements (URLs, markers, paths) into a project pyproject.toml/uv workspace instead of the packages list

Example fix

# before
install_python_packages(packages=["numpy>=1.26; sys_platform=='linux'"])
# after
install_python_packages(packages=["numpy>=1.26"])  # handle markers in pyproject.toml
Defensive patterns

Strategy: validation

Validate before calling

import re
SAFE = re.compile(r"^[A-Za-z0-9_.><=!~+*-]+$")
packages = [p for p in packages if SAFE.match(p.strip()) and not p.strip().startswith("-")]
install_python_packages(packages=packages, _context=ctx)

Type guard

def specs_are_safe(packages) -> bool:
    import re
    return all(
        isinstance(p, str)
        and re.match(r"^[A-Za-z0-9_.><=!~+*-]+$", p.strip())
        and not p.strip().startswith("-")
        for p in packages
    )

Try / catch

try:
    install_python_packages(packages=packages, _context=ctx)
except ValueError as e:
    if "unsafe characters" in str(e):
        raise ValueError(f"reject suspicious package spec: {e}") from e
    raise

Prevention

When it happens

Trigger: Passing "numpy; sys_platform == 'linux'" (environment markers), "numpy @ https://..." (direct URL), "numpy && rm -rf /" (injection attempt), or specs with unquoted spaces/quotes; sometimes even otherwise-valid extras syntax not covered by the regex.

Common situations: Copy-pasting full pip-style requirement strings that include markers or URLs; user-supplied package input interpolated without sanitization; environment-marker requirements from a requirements.txt passed through verbatim.

Related errors


AI-assisted analysis of OpenBMB/ChatDev@4fb2db0ea9 (2026-08-27). Data as JSON: /api/errors/76c073431129a801. Report an issue: GitHub.