Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 2 additions & 0 deletions cli/utils/template_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion resume_pdf_lib/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"\%",
Expand All @@ -499,6 +499,8 @@ def latex_escape(text: Any) -> Markup:
"_": r"\_",
"{": r"\{",
"}": r"\}",
"[": r"{[}",
"]": r"{]}",
"~": r"\textasciitilde{}",
"^": r"\^{}",
"<": r"\textless{}",
Expand Down
24 changes: 24 additions & 0 deletions tests/test_bracket_escape.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from cli.utils.template_filters import latex_escape as cli_latex_escape
from resume_pdf_lib.generator import latex_escape as lib_latex_escape


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
2 changes: 1 addition & 1 deletion tests/test_template_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down