fix: sanitize newlines in docker env dict values and v2 env rendering - #5692
fix: sanitize newlines in docker env dict values and v2 env rendering#5692feiiiiii5 wants to merge 4 commits into
Conversation
Remote clients serialising a list[Path] field with URL-backed values wrote each URL into the ``data`` dict under the same field name, so only the last URL survived and earlier values were silently dropped before reaching the service. URL-backed values are now emitted as repeated text parts (httpx files with filename=None; aiohttp add_field calls), so every value in a list field produces its own same-name part. The server already aggregates repeated field names via form.getlist.
Values from the legacy docker.env dict were interpolated into ARG/ENV Dockerfile lines without sanitization; a value containing a newline broke out of the line and injected an arbitrary Dockerfile instruction executed during docker build. base_v2.j2 had the same flaw for the new envs path: bash_quote does not remove newlines. Collapse whitespace (including newlines) on dict env keys and values in _convert_env, and normalize v2 env values before bash_quote, matching the hardened sibling paths (system_packages, base_image, envs names). Addresses bentoml#5656.
shashvat-singham
left a comment
There was a problem hiding this comment.
Ran the branch — tests/unit/_internal/client/test_multipart.py is 3 passed.
The template fix is the right one, and worth keeping. Adding normalize_line to the ARG value is what actually closes #5656: bash_quote alone could never have, because Docker parses the Dockerfile line by line, so a raw newline in the value ends the ARG instruction no matter how the shell would later quote it. And it's sufficient on its own — the value appears only on the ARG line; the ENV line just references ${NAME}, so there's no second path a value reaches.
Given that, the _convert_env change looks redundant for security but lossy for data. It collapses all whitespace in the config model, not at the render boundary, so it silently rewrites legitimate values that were never a problem:
'a\nRUN echo pwned' -> 'a RUN echo pwned' # the case you're targeting
'a\tb' -> 'a b' # tab destroyed
'-Xmx1g -Xms512m' -> '-Xmx1g -Xms512m' # double space collapsed
' padded ' -> 'padded' # surrounding whitespace stripped
Because it mutates BentoEnvSchema rather than the generated Dockerfile, the altered value is what the service sees at runtime too — so an env var whose value legitimately contains a tab or repeated spaces (JAVA_OPTS, a format string, an indented cert/PEM-ish blob) is quietly changed in a context where nothing unsafe was happening. That's a behaviour change for correct inputs in exchange for defence the template already provides.
My suggestion would be to drop the _convert_env hunk and keep the escaping at the point of rendering. If you'd rather have belt-and-braces at the model layer, rejecting a value containing a newline would be better than silently normalising it — a user who put a newline in an env value has made a mistake and would want to hear about it, and it leaves tabs and spacing alone.
Separately: this PR appears to contain #5691. The http.py / proxy2.py multipart changes here are the same fix as "send one multipart part per URL in list file fields", and the new test file covers that rather than the env sanitisation. If they're stacked deliberately that's fine, but as it stands the title and the linked issue (#5656) only describe the env half, so a reviewer landing here won't expect the client changes — and if #5691 merges first this will conflict. Worth either splitting them or retitling to say it carries both.
|
Addressed in commit
Local: |
|
Follow-up commit |
|
Closing as part of a contribution-portfolio consolidation: my effort is now focused on a single project (failroute, a static analyzer for silent-failure anti-patterns) and its upstream follow-through. Happy to revisit if there is interest. |
|
Reopening — the 08-28 close was a mistake on my side; this had a real review on it. @shashvat-singham's 08-15 review confirmed the Sorry for the noise. |
What does this PR address?
Values supplied through the legacy
docker.envdict (bentofile.yaml->docker.env: {KEY: VALUE}) and through the newenvspath are interpolated into generated DockerfileARGlines. A value containing a newline breaks out of the line and injects an arbitrary Dockerfile instruction (e.g.RUN ...), which executes duringdocker build.Fixes #5656.
Fix
Sanitize only at the Dockerfile render boundary, so the config model keeps user values byte-for-byte:
base.j2(legacy dict path):ARG {{ key|normalize_line }}={{ value|normalize_line|bash_quote }}and the matchingENVreference.base_v2.j2(envspath): addnormalize_linebeforebash_quotefor the value.Test
ARGline.DockerOptions.envkeeps tabs, repeated spaces, and surrounding whitespace unchanged.Before submitting:
pre-commit run -ascript has passed?