{"record":{"id":"76c073431129a801","repo":"OpenBMB/ChatDev","slug":"unsafe-characters-detected-in-package-spec-pkg","errorCode":null,"errorMessage":"unsafe characters detected in package spec {pkg}","messagePattern":"unsafe characters detected in package spec (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"functions/function_calling/uv_related.py","lineNumber":73,"sourceCode":"    def resolve_under_workspace(self, relative_path: str | Path) -> Path:\n        candidate = Path(relative_path)\n        absolute = candidate if candidate.is_absolute() else self.workspace_root / candidate\n        absolute = absolute.expanduser().resolve()\n        if self.workspace_root not in absolute.parents and absolute != self.workspace_root:\n            raise ValueError(\"script path is outside workspace root\")\n        return absolute\n\n\ndef _validate_packages(packages: Sequence[str]) -> List[str]:\n    normalized: List[str] = []\n    for pkg in packages:\n        if not isinstance(pkg, str):\n            raise ValueError(\"package entries must be strings\")\n        stripped = pkg.strip()\n        if not stripped:\n            raise ValueError(\"package names cannot be empty\")\n        if not _SAFE_PACKAGE_RE.match(stripped):\n            raise ValueError(f\"unsafe characters detected in package spec {pkg}\")\n        if stripped.startswith(\"-\"):\n            raise ValueError(f\"flags are not allowed in packages list: {pkg}\")\n        normalized.append(stripped)\n    if not normalized:\n        raise ValueError(\"at least one package is required\")\n    return normalized\n\n\ndef _coerce_timeout_seconds(timeout_seconds: Any) -> float | None:\n    if timeout_seconds is None:\n        return None\n    if isinstance(timeout_seconds, bool):\n        raise ValueError(\"timeout_seconds must be a number\")\n    if isinstance(timeout_seconds, (int, float)):\n        value = float(timeout_seconds)\n    elif isinstance(timeout_seconds, str):\n        raw = timeout_seconds.strip()\n        if not raw:","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/OpenBMB/ChatDev/blob/4fb2db0ea90375ce1059f44fe03ffbd191a7a169/functions/function_calling/uv_related.py#L55-L91","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\ninstall_python_packages(packages=[\"numpy>=1.26; sys_platform=='linux'\"])\n# after\ninstall_python_packages(packages=[\"numpy>=1.26\"])  # handle markers in pyproject.toml","handlingStrategy":"validation","validationCode":"import re\nSAFE = re.compile(r\"^[A-Za-z0-9_.><=!~+*-]+$\")\npackages = [p for p in packages if SAFE.match(p.strip()) and not p.strip().startswith(\"-\")]\ninstall_python_packages(packages=packages, _context=ctx)","typeGuard":"def specs_are_safe(packages) -> bool:\n    import re\n    return all(\n        isinstance(p, str)\n        and re.match(r\"^[A-Za-z0-9_.><=!~+*-]+$\", p.strip())\n        and not p.strip().startswith(\"-\")\n        for p in packages\n    )","tryCatchPattern":"try:\n    install_python_packages(packages=packages, _context=ctx)\nexcept ValueError as e:\n    if \"unsafe characters\" in str(e):\n        raise ValueError(f\"reject suspicious package spec: {e}\") from e\n    raise","preventionTips":["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"],"tags":["security","uv","packages","injection-guard"],"backgroundTag":"unsafe-characters-in-argument","analyzedSha":"4fb2db0ea90375ce1059f44fe03ffbd191a7a169","analyzedAt":"2026-08-27T14:35:29.622Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}