Released in v2026.10.17, 21 Oct 2026  Upstream bug fix in mise

mise: unterminated string in an aqua template hung the lexer

Our scanner flagged that mise, the polyglot tool-version manager, could loop forever while reading a template from an aqua registry entry. We sent a pull request with a fix and a regression test. Maintainer Jeff Dickey added more tests and merged it the same day, and the fix shipped in mise v2026.10.17.

What was wrong

mise installs many tools through the aqua backend. Registry entries describe download URLs and asset names with small Go-style templates, which mise parses with its own lexer in crates/aqua-registry/src/template.rs.

When a token started with ", lex() looked for the closing quote. If there was none, or every candidate quote was escaped, nothing advanced the input, and the outer while !code.is_empty() loop kept re-entering the same branch. A malformed template such as {{"abc}} made mise hang instead of reporting an error.

The escape check had a second problem: it took a byte offset from match_indices and used it as a character index (code.chars().nth(end)), and it did not tell \" apart from \\".

The templates come from the baked-in registry or from registries the user configures, so this is a hang on a broken registry entry, not a remote attack. Upstream describes it the same way.

How it was fixed

The lexer now counts the backslashes in front of each candidate quote byte-wise, treats the quote as closing only when that count is even, and stops with an error when no closing quote is found:

             for (end, _) in code[1..].match_indices('"') {
-                if code.chars().nth(end) != Some('\\') {
-                    tokens.push(Token::String(&code[1..end + 1]));
+                let slash_count = code[1..=end]
+                    .bytes()
+                    .rev()
+                    .take_while(|&b| b == b'\\')
+                    .count();
+                if slash_count % 2 == 0 {
+                    tokens.push(Token::String(&code[1..=end]));
                     code = &code[end + 2..];
+                    found = true;
                     break;
                 }
             }
+            if !found {
+                bail!("unterminated string literal");
+            }

Our pull request added the regression test test_render_unterminated_string_fails. Jeff Dickey (jdx) then added commits that assert the exact “unterminated string literal” error and cover an empty string literal, and merged the pull request. The mise release notes for v2026.10.17 describe the change as:

“An aqua registry template with an unterminated string literal now fails with an "unterminated string literal" error instead of hanging. Escaped quotes after backslashes are also detected correctly.”

Timeline

How to read this

The scanner’s triage pictured a malicious registry or a man in the middle. In practice the templates come from the registry mise ships with or from registries the user chose, so the honest description is a hang on a broken template. That is how upstream describes it, too. Findings are leads for people, not verdicts.

Sources

← Back to Success Stories