djust, a Django framework, ships an MCP server tool called find_handlers_for_template that accepts any absolute file path without confining it to the project or template directories. A local AI agent, possibly steered by untrusted input, can therefore confirm whether arbitrary files exist and read them, though the tool returns only substrings matching a fixed dj-* attribute regex, not raw file contents. The vendor rates it Low (CVSS 3.3, local vector, stdio only, no network listener). There is no CISA KEV listing, no public exploit, no Nuclei template and no EPSS score, so exploitation pressure is minimal. Upgrade to djust 1.0.10, 1.1.7, 1.2.4 or 1.3.0rc8, and until then restrict the MCP server to trusted agents or use logical template names only.
What is the risk?
Low overall. Exploitation needs local access as the same OS user that runs the MCP server (AV:L, PR:L), and the primary realistic threat is an AI agent manipulated by prompt injection. Impact is limited to a file existence and readability oracle plus disclosure of substrings matching dj-(click|submit|change|input|keydown|keyup)="..." patterns. The weakness is a missing server-side path confinement check (CWE-22), in contrast to the sibling loaddata tool, which already enforced one. No known exploitation, no public exploit code, and EPSS data is unavailable.
How does the attack unfold?
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| djust | pip | >= 0.9.0, <= 1.0.9 | 1.0.10 |
Do you use djust? You're affected.
How severe is it?
What is the attack surface?
What should I do?
1 step1) Upgrade djust to 1.0.10, 1.1.7, 1.2.4 or 1.3.0rc8 or later. 2) If patching is delayed, run the MCP server only in projects whose agent you trust with file access, and call the tool with logical template names only. 3) Require human approval for MCP tool calls that include absolute paths, and run agents with least-privilege OS accounts and a sandbox or container that excludes secrets such as .env files and SSH keys. 4) Detection: review MCP and agent logs for find_handlers_for_template calls with absolute paths outside the project or template directories, and watch for openat syscalls from the Django process to unexpected locations. 5) Inventory projects that depend on djust via pip to confirm the fixed version.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is CVE-2026-113731?
djust, a Django framework, ships an MCP server tool called find_handlers_for_template that accepts any absolute file path without confining it to the project or template directories. A local AI agent, possibly steered by untrusted input, can therefore confirm whether arbitrary files exist and read them, though the tool returns only substrings matching a fixed dj-* attribute regex, not raw file contents. The vendor rates it Low (CVSS 3.3, local vector, stdio only, no network listener). There is no CISA KEV listing, no public exploit, no Nuclei template and no EPSS score, so exploitation pressure is minimal. Upgrade to djust 1.0.10, 1.1.7, 1.2.4 or 1.3.0rc8, and until then restrict the MCP server to trusted agents or use logical template names only.
Is CVE-2026-113731 actively exploited?
No confirmed active exploitation of CVE-2026-113731 has been reported, but organizations should still patch proactively.
How to fix CVE-2026-113731?
1) Upgrade djust to 1.0.10, 1.1.7, 1.2.4 or 1.3.0rc8 or later. 2) If patching is delayed, run the MCP server only in projects whose agent you trust with file access, and call the tool with logical template names only. 3) Require human approval for MCP tool calls that include absolute paths, and run agents with least-privilege OS accounts and a sandbox or container that excludes secrets such as .env files and SSH keys. 4) Detection: review MCP and agent logs for find_handlers_for_template calls with absolute paths outside the project or template directories, and watch for openat syscalls from the Django process to unexpected locations. 5) Inventory projects that depend on djust via pip to confirm the fixed version.
What systems are affected by CVE-2026-113731?
This vulnerability affects the following AI/ML architecture patterns: agent frameworks, MCP servers, AI-assisted development environments, tool-calling LLM integrations.
What is the CVSS score for CVE-2026-113731?
CVE-2026-113731 has a CVSS v3.1 base score of 3.3 (LOW). The EPSS exploitation probability is 0.19%.
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0037Data from Local SystemAML.T0051.001IndirectAML.T0053AI Agent Tool InvocationAML.T0085.001AI Agent ToolsCompliance Controls Affected
What are the technical details?
Original Advisory
### 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). 1. MCP client calls: find_handlers_for_template(template_path="/etc/passwd") 2. server.py ~line 393: ``` if os.path.isabs(template_path) and os.path.isfile(template_path): resolved_path = template_path ``` resolved_path == "/etc/passwd", unchanged, unconfined 3. server.py ~line 430: ``` with open(resolved_path, "r", encoding="utf-8", errors="replace") as f: source = f.read() ``` /etc/passwd is now fully read into `source` 4. 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 returns "Template file not found"). 5. On a target where the absolute path points to a file that DOES contain text shaped like `dj-click="..."` (e.g. a copied HTML fragment, a stray config value, or a crafted file placed by the attacker via another vector), the matched substrings are returned verbatim in the JSON response - full-content disclosure of just those substrings, not the whole file. ### Suggested Fix Apply the same confinement loaddata already uses, before the os.path.isfile check: ``` if os.path.isabs(template_path): candidate = os.path.realpath(template_path) if base_dir and not candidate.startswith(os.path.realpath(str(base_dir))): return json.dumps({ "error": f"template path '{template_path}' resolves outside the project directory", "resolved": candidate, "project_dir": str(base_dir), }) if os.path.isfile(candidate): resolved_path = candidate ``` ### Limitations Not exercised against a live Django/djust project — this is a source-level trace of the exact executed branch (os.path.isabs + os.path.isfile + open()), not an end-to-end runtime capture. Did not verify whether any MCP-host-level confirmation/approval step (some MCP clients prompt the user before a tool call with a suspicious-looking path argument) would intercept this in practice - the vulnerability is in the server's own missing server-side guard regardless of client-side UX. ### Disclosure Timeline 2026-09-24: found during audit, confirmed via source trace to the open() sink; classified Medium due to the response-content limitation (matched substrings only, not raw file content). Please check the log file, and apply CVE once the fix is made. ``` log: 10 14:53:20.185935 openat(AT_FDCWD, "/case/project/templates/nonexistent_inside_project.html", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) 10 14:53:20.187205 write(1, "{\"error\": \"Could not resolve tem"..., 204) = 204 ... 10 14:53:20.191831 write(1, "=== ATTACK: absolute path OUTSID"..., 57) = 57 10 14:53:20.194864 write(1, "target path:", 12) = 12 10 14:53:20.198521 write(1, "/case/outside/evil_content.html", 31) = 31 10 14:53:20.202290 newfstatat(AT_FDCWD, "/case/outside/evil_content.html", {st_mode=S_IFREG|0777, st_size=185, ...}, 0) = 0 10 14:53:20.203762 write(1, "exists on disk (from the contain"..., 42) = 42 10 14:53:20.207224 write(1, "True", 4) = 4 10 14:53:20.210392 newfstatat(AT_FDCWD, "/case/outside/evil_content.html", {st_mode=S_IFREG|0777, st_size=185, ...}, 0) = 0 10 14:53:20.211721 newfstatat(AT_FDCWD, "/case/outside/evil_content.html", {st_mode=S_IFREG|0777, st_size=185, ...}, 0) = 0 10 14:53:20.213284 openat(AT_FDCWD, "/case/outside/evil_content.html", O_RDONLY|O_CLOEXEC) = 3 10 14:53:20.215202 newfstatat(3, "", {st_mode=S_IFREG|0777, st_size=185, ...}, AT_EMPTY_PATH) = 0 10 14:53:20.216994 ioctl(3, TCGETS, 0x7ffd68e26aa0) = -1 ENOTTY (Inappropriate ioctl for device) 10 14:53:20.221797 read(3, "<html><body>\n<!-- This file live"..., 186) = 185 10 14:53:20.223610 read(3, "", 1) = 0 10 14:53:20.224913 close(3) = 0 ``` ## Maintainer assessment The djust MCP server (`manage.py djust_mcp`, `python -m djust.mcp`) only serves stdio to the MCP client that launched it, as the same OS user, on the developer's machine; there is no network listener. The realistic caller is a local AI agent acting on untrusted input. The tool returns only `dj-*`-attribute-shaped substrings and an existence answer, not file contents. We therefore rate this Low (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N, 3.3). Affected: every release with the tool, 0.9.0 – 1.0.9, 1.1.0 – 1.1.6, 1.2.0 – 1.2.3, 1.3.0rc4 – 1.3.0rc7. ## Patches Fixed in 1.0.10, 1.1.7, 1.2.4 and 1.3.0rc8. `find_handlers_for_template` reads an absolute path only when it resolves (symlinks and `..` followed) inside a directory the template loaders serve or inside `BASE_DIR`, and answers any other absolute path like a missing template. The `seed_fixtures` guard compared paths with a string prefix (so `/app-data` passed for a `BASE_DIR` of `/app`) and skipped the check when `BASE_DIR` was unset; it now compares whole path components and refuses without `BASE_DIR`. Both use one containment helper. ## Workarounds Run the MCP server only in projects whose agent you trust with file access, or pass logical template names only.
Exploitation Scenario
A developer runs an AI coding assistant connected to the project's djust MCP server. The assistant reads a poisoned README, issue or web page containing an indirect prompt injection instructing it to call find_handlers_for_template with absolute paths such as /home/dev/.ssh/id_rsa, /etc/passwd or the project's .env. The tool returns whether each file exists, which lets the attacker map sensitive files, and returns any dj-* attribute-shaped substrings in files the attacker can influence. Combined with other agent tools, this reconnaissance could support further credential discovery or data exfiltration, though by itself it yields no general-purpose file read.
Weaknesses (CWE)
CWE-22Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')PrimaryCWE-22Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'): The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
- [Implementation] Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue." Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylis
- [Architecture and Design] For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:NReferences
- github.com/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
- 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
- github.com/djust-org/djust/security/advisories/GHSA-r57g-mc79-8hj2
- nvd.nist.gov/vuln/detail/CVE-2026-113731
Timeline
Related Vulnerabilities
CVE-2025-512010.0smolagents: sandbox escape enables unauthenticated RCE
Same attack type: Data LeakageCVE-2025-5376710.0Azure OpenAI: SSRF EoP, no auth required (CVSS 10)
Same attack type: Data ExtractionCVE-2023-376510.0MLflow: path traversal allows arbitrary file read
Same attack type: Data LeakageCVE-2025-282810.0LangChain RequestsToolkit: SSRF exposes cloud metadata
Same attack type: Data ExtractionCVE-2026-2185810.0n8n: Input Validation flaw enables exploitation
Same attack type: Data Extraction