Skip to content

Commit b00388b

Browse files
committed
fix bug
1 parent 1048a0e commit b00388b

2 files changed

Lines changed: 44 additions & 11 deletions

File tree

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,12 @@ def _substitute_utcp_args(
380380
as a single token)
381381
382382
powershell (Windows):
383-
- bare: ``$env:VAR``
384-
- inside double quotes: ``$env:VAR`` (PS expands inside dq)
383+
- bare: ``${env:VAR}``
384+
- inside double quotes: ``${env:VAR}`` (PS expands inside dq;
385+
braced form prevents
386+
suffix characters
387+
from being consumed
388+
into the var name)
385389
- inside single quotes: ValueError -- PS does not expand inside
386390
single-quoted strings, so
387391
we cannot safely
@@ -524,9 +528,17 @@ def collect(name: str) -> str:
524528
f"instead."
525529
)
526530
v = collect(m.group(1))
527-
# Both bare and dq accept `$env:VAR` -- PowerShell expands
528-
# it inside double-quoted strings.
529-
out.append(f"$env:{v}")
531+
# Use the braced form `${env:VAR}` rather than `$env:VAR`
532+
# so the variable name is explicitly delimited. The bare
533+
# form lets PowerShell's lexer keep consuming
534+
# alphanumerics + `_` until it hits a non-identifier
535+
# char, which would silently swallow any suffix text in
536+
# the template (e.g. template
537+
# ``"URL=UTCP_ARG_id_UTCP_END123"`` would be substituted
538+
# as ``"URL=$env:__UTCP_ARG_<nonce>_id123"`` and resolve
539+
# an env var that does not exist). Braces close that
540+
# boundary cleanly.
541+
out.append("${env:" + v + "}")
530542
i = m.end()
531543
continue
532544

plugins/communication_protocols/cli/tests/test_cli_security.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,21 +82,39 @@ def test_bash_escaped_dq_does_not_flip_state(self, transport):
8282
assert cmd == f'echo "esc\\" ${{{_v("a")}}}"'
8383

8484
@pytest.mark.skipif(os.name != "nt", reason="PowerShell semantics")
85-
def test_ps_bare_emits_env_var(self, transport):
85+
def test_ps_bare_emits_braced_env_var(self, transport):
8686
cmd, env = transport._substitute_utcp_args(
8787
"mytool UTCP_ARG_x_UTCP_END", {"x": "a b"}, NONCE
8888
)
89-
assert cmd == f"mytool $env:{_v('x')}"
89+
# Braced form so suffix chars cannot be consumed into the var
90+
# name boundary.
91+
assert cmd == "mytool ${env:" + _v("x") + "}"
9092
assert env[_v("x")] == "a b"
9193

9294
@pytest.mark.skipif(os.name != "nt", reason="PowerShell semantics")
93-
def test_ps_dq_emits_env_var(self, transport):
95+
def test_ps_dq_emits_braced_env_var(self, transport):
9496
cmd, env = transport._substitute_utcp_args(
9597
'Write-Output "Hi UTCP_ARG_x_UTCP_END!"', {"x": "a; rm /"}, NONCE
9698
)
97-
assert cmd == f'Write-Output "Hi $env:{_v("x")}!"'
99+
assert cmd == 'Write-Output "Hi ${env:' + _v("x") + '}!"'
98100
assert env[_v("x")] == "a; rm /"
99101

102+
@pytest.mark.skipif(os.name != "nt", reason="PowerShell semantics")
103+
def test_ps_alphanumeric_suffix_does_not_extend_var_name(self, transport):
104+
# Regression: with the bare `$env:VAR` form, an alphanumeric or
105+
# underscore suffix in the template would be parsed as part of
106+
# the env var name. The braced form `${env:VAR}` closes the
107+
# boundary cleanly so the template suffix stays literal.
108+
cmd, env = transport._substitute_utcp_args(
109+
'Write-Output "URL=UTCP_ARG_id_UTCP_END123suffix"',
110+
{"id": "abc"},
111+
NONCE,
112+
)
113+
assert cmd == (
114+
'Write-Output "URL=${env:' + _v("id") + '}123suffix"'
115+
)
116+
assert env[_v("id")] == "abc"
117+
100118
@pytest.mark.skipif(os.name != "nt", reason="PowerShell semantics")
101119
def test_ps_sq_raises_with_clear_message(self, transport):
102120
with pytest.raises(ValueError, match="single-quoted"):
@@ -110,7 +128,7 @@ def test_ps_backtick_in_dq_preserved(self, transport):
110128
'Write-Output "pre `"x`" UTCP_ARG_a_UTCP_END"', {"a": "v"}, NONCE
111129
)
112130
# dq state must still be active when placeholder is hit.
113-
assert cmd == f'Write-Output "pre `"x`" $env:{_v("a")}"'
131+
assert cmd == 'Write-Output "pre `"x`" ${env:' + _v("a") + '}"'
114132

115133
def test_multiple_placeholders_share_namespace(self, transport):
116134
cmd, env = transport._substitute_utcp_args(
@@ -132,7 +150,10 @@ def test_multiple_placeholders_in_same_dq_compose(self, transport):
132150
NONCE,
133151
)
134152
if os.name == "nt":
135-
assert cmd == f'curl "https://api/$env:{_v("id")}/$env:{_v("action")}"'
153+
assert cmd == (
154+
'curl "https://api/${env:' + _v("id")
155+
+ '}/${env:' + _v("action") + '}"'
156+
)
136157
else:
137158
assert (
138159
cmd

0 commit comments

Comments
 (0)