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
- Simplify the spec to plain name (optionally with a simple version constraint) and handle markers/URLs yourself
- Sanitize user input to the allowed character set before passing it
- 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
- Pass plain package names with at most simple version constraints
- Put markers/URLs/path installs in pyproject.toml, not the packages list
- Whitelist-validate all user-supplied package input
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
- flags are not allowed in packages list: {pkg}
- script path is outside workspace root
- package entries must be strings
- package names cannot be empty
- Agent node '{nid}' config must be an object
AI-assisted analysis of OpenBMB/ChatDev@4fb2db0ea9 (2026-08-27).
Data as JSON: /api/errors/76c073431129a801.
Report an issue: GitHub.