KEVWire

Security intelligence, with context.

Response snapshot Oct 27, 2026, 00:06 UTC

Back to the wire
Permalink
VulnerabilityCVE-2026-113731

djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool

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.

At a glance

Exploited
Not known
Severity
3.3 Low (CVSS 3.1)
Chance of exploitation
0.2% in the next 30 days
Sources
1 source

What to do

GitHub lists first patched versions: djust: 1.0.10; djust: 1.1.7; djust: 1.2.4; djust: 1.3.0rc8. Confirm the affected range and upgrade guidance in the exact advisory.

Read GitHub-reviewed advisory GHSA-r57g-mc79-8hj2 (opens a new tab)

What happened

### 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.

Continue reading

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, "/cas

Affected package ranges reported by GitHub: djust: >= 0.9.0, <= 1.0.9 djust: >= 1.1.0, <= 1.1.6 djust: >= 1.2.0.dev0, <= 1.2.3 djust: >= 1.3.0.dev0, <= 1.3.0rc7

Scores and how they were measured

CVSS base severity

3.3

Version 3.1As of

CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N

Measurement source (opens a new tab)

How bad it is if exploited. Not a prediction of exploitation or proof you are exposed.

EPSS probability

0.2%

Scored Percentile 8.2%

Model and measurement source (opens a new tab)

A model's estimate of exploitation in the next 30 days, not certainty or your own risk.

Related technology

Sources

  1. Primary source

    GitHub-reviewed advisory GHSA-r57g-mc79-8hj2 (opens a new tab)

    The public advisory supplies the description, affected package/version ranges, and any listed CVSS metric. GitHub review is not local review.

    Published Checked

  2. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-r57g-mc79-8hj2, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  3. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-r57g-mc79-8hj2, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  4. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-r57g-mc79-8hj2, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  5. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-r57g-mc79-8hj2, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  6. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-r57g-mc79-8hj2, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  7. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-r57g-mc79-8hj2, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  8. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-r57g-mc79-8hj2, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  9. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-r57g-mc79-8hj2, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  10. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-r57g-mc79-8hj2, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  11. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-r57g-mc79-8hj2, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  12. Primary source

    FIRST measured EPSS score (opens a new tab)

    Measured EPSS probability and percentile for CVE-2026-113731, scored 2026-10-26. This is a model estimate, not proof of exposure.

    Published Checked

Cite this record

For factual claims, cite the primary sources above; cite KEVWire for its compilation, dates, and classifications.

APA

KEVWire. (2026, October 21). djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool (CVE-2026-113731). https://kevwire.com/record/CVE-2026-113731

MLA

"djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool (CVE-2026-113731)." KEVWire, 21 Oct. 2026, kevwire.com/record/CVE-2026-113731.

Chicago

KEVWire. "djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool (CVE-2026-113731)." Last modified October 21, 2026. https://kevwire.com/record/CVE-2026-113731.

Evidence summary (plain text)

djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool (Oct 21, 2026).
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.

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.
CVSS 3.3 (version 3.1; evidence as of Oct 21, 2026; https://github.com/advisories/GHSA-r57g-mc79-8hj2).
EPSS 0.2% for the following 30 days, scored Oct 26, 2026 (https://api.first.org/data/v1/epss?cve=CVE-2026-113731&date=2026-10-26).
Sources:
GitHub-reviewed advisory GHSA-r57g-mc79-8hj2: https://github.com/advisories/GHSA-r57g-mc79-8hj2
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yMlQxNTo1Mjo1NlrOAAfxiA%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yMlQxNDowNjoxNFrOAAfxZw%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yMlQwODoyNzoyM1rOAAYp9A%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yMVQyMTozNTo1OFrOAAfwCQ%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yMVQyMTozMDoxM1rOAAfv2A%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yMVQyMToyODowOVrOAAfv1g%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yMVQyMToyNDoyMFrOAAfv1Q%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yMVQyMDo0NTowN1rOAAfvzQ%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yMVQyMDozMDoyNlrOAAfvxw%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yMVQyMDowMzo1N1rOAAfvxQ%3D%3D
FIRST measured EPSS score: https://api.first.org/data/v1/epss?cve=CVE-2026-113731&date=2026-10-26
Record ID: CVE-2026-113731.
Record origin: Ingested record.
AI evidence: Not assessed.
Known exploitation: Not assessed.
Zero-day status: Not assessed. KEV membership alone does not establish zero-day status.
About this data

This record was ingested from a data source. Retrieved: Oct 26, 2026, 19:40 UTC. Ingestion is not an independent editorial investigation.

A CISA KEV listing documents known exploitation, not necessarily a zero-day; absence from the catalog does not prove a vulnerability has never been exploited.

Technology tags indicate possible relevance, not verified exposure. Confirm the affected product, version, and configuration against your own inventory; KEVWire does not scan your systems.

A personal reading checklist on this device, not verified remediation.

Record JSON

Your account

Email alerts

Email alerts are not available yet. To follow your stack today, use its RSS feed, which honors your filters, or connect an AI agent through the API.

Suggest an improvement

Ideas, missing sources, corrections, and bugs are all welcome. No account needed. Suggestions are reviewed by a person before anything changes.

Record details

Record detail