Skip to content

fix(VAPI-4457): scope SSML un-escape to SpeakSentence text - #321

Merged
nasanov-bw merged 3 commits into
mainfrom
VAPI-4457_scope-ssml-unescape-to-speaksentence
Nov 5, 2026
Merged

nasanov-bw merged 3 commits into
mainfrom
VAPI-4457_scope-ssml-unescape-to-speaksentence

Conversation

@nasanov-bw

@nasanov-bw nasanov-bw commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The SSML un-escape (&lt;tag&gt; -> <tag>) ran over the whole serialized BXML, so it also rewrote escaped <...> inside XML attribute values into a raw <...>. A raw < is illegal in an attribute value, so the output was malformed XML and the API returned invalid_bxml.

The un-escape now only applies to the text inside <SpeakSentence>...</SpeakSentence>, matching the C# and Ruby SDKs. Attribute values and every other element keep their XML escaping.

Jira: VAPI-4457

Validation

  • BXML unit tests pass (16 targeted tests via unittest).
  • New tests fail on main and pass on this branch.
  • Not run against a live Bandwidth call; verified that output parses as XML with the expected escaping.

@nasanov-bw
nasanov-bw requested review from a team as code owners November 5, 2026 16:59
@bwappsec

bwappsec commented Nov 5, 2026 •

Copy link
Copy Markdown

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues
✅ Licenses 0 0 0 0 0 issues
✅ Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Comment thread bandwidth/models/bxml/nestable_verb.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove this import here too

@@ -47,4 +46,4 @@ def _attributes(self):

def to_bxml(self) -> str:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is now the exact same as to_bxml in Verb, so I don't think we need the override anymore. Definitely double check behind me but I think this can go away

Comment thread bandwidth/models/bxml/verb.py Outdated
_SSML_REGEX = re.compile(r"&lt;([a-zA-Z//].*?)&gt;")


def unescape_ssml(bxml: str) -> str:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make this _unescape_ssml so its private? I don't think customers should ever need to access it

response.add_verb(self.speak_sentence)
assert expected == response.to_bxml()

def test_ssml_unescaped_but_other_attributes_untouched(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both of these tests can be moved to test_response.py, and can we update the assertions to match an entire string like the rest of the tests? Just to be more strict.

))
assert expected_bxml == self.response.to_bxml()

def test_empty_speak_sentence_does_not_swallow_following_verbs(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick but can we give this a docstring to match the others

Comment thread test/unit/models/bxml/test_response.py Outdated
def test_ssml_unescaped_but_other_attributes_untouched(self):
"""SSML in <SpeakSentence> text is un-escaped; escaped characters in other elements' attributes are not
"""
expected_bxml = '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n<Response><Redirect redirectUrl="https://example.com/a?info=&lt;https://example.com/c.pem&gt;" /><SpeakSentence voice="julie" gender="female" locale="en_US">Hello. Your number is <say-as interpret-as="telephone">asdf</say-as>, lets play a game.</SpeakSentence></Response>'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another nitpick but can we make this a double quote string instead of escaping the single quotes, to match the other strings

@nasanov-bw
nasanov-bw merged commit 0a3433f into main Nov 5, 2026
24 checks passed
@nasanov-bw
nasanov-bw deleted the VAPI-4457_scope-ssml-unescape-to-speaksentence branch November 5, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants