CVE-2026-113731
djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool
djust provides Phoenix LiveView-style reactive server-side rendering for Django with Rust-powered performance. From 0.9.0 until 1.0.10, 1.1.7, 1.2.4, and 1.3.0rc8, the find_handlers_for_template MCP tool in python/djust/mcp/server.py accepts an existing absolute template_path and reads it without restricting the resolved path to the project or configured template directories. A local MCP client can use the tool as a file existence and readability oracle and can obtain substrings that match the tool's dj-click, dj-submit, dj-change, dj-input, dj-keydown, or dj-keyup attribute pattern from files readable by the djust process. The maintainer assessment states that the MCP server communicates over local stdio as the same operating-system user and has no network listener, and the tool does not return arbitrary raw file contents. This issue is fixed in versions 1.0.10, 1.1.7, 1.2.4, and 1.3.0rc8.
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
- Low
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
3.3
djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool
djust provides Phoenix LiveView-style reactive server-side rendering for Django with Rust-powered performance. From 0.9.0 until 1.0.10, 1.1.7, 1.2.4, and 1.3.0rc8, the find_handlers_for_template MCP tool in python/djust/mcp/server.py accepts an existing absolute template_path and reads it without restricting the resolved path to the project or configured template directories. A local MCP client can use the tool as a file existence and readability oracle and can obtain substrings that match the tool's dj-click, dj-submit, dj-change, dj-input, dj-keydown, or dj-keyup attribute pattern from files readable by the djust process. The maintainer assessment states that the MCP server communicates over local stdio as the same operating-system user and has no network listener, and the tool does not return arbitrary raw file contents. This issue is fixed in versions 1.0.10, 1.1.7, 1.2.4, and 1.3.0rc8.
- Record
CVE-2026-113731- CVSS 3.1
- 3.3 Low
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
EUVDEUVD-2026-106855
3.3
djust provides Phoenix LiveView-style reactive server-side rendering for Django with Rust-powered performance. From 0.9.0 until 1.0.10, 1.1.7, 1.2.4, and 1.3.0rc8, the find_handlers_for_template MCP tool in python/djust/mcp/server.py accepts an existing absolute template_path and reads it without restricting the resolved path to the project or configured template directories. A local MCP client can use the tool as a file existence and readability oracle and can obtain substrings that match the tool's dj-click, dj-submit, dj-change, dj-input, dj-keydown, or dj-keyup attribute pattern from files readable by the djust process. The maintainer assessment states that the MCP server communicates over local stdio as the same operating-system user and has no network listener, and the tool does not return arbitrary raw file contents. This issue is fixed in versions 1.0.10, 1.1.7, 1.2.4, and 1.3.0rc8.
- Record
EUVD-2026-106855- CVSS 3.1
- 3.3 Low
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-r57g-mc79-8hj2
3.3
djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool
Affected version / file / line
Version: djust v1.1.4 (latest release, published 2026-09-23). File: python/djust/mcp/server.py:354-433 (MCP tool find_handlers_for_template). Confirmed identical on the commit used for triage, a7e4fafd1667b4db0b1465652d4a63cb17117a07.
Description
@mcp.tool()
def find_handlers_for_template(template_path: str) -> str:
"""
Args:
template_path: Either a logical template name (e.g.
'demos/counter.html' — what you'd pass to `render`) OR
an absolute filesystem path. Both are searched.
"""
...
resolved_path: str | None = None
if os.path.isabs(template_path) and os.path.isfile(template_path):
resolved_path = template_path
else:
... # logical-name resolution via Django's template loader
...
with open(resolved_path, "r", encoding="utf-8", errors="replace") as f:
source = f.read()
handler_names_in_template = sorted({m.group(2) for m in dj_event_attr_re.finditer(source)})
When template_path is an absolute path to an existing file, it is used directly, with no confinement to the project/template directories — no realpath() + prefix check, no allowlist. open(resolved_path, ...) then reads the file.
This is inconsistent with the sibling tool in the same file (loaddata, ~line 290-303), which explicitly rejects paths outside the project directory for its own path-like parameter:
candidate = os.path.realpath(...)
if base_dir and not candidate.startswith(os.path.realpath(str(base_dir))):
return json.dumps({"error": f"fixture path '{p}' resolves outside the project directory", ...})
find_handlers_for_template has no equivalent check for template_path before the open() call.
Impact
An MCP client with access to this tool (available whenever the Django project is running the MCP server via python manage.py djust_mcp — no separate opt-in flag gates this specific tool beyond that) can read the contents of any file on disk that the process has read permission to and that exists, by passing an absolute path — e.g. /etc/passwd, application .env files, SSH keys, or any other sensitive file reachable by the server process's user.
Mitigating factor (reduces this from High to Medium): the tool does not return the raw file content. It runs one fixed regex (dj-(?:click|submit|change|input|keydown|keyup)\s*=\s*'"['"]) against the content and returns only the matched handler-name strings, plus confirmation that the file exists and is readable (an existence/readability oracle on its own, independent of the regex match). If the targeted file happens to contain text matching that HTML-attribute-like pattern (plausible for config files, logs, or any text file with key="value"-shaped content), those substrings are returned in full — so the read is not purely blind, but it is not a general-purpose arbitrary-file-read primitive either.
Proof of Concept / Reproduction Traced via direct source read; no live Django project was exercised for this report (would require a full djust project scaffold, which was out of scope for this pass — see limitation below).
- MCP client calls: find_handlers_for_template(template_path="/etc/passwd")
- server.py ~line 393:
resolved_path == "/etc/passwd", unchanged, unconfinedif os.path.isabs(template_path) and os.path.isfile(template_path): resolved_path = template_path - server.py ~line 430:
/etc/passwd is now fully read intowith open(resolved_path, "r", encoding="utf-8", errors="replace") as f: source = f.read()source - The regex dj_event_attr_re.finditer(source) is applied to the real /etc/passwd content. /etc/passwd will not match this specific pattern, so the response would report an empty handler list plus "file exists and was read" - which already confirms the path-confinement bypass (compare with passing a nonexistent absolute path, which correctly…
- Record
GHSA-r57g-mc79-8hj2- CVSS 3.1
- 3.3 Low
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
NVD
3.3
djust provides Phoenix LiveView-style reactive server-side rendering for Django with Rust-powered performance. From 0.9.0 until 1.0.10, 1.1.7, 1.2.4, and 1.3.0rc8, the find_handlers_for_template MCP tool in python/djust/mcp/server.py accepts an existing absolute template_path and reads it without restricting the resolved path to the project or configured template directories. A local MCP client can use the tool as a file existence and readability oracle and can obtain substrings that match the tool's dj-click, dj-submit, dj-change, dj-input, dj-keydown, or dj-keyup attribute pattern from files readable by the djust process. The maintainer assessment states that the MCP server communicates over local stdio as the same operating-system user and has no network listener, and the tool does not return arbitrary raw file contents. This issue is fixed in versions 1.0.10, 1.1.7, 1.2.4, and 1.3.0rc8.
- Record
CVE-2026-113731- CVSS 3.1
- 3.3 Low
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
OSV
3.3
djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool
djust provides Phoenix LiveView-style reactive server-side rendering for Django with Rust-powered performance. From 0.9.0 until 1.0.10, 1.1.7, 1.2.4, and 1.3.0rc8, the find_handlers_for_template MCP tool in python/djust/mcp/server.py accepts an existing absolute template_path and reads it without restricting the resolved path to the project or configured template directories. A local MCP client can use the tool as a file existence and readability oracle and can obtain substrings that match the tool's dj-click, dj-submit, dj-change, dj-input, dj-keydown, or dj-keyup attribute pattern from files readable by the djust process. The maintainer assessment states that the MCP server communicates over local stdio as the same operating-system user and has no network listener, and the tool does not return arbitrary raw file contents. This issue is fixed in versions 1.0.10, 1.1.7, 1.2.4, and 1.3.0rc8.
- Record
CVE-2026-113731- CVSS 3.1
- 3.3 Low
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-05
OSVGHSA-r57g-mc79-8hj2
3.3
djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool
Affected version / file / line
Version: djust v1.1.4 (latest release, published 2026-09-23). File: python/djust/mcp/server.py:354-433 (MCP tool find_handlers_for_template). Confirmed identical on the commit used for triage, a7e4fafd1667b4db0b1465652d4a63cb17117a07.
Description
@mcp.tool()
def find_handlers_for_template(template_path: str) -> str:
"""
Args:
template_path: Either a logical template name (e.g.
'demos/counter.html' — what you'd pass to `render`) OR
an absolute filesystem path. Both are searched.
"""
...
resolved_path: str | None = None
if os.path.isabs(template_path) and os.path.isfile(template_path):
resolved_path = template_path
else:
... # logical-name resolution via Django's template loader
...
with open(resolved_path, "r", encoding="utf-8", errors="replace") as f:
source = f.read()
handler_names_in_template = sorted({m.group(2) for m in dj_event_attr_re.finditer(source)})
When template_path is an absolute path to an existing file, it is used directly, with no confinement to the project/template directories — no realpath() + prefix check, no allowlist. open(resolved_path, ...) then reads the file.
This is inconsistent with the sibling tool in the same file (loaddata, ~line 290-303), which explicitly rejects paths outside the project directory for its own path-like parameter:
candidate = os.path.realpath(...)
if base_dir and not candidate.startswith(os.path.realpath(str(base_dir))):
return json.dumps({"error": f"fixture path '{p}' resolves outside the project directory", ...})
find_handlers_for_template has no equivalent check for template_path before the open() call.
Impact
An MCP client with access to this tool (available whenever the Django project is running the MCP server via python manage.py djust_mcp — no separate opt-in flag gates this specific tool beyond that) can read the contents of any file on disk that the process has read permission to and that exists, by passing an absolute path — e.g. /etc/passwd, application .env files, SSH keys, or any other sensitive file reachable by the server process's user.
Mitigating factor (reduces this from High to Medium): the tool does not return the raw file content. It runs one fixed regex (dj-(?:click|submit|change|input|keydown|keyup)\s*=\s*'"['"]) against the content and returns only the matched handler-name strings, plus confirmation that the file exists and is readable (an existence/readability oracle on its own, independent of the regex match). If the targeted file happens to contain text matching that HTML-attribute-like pattern (plausible for config files, logs, or any text file with key="value"-shaped content), those substrings are returned in full — so the read is not purely blind, but it is not a general-purpose arbitrary-file-read primitive either.
Proof of Concept / Reproduction Traced via direct source read; no live Django project was exercised for this report (would require a full djust project scaffold, which was out of scope for this pass — see limitation below).
- MCP client calls: find_handlers_for_template(template_path="/etc/passwd")
- server.py ~line 393:
resolved_path == "/etc/passwd", unchanged, unconfinedif os.path.isabs(template_path) and os.path.isfile(template_path): resolved_path = template_path - server.py ~line 430:
/etc/passwd is now fully read intowith open(resolved_path, "r", encoding="utf-8", errors="replace") as f: source = f.read()source - The regex dj_event_attr_re.finditer(source) is applied to the real /etc/passwd content. /etc/passwd will not match this specific pattern, so the response would report an empty handler list plus "file exists and was read" - which already confirms the path-confinement bypass (compare with passing a nonexistent absolute path, which correctly…
- Record
GHSA-r57g-mc79-8hj2- CVSS 3.1
- 3.3 Low
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
djustPyPI |
0.9.0 to 1.0.9 | 1.0.10 |
djustPyPI |
1.1.0 to 1.1.6 | 1.1.7 |
djustPyPI |
1.2.0.dev0 to 1.2.3 | 1.2.4 |
djustPyPI |
1.3.0.dev0 to 1.3.0rc7 | 1.3.0rc8 |
djustPyPI |
0.9.0 up to 1.0.10, plus listed versions 0.9.10.9.20.9.40.9.50.9.6 Show 12 moreShow less0.9.71.0.01.0.0rc111.0.11.0.21.0.31.0.41.0.51.0.61.0.71.0.81.0.9 |
1.0.10 |
djustPyPI |
1.1.0 up to 1.1.7, plus listed versions 1.1.11.1.21.1.31.1.41.1.51.1.6 |
1.1.7 |
djustPyPI |
1.2.0.dev0 up to 1.2.4, plus listed versions 1.2.01.2.11.2.1rc11.2.1rc21.2.21.2.3 |
1.2.4 |
djustPyPI |
1.3.0.dev0 up to 1.3.0rc8, plus listed versions 1.3.0rc41.3.0rc51.3.0rc61.3.0rc7 |
1.3.0rc8 |
History
| Recorded | Changed | Sources |
|---|---|---|
| facts | OSV | |
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-113731 is a low vulnerability (CVSS 3.3) in Djust-org Djust, published October 20, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.19%, the 8th percentile. Fixed in djust 1.0.10.
References
github.com/djust-org/djust/security/advisories/GHSA-r57g-mc79-8hj2
github.com/djust-org/djust/commit/45a5045793ebe7c9c2df86b0ad34275af6822619
github.com/djust-org/djust/commit/744bccf699c65c815d9379b36236a7c5592a339b
github.com/djust-org/djust/commit/85eed7caaf620bdb08986f2f3bb79432c0d3d715
github.com/djust-org/djust/commit/a62caf6b4ef540ad688d4dfdfa407ff914c78728
github.com/djust-org/djust/releases/tag/v1.0.10
Show 7 moreShow less
github.com/djust-org/djust/releases/tag/v1.1.7
github.com/djust-org/djust/releases/tag/v1.2.4
github.com/djust-org/djust/releases/tag/v1.3.0rc8
nvd.nist.gov/vuln/detail/CVE-2026-113731
github.com/advisories/GHSA-r57g-mc79-8hj2
github.com/CVEProject/cvelistV5/tree/main/cves/2026/113xxx/CVE-2026-113731.json
github.com/djust-org/djust
Left out here: the rest of 2 source descriptions. The JSON record has them.