Skip to content

chore(deps): update dependency mako to v1.4.2 [security] - #18631

Merged
chalmerlowe merged 1 commit into
googleapis:mainfrom
renovate-bot:renovate/pypi-mako-vulnerability
Oct 13, 2026
Merged

chalmerlowe merged 1 commit into
googleapis:mainfrom
renovate-bot:renovate/pypi-mako-vulnerability

Conversation

@renovate-bot

@renovate-bot renovate-bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
mako ==1.3.12 → ==1.4.2 age confidence

Mako: Path traversal via drive-letter URI on Windows in TemplateLookup

CVE-2026-102991 / GHSA-5639-2j2p-m4mx

More information

Details

Summary

On Windows, a TemplateLookup URI beginning with a drive designator (e.g. C:/../../secret.txt or C:\..\..\secret.txt) bypasses the directory traversal check in Template.__init__, allowing reads of files outside the configured template directory.

This is a third, independent instance of the root cause behind CVE-2026-41205 (the // prefix) and CVE-2026-44307 (the backslash form). Both of those fixes normalized a separator spelling. Neither addressed the other way posixpath and ntpath disagree: the drive designator. Both prior fixes remain effective on their own terms; this variant survives them for an independent reason.

Details

The root cause is the same posixpath / os.path mismatch: resolution is done with posixpath, confinement is checked with os.path, which is ntpath on Windows.


##### mako/lookup.py -- resolution
247:  srcfile = posixpath.normpath(posixpath.join(dir_, u))   # posixpath, always
248:  if os.path.isfile(srcfile):                             # ntpath on Windows

##### mako/template.py -- confinement
268:  u_norm = os.path.normpath(u_norm)                       # ntpath on Windows
269:  if u_norm.startswith(".."):                             # never true for a drive URI

posixpath has no concept of a drive, so it treats C: as an ordinary path component; the .. segments pop C: and then escape the template root. ntpath, by contrast, splits the drive off and treats the remainder as rooted, discarding the .. entirely -- so the guard on line 269 inspects a string from which the evidence has already been removed:

URI ntpath.normpath (what the guard sees) guard fires
//../secret.txt ..\secret.txt yes
\..\secret.txt ..\secret.txt yes
C:/../../secret.txt C:\secret.txt no

Because the guard cannot fire for any URI beginning with a single-letter drive designator, the traversal is unconstrained, and its depth does not have to be guessed. posixpath.normpath() saturates excess .. segments at the root rather than erroring, so a URI carrying more .. than the template root is deep reaches the volume root and descends from there. A single fixed payload therefore works regardless of where the template directory sits:

template root = <base>/srv/app/templates

'C:/../../../../boot.ini'          -> reads <base>/boot.ini
'C:/' + '../'*40 + 'etc/hostname'  -> reads /etc/hostname   (no depth guess)
'C:/../../../../../etc/hostname'   -> TopLevelLookupException (wrong depth)

On Windows the equivalent is C:/../../../../../../../../Windows/win.ini, which reads that file for any template root depth. Only a target addressed relative to the template root needs the .. count to be exact; anything reachable from the volume root does not.

Affected code
  • mako/template.py: Template.__init__() URI validation uses os.path.normpath(), which on Windows strips the drive and discards the .. segments before the startswith("..") guard is applied.
  • mako/lookup.py: TemplateLookup.get_template() resolves with posixpath.normpath/posixpath.join, where C: is an ordinary component, then performs the existence check with os.path.isfile().
Reproduction

Windows is emulated with the technique already used by the project's own test suite (test/test_template.py:1398-1401):

import os, ntpath
os.path = ntpath

from mako.lookup import TemplateLookup
lookup = TemplateLookup(directories=["root/tpl"])

for uri in ["ok.html",
            "//../../secret.txt",
            "\\..\\..\\secret.txt",
            "../../secret.txt",
            "C:/../../../secret.txt",
            "C:\\..\\..\\..\\secret.txt",
            "d:/../../../secret.txt"]:
    try:
        t = lookup.get_template(uri)
        print(repr(uri), "-> filename=", t.filename, repr(t.render()))
    except Exception as e:
        print(repr(uri), "-> REFUSED", type(e).__name__)

Against 1.4.1, with a sentinel file written outside the configured directory:

'ok.html'                    -> root/tpl/ok.html        'OK'         (positive control)
'//../../secret.txt'         -> REFUSED   (CVE-2026-41205 fix works)
'\..\..\secret.txt'          -> REFUSED   (CVE-2026-44307 fix works)
'../../secret.txt'           -> REFUSED
'C:/../../../secret.txt'     -> reads the sentinel
'C:\..\..\..\secret.txt'     -> reads the sentinel
'd:/../../../secret.txt'     -> reads the sentinel

On POSIX the same URIs are refused, because posixpath.normpath keeps C: as an ordinary component and the .. survive to be inspected. The issue is Windows-only.

Note that d:/../../../secret.txt resolves relative to the template root, not to drive D: -- this is not a cross-drive read. Reaching another drive would require a leading .., which ntpath.normpath renders as ..\..\D:\x and the existing guard catches.

Impact

If an application on Windows passes user-controlled template names or include paths to TemplateLookup.get_template(), an attacker may load and disclose any file readable by the application process on the same volume as the template directory, not merely files adjacent to it. The primary impact is local file disclosure. If the targeted file contains Mako/Python template syntax, it may also be parsed and executed as a template.

As with CVE-2026-41205, the URI is not generally reachable through a raw URL path, since a conforming URL normalizer collapses C:/../.. first. The relevant vectors are query strings, form and JSON bodies, route parameters, and <%include file="${...}"/>.

Remediation

Normalize both sides with the same module. The resolution side already commits to posixpath, so the confinement check should as well:

u_norm = posixpath.normpath(self.uri.replace("\\", "/").lstrip("/"))
if u_norm.startswith(".."):
    raise exceptions.TemplateLookupException(...)

This closes the drive case for the same reason it closes //: posixpath treats C: as an ordinary component, so the .. segments survive to be inspected. Verified to block all three drive forms with the full test suite passing.

Credit

Reported by Eurico Nicacio under coordinated disclosure.

Severity

  • CVSS Score: 6.5 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate-bot
renovate-bot requested a review from a team as a code owner October 12, 2026 09:16
@trusted-contributions-gcf trusted-contributions-gcf Bot added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Oct 12, 2026
@yoshi-kokoro yoshi-kokoro removed the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Oct 12, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the Mako dependency in packages/sqlalchemy-spanner/requirements.txt from version 1.3.12 to 1.4.2, along with its corresponding hashes. There are no review comments to evaluate, and I have no feedback to provide.

parthea pushed a commit that referenced this pull request Oct 13, 2026
…unique naming and teardown (#18623)

**Fix Schema Collision and DDL Concurrency in SQLAlchemy Spanner Literal
Tests**

## Problem
In the Cloud Spanner SQLAlchemy compliance tests, tests using the
`literal_round_trip_spanner` fixture were failing with `400 Duplicate
name in schema: t_literal_round_trip_spanner`.

This occurred because the fixture created a table with a static,
hardcoded name (`t_literal_round_trip_spanner`) without teardown between
tests. When subsequent test methods in the same class (such as
`StringTest`, `NumericTest`, `BooleanTest`, and `TextTest`) invoked the
fixture, Cloud Spanner rejected creating an already existing table.
Furthermore, executing DDL statements while read-write transactions from
queries remain active triggers `400 Schema change operation rejected
because a concurrent schema change operation or read-write transaction
is already in progress`.

## Solution
1. **Dynamic Unique Table Naming**: Generated an isolated table name per
test invocation using the column type and a random hexadecimal suffix
(for example, `t_lit_string_a3f2b1`).
2. **Cross-Version DBAPI Resolution**: Retrieved `dbapi_connection`
first with fallback to `connection` to support both modern SQLAlchemy
2.0 and legacy 1.4 without deprecation warnings.
3. **Transaction State Management**: Added explicit rollback of active
transactions before executing DDL drops, ensuring Cloud Spanner does not
reject table drops due to concurrent active transactions.
4. **Guaranteed Metadata Teardown**: Enclosed table creation in the
primary try block and table drop in a nested `try...finally` block to
ensure that table entries are always evicted from SQLAlchemy metadata
even if creation or drop operations fail.

## Related Pull Requests
This fix unblocks and resolves failing `Kokoro Sqlalchemy Compliance
Tests` across the following pull requests once rebased onto `main`:
* #18631 (`chore(deps): update dependency mako to v1.4.2 [security]`)
* #18565 (`chore(deps): update dependency urllib3 to v2.6.3`)
* #18618 (`chore(deps): update dependency packaging to v26.3`)
* #18621 (`chore(deps): update dependency pycparser to v3.11`)

## Notes to Reviewers
* This change is strictly localized to
`packages/sqlalchemy-spanner/tests/conftest.py`.
* It provides test isolation for literal rendering tests, eliminating
schema collisions and transaction concurrency failures without modifying
runtime dialect behavior.
@renovate-bot
renovate-bot force-pushed the renovate/pypi-mako-vulnerability branch from 1135139 to 94ff0b3 Compare October 13, 2026 15:58
@trusted-contributions-gcf trusted-contributions-gcf Bot added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Oct 13, 2026
@yoshi-kokoro yoshi-kokoro removed the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Oct 13, 2026

@chalmerlowe chalmerlowe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@chalmerlowe
chalmerlowe merged commit 0a13d88 into googleapis:main Oct 13, 2026
55 checks passed
@renovate-bot
renovate-bot deleted the renovate/pypi-mako-vulnerability branch October 13, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants