Every CVE Search/ Get API key

CVE-2026-57170

Trestle SSTI in Jinja2 include tags allows arbitrary code execution (Incomplete fix of CVE-2026-46439)

7.8 HighFix available

Compliance-trestle (Trestle) is a Python SDK and command-line tool for managing OSCAL compliance documents. In versions prior to 3.12.4 and 4.0.0 through 4.0.3, the custom Jinja2 include tags mdsection_include and md_clean_include re-parse the content of an included Markdown file as Jinja2 template code in a non-sandboxed environment, allowing server-side template injection that can lead to arbitrary code execution. The MDSectionInclude and MDCleanInclude tags in Trestle/core/jinja/tags.py pass included file content to Parser(self.environment, ...).parse(), splicing it into the host template's compilation, and the environment is a plain jinja2.Environment rather than a SandboxedEnvironment, so any expressions in the file are evaluated with full access to the usual SSTI gadget chain. Because Trestle's Markdown writers emit OSCAL prose and component-description fields verbatim, applying delimiter neutralization only to parameter tables, attacker-controlled OSCAL data such as a control statement, part prose, or component description containing Jinja2 syntax flows into an included Markdown file and is executed when the include tag re-parses it. This issue is fixed in version 4.1.0.

Attack profile

How easy it is to exploit

Attack vector
Local
Complexity
Low
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
High
Integrity
High
Availability
High

Exploit likelihood

0.24%Unchanged since Aug 26, 2026
90%50%10%1%

What each source reports

CVE List Score used 7.8

Trestle SSTI in Jinja2 include tags allows arbitrary code execution (Incomplete fix of CVE-2026-46439)

Compliance-trestle (Trestle) is a Python SDK and command-line tool for managing OSCAL compliance documents. In versions prior to 3.12.4 and 4.0.0 through 4.0.3, the custom Jinja2 include tags mdsection_include and md_clean_include re-parse the content of an included Markdown file as Jinja2 template code in a non-sandboxed environment, allowing server-side template injection that can lead to arbitrary code execution. The MDSectionInclude and MDCleanInclude tags in Trestle/core/jinja/tags.py pass included file content to Parser(self.environment, ...).parse(), splicing it into the host template's compilation, and the environment is a plain jinja2.Environment rather than a SandboxedEnvironment, so any expressions in the file are evaluated with full access to the usual SSTI gadget chain. Because Trestle's Markdown writers emit OSCAL prose and component-description fields verbatim, applying delimiter neutralization only to parameter tables, attacker-controlled OSCAL data such as a control statement, part prose, or component description containing Jinja2 syntax flows into an included Markdown file and is executed when the include tag re-parses it. This issue is fixed in version 4.1.0.

Record
CVE-2026-57170
CVSS 3.1
7.8 HighCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-94, CWE-1336
Updated
2026-11-02
EUVDEUVD-2026-66211 7.8

Compliance-trestle (Trestle) is a Python SDK and command-line tool for managing OSCAL compliance documents. In versions prior to 3.12.4 and 4.0.0 through 4.0.3, the custom Jinja2 include tags mdsection_include and md_clean_include re-parse the content of an included Markdown file as Jinja2 template code in a non-sandboxed environment, allowing server-side template injection that can lead to arbitrary code execution. The MDSectionInclude and MDCleanInclude tags in Trestle/core/jinja/tags.py pass included file content to Parser(self.environment, ...).parse(), splicing it into the host template's compilation, and the environment is a plain jinja2.Environment rather than a SandboxedEnvironment, so any expressions in the file are evaluated with full access to the usual SSTI gadget chain. Because Trestle's Markdown writers emit OSCAL prose and component-description fields verbatim, applying delimiter neutralization only to parameter tables, attacker-controlled OSCAL data such as a control statement, part prose, or component description containing Jinja2 syntax flows into an included Markdown file and is executed when the include tag re-parses it. This issue is fixed in version 4.1.0.

Record
EUVD-2026-66211
CVSS 3.1
7.8 HighCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Updated
2026-11-02
GitHub AdvisoriesGHSA-mr95-65j8-9mxp 7.8

Trestle SSTI in Jinja2 include tags allows arbitrary code execution (Incomplete fix of CVE-2026-46439)

Reporter: Cavan Loughran, Celvex Group Inc.

Summary

The fix for CVE-2026-46439 (3.12.2 / 4.0.3) removed the recursive re-render loop in trestle/core/commands/author/jinja.py render_template, but the custom include tags in trestle/core/jinja/tags.py (MDSectionInclude, MDCleanInclude) still re-parse the CONTENT of an included markdown file as a Jinja2 template via Parser(self.environment, <file text>).parse() in a plain (non-sandboxed) jinja2.Environment. Because the trestle markdown writers (ssp_io.py SSPMarkdownWriter, docs_control_writer.py DocsControlWriter) write OSCAL prose / component-description fields verbatim (the {{ -> [[ neutralization is applied to parameter tables only), attacker-controlled OSCAL data that flows into an included markdown file is interpreted as template code and can achieve arbitrary code execution on the runner. This is the same trust boundary and impact as CVE-2026-46439 via a sink the fix did not cover.

Affected versions

4.0.3 and earlier on this code path (the include tags predate and were untouched by the parent fix); the 3.12.x line likewise. Verified present in the fixed release 4.0.3 by direct source read.

Technical detail

Sink (UNCHANGED by the parent fix), trestle/core/jinja/tags.py:

MDSectionInclude.parse (the {% mdsection_include 'file.md' '# Section' %} tag), approximately: md_content, _, _ = self.environment.loader.get_source(self.environment, markdown_source.value) # ~line 90 ... local_parser = Parser(self.environment, md_section.content.raw_text) # ~line 104 <-- RE-PARSE top_level_output = local_parser.parse() # ~line 105 return top_level_output.body # ~line 106

MDCleanInclude.parse (the {% md_clean_include 'file.md' %} tag), approximately: md_content, _, _ = self.environment.loader.get_source(self.environment, markdown_source.value) # ~line 141 ... local_parser = Parser(self.environment, content) # ~line 150 <-- RE-PARSE top_level_output = local_parser.parse() # ~line 151 return top_level_output.body # ~line 152

Parser(self.environment, <included file text>).parse() compiles the included file's markdown text AS A JINJA TEMPLATE and splices the resulting AST .body directly into the host template's compilation. Any {{ ... }} / {% ... %} present in the included file is interpreted as template code, not emitted as literal text. The single-pass fix in render_template has no effect on this: the re-parse happens at compile time inside the tag, on a string the outer render loop never sees as data.

Environment is NOT sandboxed (UNCHANGED by the fix), trestle/core/commands/author/jinja.py _create_jinja_environment: return Environment(loader=FileSystemLoader(template_folder), extensions=extensions(), trim_blocks=True, autoescape=True) This is a plain jinja2.Environment, not jinja2.sandbox.SandboxedEnvironment, so a re-parsed expression has full access to the usual Jinja SSTI gadget chain leading to OS command execution. autoescape=True HTML-escapes output but does not constrain expression evaluation, so it does not mitigate code execution.

Data bridge (partial neutralization only): the included markdown is, in trestle's documented workflow, produced from OSCAL by the trestle generators / writers. trestle/core/ssp_io.py SSPMarkdownWriter writes control statement prose (get_control_statement_ssp) and component implementation descriptions (prose = by_comp.description) into markdown with no Jinja-delimiter escaping. trestle/core/docs_control_writer.py DocsControlWriter writes control section prose (self._md_file.new_line(prose)) and part text raw. The maintainers DO know about the {{-in-data risk: ssp_io.py applies line.replace('{{', '[[').replace('}}', ']]') to neutralize delimiters, but ONLY inside the parameter-table method (_parameter_table). Control-statement prose and component description fields receive…

Record
GHSA-mr95-65j8-9mxp
CVSS 3.1
7.8 HighCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-94, CWE-1336
Updated
2026-11-02
NVD 7.8

Compliance-trestle (Trestle) is a Python SDK and command-line tool for managing OSCAL compliance documents. In versions prior to 3.12.4 and 4.0.0 through 4.0.3, the custom Jinja2 include tags mdsection_include and md_clean_include re-parse the content of an included Markdown file as Jinja2 template code in a non-sandboxed environment, allowing server-side template injection that can lead to arbitrary code execution. The MDSectionInclude and MDCleanInclude tags in Trestle/core/jinja/tags.py pass included file content to Parser(self.environment, ...).parse(), splicing it into the host template's compilation, and the environment is a plain jinja2.Environment rather than a SandboxedEnvironment, so any expressions in the file are evaluated with full access to the usual SSTI gadget chain. Because Trestle's Markdown writers emit OSCAL prose and component-description fields verbatim, applying delimiter neutralization only to parameter tables, attacker-controlled OSCAL data such as a control statement, part prose, or component description containing Jinja2 syntax flows into an included Markdown file and is executed when the include tag re-parses it. This issue is fixed in version 4.1.0.

Record
CVE-2026-57170
CVSS 3.1
7.8 HighCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-94, CWE-1336
Updated
2026-11-02
OSV 7.8

Trestle SSTI in Jinja2 include tags allows arbitrary code execution (Incomplete fix of CVE-2026-46439)

Compliance-trestle (Trestle) is a Python SDK and command-line tool for managing OSCAL compliance documents. In versions prior to 3.12.4 and 4.0.0 through 4.0.3, the custom Jinja2 include tags mdsection_include and md_clean_include re-parse the content of an included Markdown file as Jinja2 template code in a non-sandboxed environment, allowing server-side template injection that can lead to arbitrary code execution. The MDSectionInclude and MDCleanInclude tags in Trestle/core/jinja/tags.py pass included file content to Parser(self.environment, ...).parse(), splicing it into the host template's compilation, and the environment is a plain jinja2.Environment rather than a SandboxedEnvironment, so any expressions in the file are evaluated with full access to the usual SSTI gadget chain. Because Trestle's Markdown writers emit OSCAL prose and component-description fields verbatim, applying delimiter neutralization only to parameter tables, attacker-controlled OSCAL data such as a control statement, part prose, or component description containing Jinja2 syntax flows into an included Markdown file and is executed when the include tag re-parses it. This issue is fixed in version 4.1.0.

Record
CVE-2026-57170
CVSS 3.1
7.8 HighCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-1336, CWE-94
Updated
2026-11-02
OSVGHSA-mr95-65j8-9mxp 7.8

Trestle SSTI in Jinja2 include tags allows arbitrary code execution (Incomplete fix of CVE-2026-46439)

Reporter: Cavan Loughran, Celvex Group Inc.

Summary

The fix for CVE-2026-46439 (3.12.2 / 4.0.3) removed the recursive re-render loop in trestle/core/commands/author/jinja.py render_template, but the custom include tags in trestle/core/jinja/tags.py (MDSectionInclude, MDCleanInclude) still re-parse the CONTENT of an included markdown file as a Jinja2 template via Parser(self.environment, <file text>).parse() in a plain (non-sandboxed) jinja2.Environment. Because the trestle markdown writers (ssp_io.py SSPMarkdownWriter, docs_control_writer.py DocsControlWriter) write OSCAL prose / component-description fields verbatim (the {{ -> [[ neutralization is applied to parameter tables only), attacker-controlled OSCAL data that flows into an included markdown file is interpreted as template code and can achieve arbitrary code execution on the runner. This is the same trust boundary and impact as CVE-2026-46439 via a sink the fix did not cover.

Affected versions

4.0.3 and earlier on this code path (the include tags predate and were untouched by the parent fix); the 3.12.x line likewise. Verified present in the fixed release 4.0.3 by direct source read.

Technical detail

Sink (UNCHANGED by the parent fix), trestle/core/jinja/tags.py:

MDSectionInclude.parse (the {% mdsection_include 'file.md' '# Section' %} tag), approximately: md_content, _, _ = self.environment.loader.get_source(self.environment, markdown_source.value) # ~line 90 ... local_parser = Parser(self.environment, md_section.content.raw_text) # ~line 104 <-- RE-PARSE top_level_output = local_parser.parse() # ~line 105 return top_level_output.body # ~line 106

MDCleanInclude.parse (the {% md_clean_include 'file.md' %} tag), approximately: md_content, _, _ = self.environment.loader.get_source(self.environment, markdown_source.value) # ~line 141 ... local_parser = Parser(self.environment, content) # ~line 150 <-- RE-PARSE top_level_output = local_parser.parse() # ~line 151 return top_level_output.body # ~line 152

Parser(self.environment, <included file text>).parse() compiles the included file's markdown text AS A JINJA TEMPLATE and splices the resulting AST .body directly into the host template's compilation. Any {{ ... }} / {% ... %} present in the included file is interpreted as template code, not emitted as literal text. The single-pass fix in render_template has no effect on this: the re-parse happens at compile time inside the tag, on a string the outer render loop never sees as data.

Environment is NOT sandboxed (UNCHANGED by the fix), trestle/core/commands/author/jinja.py _create_jinja_environment: return Environment(loader=FileSystemLoader(template_folder), extensions=extensions(), trim_blocks=True, autoescape=True) This is a plain jinja2.Environment, not jinja2.sandbox.SandboxedEnvironment, so a re-parsed expression has full access to the usual Jinja SSTI gadget chain leading to OS command execution. autoescape=True HTML-escapes output but does not constrain expression evaluation, so it does not mitigate code execution.

Data bridge (partial neutralization only): the included markdown is, in trestle's documented workflow, produced from OSCAL by the trestle generators / writers. trestle/core/ssp_io.py SSPMarkdownWriter writes control statement prose (get_control_statement_ssp) and component implementation descriptions (prose = by_comp.description) into markdown with no Jinja-delimiter escaping. trestle/core/docs_control_writer.py DocsControlWriter writes control section prose (self._md_file.new_line(prose)) and part text raw. The maintainers DO know about the {{-in-data risk: ssp_io.py applies line.replace('{{', '[[').replace('}}', ']]') to neutralize delimiters, but ONLY inside the parameter-table method (_parameter_table). Control-statement prose and component description fields receive…

Record
GHSA-mr95-65j8-9mxp
CVSS 3.1
7.8 HighCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-1336, CWE-94
Updated
2026-11-02
OSVPYSEC-2026-4029 7.8

Trestle SSTI in Jinja2 include tags allows arbitrary code execution (Incomplete fix of CVE-2026-46439)

Reporter: Cavan Loughran, Celvex Group Inc.

Summary

The fix for CVE-2026-46439 (3.12.2 / 4.0.3) removed the recursive re-render loop in trestle/core/commands/author/jinja.py render_template, but the custom include tags in trestle/core/jinja/tags.py (MDSectionInclude, MDCleanInclude) still re-parse the CONTENT of an included markdown file as a Jinja2 template via Parser(self.environment, <file text>).parse() in a plain (non-sandboxed) jinja2.Environment. Because the trestle markdown writers (ssp_io.py SSPMarkdownWriter, docs_control_writer.py DocsControlWriter) write OSCAL prose / component-description fields verbatim (the {{ -> [[ neutralization is applied to parameter tables only), attacker-controlled OSCAL data that flows into an included markdown file is interpreted as template code and can achieve arbitrary code execution on the runner. This is the same trust boundary and impact as CVE-2026-46439 via a sink the fix did not cover.

Affected versions

4.0.3 and earlier on this code path (the include tags predate and were untouched by the parent fix); the 3.12.x line likewise. Verified present in the fixed release 4.0.3 by direct source read.

Technical detail

Sink (UNCHANGED by the parent fix), trestle/core/jinja/tags.py:

MDSectionInclude.parse (the {% mdsection_include 'file.md' '# Section' %} tag), approximately: md_content, _, _ = self.environment.loader.get_source(self.environment, markdown_source.value) # ~line 90 ... local_parser = Parser(self.environment, md_section.content.raw_text) # ~line 104 <-- RE-PARSE top_level_output = local_parser.parse() # ~line 105 return top_level_output.body # ~line 106

MDCleanInclude.parse (the {% md_clean_include 'file.md' %} tag), approximately: md_content, _, _ = self.environment.loader.get_source(self.environment, markdown_source.value) # ~line 141 ... local_parser = Parser(self.environment, content) # ~line 150 <-- RE-PARSE top_level_output = local_parser.parse() # ~line 151 return top_level_output.body # ~line 152

Parser(self.environment, <included file text>).parse() compiles the included file's markdown text AS A JINJA TEMPLATE and splices the resulting AST .body directly into the host template's compilation. Any {{ ... }} / {% ... %} present in the included file is interpreted as template code, not emitted as literal text. The single-pass fix in render_template has no effect on this: the re-parse happens at compile time inside the tag, on a string the outer render loop never sees as data.

Environment is NOT sandboxed (UNCHANGED by the fix), trestle/core/commands/author/jinja.py _create_jinja_environment: return Environment(loader=FileSystemLoader(template_folder), extensions=extensions(), trim_blocks=True, autoescape=True) This is a plain jinja2.Environment, not jinja2.sandbox.SandboxedEnvironment, so a re-parsed expression has full access to the usual Jinja SSTI gadget chain leading to OS command execution. autoescape=True HTML-escapes output but does not constrain expression evaluation, so it does not mitigate code execution.

Data bridge (partial neutralization only): the included markdown is, in trestle's documented workflow, produced from OSCAL by the trestle generators / writers. trestle/core/ssp_io.py SSPMarkdownWriter writes control statement prose (get_control_statement_ssp) and component implementation descriptions (prose = by_comp.description) into markdown with no Jinja-delimiter escaping. trestle/core/docs_control_writer.py DocsControlWriter writes control section prose (self._md_file.new_line(prose)) and part text raw. The maintainers DO know about the {{-in-data risk: ssp_io.py applies line.replace('{{', '[[').replace('}}', ']]') to neutralize delimiters, but ONLY inside the parameter-table method (_parameter_table). Control-statement prose and component description fields receive…

Record
PYSEC-2026-4029
CVSS 3.1
7.8 HighCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
compliance-trestle
PyPI
before 3.12.4
3.12.4
compliance-trestle
PyPI
4.0.0 up to 4.1.0
4.1.0
compliance-trestle
PyPI
before 3.12.4, plus listed versions
0.0.20.0.30.1.00.1.10.10.0
Show 45 moreShow less
0.11.00.12.00.13.00.13.10.14.00.14.10.14.20.14.30.14.40.15.00.15.10.16.00.17.00.18.00.18.10.19.00.2.00.2.10.2.20.20.00.21.00.22.00.22.10.23.00.24.00.25.00.25.10.26.00.27.00.27.10.27.20.28.00.28.10.29.00.3.00.30.00.31.00.32.00.32.10.33.00.34.00.35.00.36.00.37.00.4.0
3.12.4
compliance-trestle
PyPI
4.0.0 up to 4.1.0, plus listed versions
4.0.14.0.24.0.3
4.1.0
compliance-trestle
PyPI
before 3.12.4, 4.0.0 up to 4.1.0, plus listed versions
0.0.20.0.30.1.00.1.10.10.0
Show 45 moreShow less
0.11.00.12.00.13.00.13.10.14.00.14.10.14.20.14.30.14.40.15.00.15.10.16.00.17.00.18.00.18.10.19.00.2.00.2.10.2.20.20.00.21.00.22.00.22.10.23.00.24.00.25.00.25.10.26.00.27.00.27.10.27.20.28.00.28.10.29.00.3.00.30.00.31.00.32.00.32.10.33.00.34.00.35.00.36.00.37.00.4.0
3.12.44.1.0

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-57170 is a high vulnerability (CVSS 7.8) in Oscal-compass Compliance-trestle, published August 25, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.24%, the 14th percentile. Fixed in compliance-trestle 3.12.4.

References

Show 5 moreShow less
This page doesn't list everything

Left out here: 101 versions and the rest of 3 source descriptions. The JSON record has them.

MoveEnter Open