From dc3fb70a1eab7f3684c5f7b643ca6a88dfb08a83 Mon Sep 17 00:00:00 2001 From: disqualifier Date: Sun, 28 Jun 2026 17:18:28 -0400 Subject: [PATCH] fix: shlex.quote values in as_curl() so the command is valid and not injectable header/body/url/proxy values were wrapped in raw single quotes, so a value containing a quote or shell metacharacter produced a broken or injectable command. every interpolated value is now shell-quoted. Signed-off-by: disqualifier --- README.md | 4 ++-- src/aioweb/preview.py | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3025b37..01ddbcd 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,13 @@ and swap the HTTP client while inheriting everything else. `requirements.txt`: ``` -aioweb @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb.git@v0.1.1 +aioweb @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb.git@v0.1.2 ``` Direct: ```bash -pip install "aioweb @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb.git@v0.1.1" +pip install "aioweb @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb.git@v0.1.2" ``` Requires `aiohttp` and `yarl` (pulled transitively). diff --git a/src/aioweb/preview.py b/src/aioweb/preview.py index 9d04667..5cc1133 100644 --- a/src/aioweb/preview.py +++ b/src/aioweb/preview.py @@ -3,6 +3,7 @@ request preview for aioweb — format or export a request without sending it """ import json as _json +import shlex class RequestPreview: @@ -25,15 +26,20 @@ class RequestPreview: return "\n".join(f"{key}: {value}" for key, value in self.details.items()) def as_curl(self): - """equivalent cURL command for the request""" - parts = [f"curl -X {self.details['method']}"] + """equivalent cURL command for the request + + every interpolated value is shell-quoted with shlex.quote, so headers, + body, url, or proxy containing quotes/spaces/metacharacters produce a + valid, non-injectable command rather than a broken or unsafe one. + """ + parts = [f"curl -X {shlex.quote(self.details['method'])}"] for header, value in (self.details["headers"] or {}).items(): - parts.append(f"-H '{header}: {value}'") + parts.append(f"-H {shlex.quote(f'{header}: {value}')}") if self.details["data"]: - parts.append(f"--data '{self.details['data']}'") + parts.append(f"--data {shlex.quote(str(self.details['data']))}") elif self.details["json"]: - parts.append(f"--data '{_json.dumps(self.details['json'])}'") - parts.append(f"'{self.details['url']}'") + parts.append(f"--data {shlex.quote(_json.dumps(self.details['json']))}") + parts.append(shlex.quote(str(self.details["url"]))) if self.details["proxy"]: - parts.append(f"--proxy '{self.details['proxy']}'") + parts.append(f"--proxy {shlex.quote(str(self.details['proxy']))}") return " \\\n ".join(parts)