From 6b1e460241bf5599054267a38211b08d60e9e9ad Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 21 Feb 2026 00:28:35 +0000 Subject: [PATCH 1/3] Fix: Escape square brackets in LaTeX to prevent optional argument injection - Updated `cli/utils/template_filters.py` to escape `[` as `{[}` and `]` as `{]}`. - Updated `resume_pdf_lib/generator.py` to escape `[` and `]` similarly. - Added `tests/test_bracket_escape.py` to verify bracket escaping. - Updated `tests/test_template_generator.py` to assert that brackets are escaped. - Added entry to `.jules/sentinel.md` documenting the vulnerability and fix. This prevents attackers from injecting LaTeX commands via optional arguments (e.g., `\item[...]`) if user input is used in such contexts. Co-authored-by: anchapin <6326294+anchapin@users.noreply.github.com> --- .jules/sentinel.md | 5 +++++ cli/utils/template_filters.py | 2 ++ resume_pdf_lib/generator.py | 4 +++- tests/test_bracket_escape.py | 23 +++++++++++++++++++++++ tests/test_template_generator.py | 2 +- 5 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tests/test_bracket_escape.py diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 3a1d237..2da22c6 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -7,3 +7,8 @@ **Vulnerability:** The `CoverLetterGenerator` used a standard Jinja2 environment (intended for HTML/XML or plain text) to render LaTeX templates. This allowed malicious user input (or AI hallucinations) containing LaTeX control characters (e.g., `\input{...}`) to be injected directly into the LaTeX source, leading to potential Local File Inclusion (LFI) or other exploits. **Learning:** Jinja2's default `autoescape` is context-aware based on file extensions, but usually only for HTML/XML. It does NOT automatically escape LaTeX special characters. Relying on manual filters (like `| latex_escape`) in templates is error-prone and brittle, as developers might forget to apply them to every variable. **Prevention:** Always use a dedicated Jinja2 environment for LaTeX generation that enforces auto-escaping via a `finalize` hook (e.g., `tex_env.finalize = latex_escape`). This ensures *all* variable output is sanitized by default, providing defense-in-depth even if the template author forgets explicit filters. + +## 2025-02-24 - [High] LaTeX Injection via Optional Arguments +**Vulnerability:** The `latex_escape` utility function did not escape square brackets `[` and `]`. In LaTeX, these characters are used to denote optional arguments (e.g., `\item[label]`). If user input containing unescaped brackets is placed inside an optional argument context, it allows attackers to break out of the intended structure and inject arbitrary LaTeX commands. +**Learning:** Standard LaTeX escaping often focuses on special characters like `\`, `{`, `}`, `$`, etc., but overlooks context-specific delimiters like `[` and `]`. Security functions must consider the syntactic role of characters in the target language, not just the "obvious" special characters. +**Prevention:** Escape `[` as `{[}` and `]` as `{]}` in all LaTeX sanitization logic. This neutralizes their syntactic meaning while preserving their visual rendering, preventing injection attacks in optional argument contexts. diff --git a/cli/utils/template_filters.py b/cli/utils/template_filters.py index 5fd3852..70ade2e 100644 --- a/cli/utils/template_filters.py +++ b/cli/utils/template_filters.py @@ -74,6 +74,8 @@ def latex_escape(text): replacements["\\"] = r"\textbackslash{}" replacements["{"] = r"\{" replacements["}"] = r"\}" + replacements["["] = r"{[}" + replacements["]"] = r"{]}" # 3. Build regex pattern (keys sorted by length descending to match longest first) # Escape keys to handle regex special characters in the keys themselves diff --git a/resume_pdf_lib/generator.py b/resume_pdf_lib/generator.py index b196bc8..80b2431 100644 --- a/resume_pdf_lib/generator.py +++ b/resume_pdf_lib/generator.py @@ -490,7 +490,7 @@ def latex_escape(text: Any) -> Markup: if char == "\\": result.append(r"\textbackslash{}") - elif char in "&%$#_{}~^<>": + elif char in "&%$#_{}~^<>[]": escaped_map = { "&": r"\&", "%": r"\%", @@ -499,6 +499,8 @@ def latex_escape(text: Any) -> Markup: "_": r"\_", "{": r"\{", "}": r"\}", + "[": r"{[}", + "]": r"{]}", "~": r"\textasciitilde{}", "^": r"\^{}", "<": r"\textless{}", diff --git a/tests/test_bracket_escape.py b/tests/test_bracket_escape.py new file mode 100644 index 0000000..47c83f2 --- /dev/null +++ b/tests/test_bracket_escape.py @@ -0,0 +1,23 @@ +from cli.utils.template_filters import latex_escape as cli_latex_escape +from resume_pdf_lib.generator import latex_escape as lib_latex_escape +from markupsafe import Markup + +def test_cli_bracket_escape(): + """Test that cli.utils.template_filters.latex_escape escapes [ and ].""" + input_str = "[Optional]" + expected = r"{[}Optional{]}" + assert str(cli_latex_escape(input_str)) == expected + + input_str = r"\item[Optional]" + expected = r"\textbackslash{}item{[}Optional{]}" + assert str(cli_latex_escape(input_str)) == expected + +def test_lib_bracket_escape(): + """Test that resume_pdf_lib.generator.latex_escape escapes [ and ].""" + input_str = "[Optional]" + expected = r"{[}Optional{]}" + assert str(lib_latex_escape(input_str)) == expected + + input_str = r"\item[Optional]" + expected = r"\textbackslash{}item{[}Optional{]}" + assert str(lib_latex_escape(input_str)) == expected diff --git a/tests/test_template_generator.py b/tests/test_template_generator.py index 44f7bf0..69bcd2b 100644 --- a/tests/test_template_generator.py +++ b/tests/test_template_generator.py @@ -56,7 +56,7 @@ def test_latex_escape_special_chars(self): assert filter_func("# section") == r"\# section" assert filter_func("text_var") == r"text\_var" assert filter_func("{item}") == r"\{item\}" - assert filter_func("[key]") == r"[key]" # Brackets not escaped + assert filter_func("[key]") == r"{[}key{]}" def test_latex_escape_copyright_symbols(self): """Test latex_escape escapes copyright symbols.""" From 98809ccc2671776fab08276045e153cdf2e2d6f5 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 21 Feb 2026 00:36:13 +0000 Subject: [PATCH 2/3] Fix: Escape square brackets in LaTeX to prevent optional argument injection - Updated `cli/utils/template_filters.py` to escape `[` as `{[}` and `]` as `{]}`. - Updated `resume_pdf_lib/generator.py` to escape `[` and `]` similarly. - Added `tests/test_bracket_escape.py` to verify bracket escaping. - Updated `tests/test_template_generator.py` to assert that brackets are escaped. - Added entry to `.jules/sentinel.md` documenting the vulnerability and fix. This prevents attackers from injecting LaTeX commands via optional arguments (e.g., `\item[...]`) if user input is used in such contexts. Co-authored-by: anchapin <6326294+anchapin@users.noreply.github.com> --- tests/test_bracket_escape.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_bracket_escape.py b/tests/test_bracket_escape.py index 47c83f2..c61a391 100644 --- a/tests/test_bracket_escape.py +++ b/tests/test_bracket_escape.py @@ -1,6 +1,8 @@ +from markupsafe import Markup + from cli.utils.template_filters import latex_escape as cli_latex_escape from resume_pdf_lib.generator import latex_escape as lib_latex_escape -from markupsafe import Markup + def test_cli_bracket_escape(): """Test that cli.utils.template_filters.latex_escape escapes [ and ].""" @@ -12,6 +14,7 @@ def test_cli_bracket_escape(): expected = r"\textbackslash{}item{[}Optional{]}" assert str(cli_latex_escape(input_str)) == expected + def test_lib_bracket_escape(): """Test that resume_pdf_lib.generator.latex_escape escapes [ and ].""" input_str = "[Optional]" From 4f6a1c921e65efa00bee36123c437251f065ff21 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 21 Feb 2026 00:44:27 +0000 Subject: [PATCH 3/3] Fix: Escape square brackets in LaTeX to prevent optional argument injection - Updated `cli/utils/template_filters.py` to escape `[` as `{[}` and `]` as `{]}`. - Updated `resume_pdf_lib/generator.py` to escape `[` and `]` similarly. - Added `tests/test_bracket_escape.py` to verify bracket escaping. - Updated `tests/test_template_generator.py` to assert that brackets are escaped. - Added entry to `.jules/sentinel.md` documenting the vulnerability and fix. This prevents attackers from injecting LaTeX commands via optional arguments (e.g., `\item[...]`) if user input is used in such contexts. Co-authored-by: anchapin <6326294+anchapin@users.noreply.github.com> --- tests/test_bracket_escape.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_bracket_escape.py b/tests/test_bracket_escape.py index c61a391..3c28279 100644 --- a/tests/test_bracket_escape.py +++ b/tests/test_bracket_escape.py @@ -1,5 +1,3 @@ -from markupsafe import Markup - from cli.utils.template_filters import latex_escape as cli_latex_escape from resume_pdf_lib.generator import latex_escape as lib_latex_escape