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
- Products
- djust
- Affected versions
- 0.9.0, 0.9.1, 0.9.2, 0.9.4, 0.9.5, 0.9.6, 0.9.7, 1.0.0, 1.0.0rc11, 1.0.1, 1.0.2, 1.0.3, 1.0.4, 1.0.5, 1.0.6, 1.0.7, 1.0.8, 1.0.9, 1.1.0, 1.1.1, 1.1.2, 1.1.3, 1.1.4, 1.1.5, 1.1.6, 1.2.0, 1.2.1, 1.2.1rc1, 1.2.1rc2, 1.2.2, 1.2.3, 1.3.0rc4, 1.3.0rc5, 1.3.0rc6, 1.3.0rc7
- Fixed versions
- 1.0.10, 1.1.7, 1.2.4, 1.3.0rc8
- CVEs
- CVE-2026-113731
- Confirmed fixed CVEs
- Not specified
- Updated in this feed
- 31 Oct 2026, 15:36 UTC · Revision 1
Source evidence
djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool
core-osv · Original sourcePublished 21 Oct 2026, 17:21 UTC
### 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
osv:GHSA-r57g-mc79-8hj2
djust
Fixed versions: 1.0.10
djust
Fixed versions: 1.1.7
djust
Fixed versions: 1.2.4
djust
Fixed versions: 1.3.0rc8
Complete source record
{
"id": "373b0cb1a004a2ccb88760aa43c1970e60d5bd736a151a00f6e89b07f80191c1",
"url": "https://osv.dev/vulnerability/GHSA-r57g-mc79-8hj2",
"method": "structured",
"payload": {
"kind": "advisory",
"title": "djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool",
"summary": "### 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 ",
"evidence": "osv:GHSA-r57g-mc79-8hj2",
"products": [
{
"name": {
"value": "djust",
"evidence": "/affected/0/package/name"
},
"prerequisites": [],
"fixed_versions": [
{
"value": "1.0.10",
"evidence": "/affected/0/ranges"
}
],
"affected_versions": [
{
"value": "0.9.0",
"evidence": "/affected/0/versions"
},
{
"value": "0.9.1",
"evidence": "/affected/0/versions"
},
{
"value": "0.9.2",
"evidence": "/affected/0/versions"
},
{
"value": "0.9.4",
"evidence": "/affected/0/versions"
},
{
"value": "0.9.5",
"evidence": "/affected/0/versions"
},
{
"value": "0.9.6",
"evidence": "/affected/0/versions"
},
{
"value": "0.9.7",
"evidence": "/affected/0/versions"
},
{
"value": "1.0.0",
"evidence": "/affected/0/versions"
},
{
"value": "1.0.0rc11",
"evidence": "/affected/0/versions"
},
{
"value": "1.0.1",
"evidence": "/affected/0/versions"
},
{
"value": "1.0.2",
"evidence": "/affected/0/versions"
},
{
"value": "1.0.3",
"evidence": "/affected/0/versions"
},
{
"value": "1.0.4",
"evidence": "/affected/0/versions"
},
{
"value": "1.0.5",
"evidence": "/affected/0/versions"
},
{
"value": "1.0.6",
"evidence": "/affected/0/versions"
},
{
"value": "1.0.7",
"evidence": "/affected/0/versions"
},
{
"value": "1.0.8",
"evidence": "/affected/0/versions"
},
{
"value": "1.0.9",
"evidence": "/affected/0/versions"
}
]
},
{
"name": {
"value": "djust",
"evidence": "/affected/1/package/name"
},
"prerequisites": [],
"fixed_versions": [
{
"value": "1.1.7",
"evidence": "/affected/1/ranges"
}
],
"affected_versions": [
{
"value": "1.1.0",
"evidence": "/affected/1/versions"
},
{
"value": "1.1.1",
"evidence": "/affected/1/versions"
},
{
"value": "1.1.2",
"evidence": "/affected/1/versions"
},
{
"value": "1.1.3",
"evidence": "/affected/1/versions"
},
{
"value": "1.1.4",
"evidence": "/affected/1/versions"
},
{
"value": "1.1.5",
"evidence": "/affected/1/versions"
},
{
"value": "1.1.6",
"evidence": "/affected/1/versions"
}
]
},
{
"name": {
"value": "djust",
"evidence": "/affected/2/package/name"
},
"prerequisites": [],
"fixed_versions": [
{
"value": "1.2.4",
"evidence": "/affected/2/ranges"
}
],
"affected_versions": [
{
"value": "1.2.0",
"evidence": "/affected/2/versions"
},
{
"value": "1.2.1",
"evidence": "/affected/2/versions"
},
{
"value": "1.2.1rc1",
"evidence": "/affected/2/versions"
},
{
"value": "1.2.1rc2",
"evidence": "/affected/2/versions"
},
{
"value": "1.2.2",
"evidence": "/affected/2/versions"
},
{
"value": "1.2.3",
"evidence": "/affected/2/versions"
}
]
},
{
"name": {
"value": "djust",
"evidence": "/affected/3/package/name"
},
"prerequisites": [],
"fixed_versions": [
{
"value": "1.3.0rc8",
"evidence": "/affected/3/ranges"
}
],
"affected_versions": [
{
"value": "1.3.0rc4",
"evidence": "/affected/3/versions"
},
{
"value": "1.3.0rc5",
"evidence": "/affected/3/versions"
},
{
"value": "1.3.0rc6",
"evidence": "/affected/3/versions"
},
{
"value": "1.3.0rc7",
"evidence": "/affected/3/versions"
}
]
}
],
"severity": "low",
"structured": {
"id": "GHSA-r57g-mc79-8hj2",
"aliases": [
"CVE-2026-113731",
"GHSA-r57g-mc79-8hj2"
],
"metrics": [
{
"type": "CVSS_V3",
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N"
}
],
"affected": [
{
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{
"introduced": "0.9.0"
},
{
"fixed": "1.0.10"
}
]
}
],
"package": {
"name": "djust",
"purl": "pkg:pypi/djust",
"ecosystem": "PyPI"
},
"versions": [
"0.9.0",
"0.9.1",
"0.9.2",
"0.9.4",
"0.9.5",
"0.9.6",
"0.9.7",
"1.0.0",
"1.0.0rc11",
"1.0.1",
"1.0.2",
"1.0.3",
"1.0.4",
"1.0.5",
"1.0.6",
"1.0.7",
"1.0.8",
"1.0.9"
],
"database_specific": {
"source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-r57g-mc79-8hj2/GHSA-r57g-mc79-8hj2.json",
"last_known_affected_version_range": "<= 1.0.9"
}
},
{
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{
"introduced": "1.1.0"
},
{
"fixed": "1.1.7"
}
]
}
],
"package": {
"name": "djust",
"purl": "pkg:pypi/djust",
"ecosystem": "PyPI"
},
"versions": [
"1.1.0",
"1.1.1",
"1.1.2",
"1.1.3",
"1.1.4",
"1.1.5",
"1.1.6"
],
"database_specific": {
"source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-r57g-mc79-8hj2/GHSA-r57g-mc79-8hj2.json",
"last_known_affected_version_range": "<= 1.1.6"
}
},
{
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{
"introduced": "1.2.0.dev0"
},
{
"fixed": "1.2.4"
}
]
}
],
"package": {
"name": "djust",
"purl": "pkg:pypi/djust",
"ecosystem": "PyPI"
},
"versions": [
"1.2.0",
"1.2.1",
"1.2.1rc1",
"1.2.1rc2",
"1.2.2",
"1.2.3"
],
"database_specific": {
"source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-r57g-mc79-8hj2/GHSA-r57g-mc79-8hj2.json",
"last_known_affected_version_range": "<= 1.2.3"
}
},
{
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{
"introduced": "1.3.0.dev0"
},
{
"fixed": "1.3.0rc8"
}
]
}
],
"package": {
"name": "djust",
"purl": "pkg:pypi/djust",
"ecosystem": "PyPI"
},
"versions": [
"1.3.0rc4",
"1.3.0rc5",
"1.3.0rc6",
"1.3.0rc7"
],
"database_specific": {
"source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-r57g-mc79-8hj2/GHSA-r57g-mc79-8hj2.json",
"last_known_affected_version_range": "<= 1.3.0rc7"
}
}
],
"modified": "2026-10-21T17:35:19.934118290Z",
"provider": "osv",
"references": [
"https://github.com/djust-org/djust/security/advisories/GHSA-r57g-mc79-8hj2",
"https://nvd.nist.gov/vuln/detail/CVE-2026-113731",
"https://github.com/djust-org/djust/commit/45a5045793ebe7c9c2df86b0ad34275af6822619",
"https://github.com/djust-org/djust/commit/744bccf699c65c815d9379b36236a7c5592a339b",
"https://github.com/djust-org/djust/commit/85eed7caaf620bdb08986f2f3bb79432c0d3d715",
"https://github.com/djust-org/djust/commit/a62caf6b4ef540ad688d4dfdfa407ff914c78728",
"https://github.com/djust-org/djust",
"https://github.com/djust-org/djust/releases/tag/v1.0.10",
"https://github.com/djust-org/djust/releases/tag/v1.1.7",
"https://github.com/djust-org/djust/releases/tag/v1.2.4",
"https://github.com/djust-org/djust/releases/tag/v1.3.0rc8"
],
"provider_metadata": {
"cwe_ids": [
"CWE-22"
],
"severity": "LOW",
"github_reviewed": true,
"nvd_published_at": "2026-10-20T22:19:30Z",
"github_reviewed_at": "2026-10-21T17:21:15Z"
}
},
"related_cves": [
{
"value": "CVE-2026-113731",
"evidence": "/aliases"
}
],
"security_relevant": true,
"severity_evidence": "/database_specific/severity"
},
"source_id": "core-osv",
"document_id": 397806,
"published_at": "2026-10-21T17:21:15+00:00"
}