@@ -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