Skip to content

Commit 1048a0e

Browse files
committed
Update cli utcp args to use env variable substitution
1 parent 26afe40 commit 1048a0e

5 files changed

Lines changed: 631 additions & 248 deletions

File tree

plugins/communication_protocols/cli/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "utcp-cli"
7-
version = "1.1.2"
7+
version = "1.1.3"
88
authors = [
99
{ name = "UTCP Contributors" },
1010
]

plugins/communication_protocols/cli/src/utcp_cli/cli_call_template.py

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,34 @@ class CommandStep(BaseModel):
1515
placeholders that will be replaced with values from tool_args. Can also
1616
reference previous command outputs using $CMD_0_OUTPUT, $CMD_1_OUTPUT, etc.
1717
18-
Placeholder substitution is shell-quoted (`shlex.quote` on Unix,
19-
PowerShell single-quoted literals on Windows) so that
20-
`tool_args` values cannot inject extra commands. As a
21-
consequence, each `UTCP_ARG_..._UTCP_END` placeholder always
22-
expands to **exactly one shell token**. Tools that previously
23-
relied on a single placeholder splitting into multiple flags
24-
(e.g. `UTCP_ARG_flags_UTCP_END` -> `--verbose --debug`) must now
25-
use one placeholder per intended flag. This change ships with
26-
utcp-cli 1.1.2 and addresses GHSA-33p6-5jxp-p3x4.
18+
Placeholders are NOT inlined as text. Instead the protocol
19+
emits a context-aware shell variable reference (`"$VAR"` /
20+
`${VAR}` / `$env:VAR`) and ships the actual `tool_args`
21+
value to the subprocess via an environment variable, so the
22+
shell expands the value AFTER it has parsed the script.
23+
Attacker-controlled bytes therefore cannot inject commands
24+
or escape any quoting context.
25+
26+
A placeholder always substitutes a **single logical value**
27+
(never a list of shell words) -- the substituted value
28+
cannot be reinterpreted as additional shell syntax. Several
29+
placeholders may appear within the same quoted region (e.g.
30+
``"https://api/UTCP_ARG_id_UTCP_END/UTCP_ARG_action_UTCP_END"``)
31+
and they compose with the surrounding literal text into one
32+
shell argument. Tools that previously relied on a single
33+
placeholder splitting into multiple flags (e.g.
34+
``UTCP_ARG_flags_UTCP_END`` -> ``--verbose --debug``) must
35+
now use one placeholder per intended flag. This change
36+
ships with utcp-cli 1.1.3 and addresses GHSA-33p6-5jxp-p3x4
37+
(including the residual double-quote-context bypass that
38+
the inline ``shlex.quote`` strategy in 1.1.2 left open).
39+
40+
PowerShell limitation: a placeholder appearing inside a
41+
single-quoted PowerShell string (``'...'``) raises
42+
``ValueError`` at script-build time -- PowerShell does not
43+
expand variables inside single quotes, and rewriting the
44+
surrounding token is too brittle. Use a double-quoted
45+
string (``"..."``) instead.
2746
append_to_final_output: Whether this command's output should be included
2847
in the final result. If not specified, defaults to False for all
2948
commands except the last one.
@@ -48,9 +67,11 @@ class CommandStep(BaseModel):
4867
command: str = Field(
4968
description=(
5069
"Command string to execute, may contain UTCP_ARG_argname_UTCP_END "
51-
"placeholders. Each placeholder is shell-quoted at substitution "
52-
"time and therefore expands to exactly one shell token; use one "
53-
"placeholder per intended argument."
70+
"placeholders. Each placeholder substitutes a single value via "
71+
"a shell-variable reference chosen for its surrounding quote "
72+
"context; substituted values cannot be reinterpreted as "
73+
"additional shell syntax. Several placeholders may appear in "
74+
"the same quoted region and compose into one argument."
5475
)
5576
)
5677
append_to_final_output: Optional[bool] = Field(
@@ -81,15 +102,29 @@ class CliCallTemplate(CallTemplate):
81102
82103
Example: `echo "Previous result: $CMD_0_OUTPUT"`
83104
84-
**Argument Substitution and Quoting (utcp-cli >= 1.1.2):**
85-
`UTCP_ARG_argname_UTCP_END` placeholders are replaced with the
86-
corresponding `tool_args` value, shell-quoted for the target shell
87-
(`shlex.quote` on Unix, PowerShell single-quoted literal on Windows).
88-
Each placeholder therefore expands to exactly one shell token. If a
89-
tool needs multiple flags or arguments, define multiple placeholders
90-
(one per flag) instead of relying on a single placeholder splitting
91-
on whitespace. This change closes the command-injection vector
92-
tracked as GHSA-33p6-5jxp-p3x4.
105+
**Argument Substitution (utcp-cli >= 1.1.3):**
106+
``UTCP_ARG_argname_UTCP_END`` placeholders are replaced with a
107+
context-aware shell variable reference (``"$VAR"`` outside quotes,
108+
``${VAR}`` inside double quotes, an adjacent-quote concat trick
109+
inside single-quoted bash). The actual ``tool_args`` value is
110+
shipped to the subprocess via a fresh, per-invocation env var; the
111+
shell expands it at runtime AFTER it has parsed the script, so
112+
attacker-controlled bytes cannot inject commands or escape any
113+
quoting context.
114+
115+
A placeholder always substitutes a single logical value (never a
116+
list of shell words). Several placeholders may appear in one
117+
quoted region and compose with the surrounding text into one
118+
argument (e.g.
119+
``"https://api/UTCP_ARG_id_UTCP_END/UTCP_ARG_action_UTCP_END"``).
120+
If a tool needs multiple separate flags, use one placeholder per
121+
flag in bare position. PowerShell single-quoted strings cannot
122+
expand variables, so a placeholder inside ``'...'`` on Windows
123+
raises ``ValueError`` at script-build time; use a double-quoted
124+
string instead. This change closes the command-injection vector
125+
tracked as GHSA-33p6-5jxp-p3x4 (and its residual
126+
double-quote-context bypass that the inline ``shlex.quote``
127+
strategy in 1.1.2 left open).
93128
94129
**Subprocess Environment (utcp-cli >= 1.1.2):**
95130
The CLI subprocess no longer inherits the full host environment.
@@ -221,8 +256,12 @@ class CliCallTemplate(CallTemplate):
221256
commands: List[CommandStep] = Field(
222257
description=(
223258
"List of commands to execute in order. Each command can contain "
224-
"UTCP_ARG_argname_UTCP_END placeholders, which are shell-quoted "
225-
"on substitution and therefore expand to exactly one shell token."
259+
"UTCP_ARG_argname_UTCP_END placeholders, which substitute a "
260+
"single value via a shell-variable reference chosen for the "
261+
"surrounding quote context. Substituted values cannot be "
262+
"reinterpreted as additional shell syntax. Several placeholders "
263+
"may appear in the same quoted region and compose into one "
264+
"argument."
226265
)
227266
)
228267
env_vars: Optional[Dict[str, str]] = Field(

0 commit comments

Comments
 (0)