Skip to content

prevent builtin shadowing in DTML#83

Merged
dataflake merged 4 commits intomasterfrom
protect_builtins
Feb 23, 2026
Merged

prevent builtin shadowing in DTML#83
dataflake merged 4 commits intomasterfrom
protect_builtins

Conversation

@mamico
Copy link
Member

@mamico mamico commented Feb 22, 2026

  • I signed and returned the Zope Contributor Agreement, and received and accepted an invitation to join a team in the zopefoundation GitHub organization.
  • I verified there aren't any other open pull requests for the same change.
  • I followed the guidelines in Developer guidelines.
  • I successfully ran code quality checks on my changes locally.
  • I successfully ran tests on my changes locally.
  • If needed, I added new tests for my changes.
  • If needed, I added documentation for my changes.
  • I included a change log entry in my commits.

This PR prevents expression evaluation in DocumentTemplate from overriding or shadowing builtins (eg. str, len, ...).

@mamico mamico requested a review from dataflake February 22, 2026 23:26
@dataflake dataflake enabled auto-merge February 23, 2026 10:05
Co-authored-by: Jens Vagelpohl <jens@plyp.com>
@dataflake dataflake merged commit edb3e68 into master Feb 23, 2026
12 checks passed
@dataflake dataflake deleted the protect_builtins branch February 23, 2026 10:53
@dataflake
Copy link
Member

Released as DocumentTemplate 5.2 and pinned in Zope master

@dataflake
Copy link
Member

@mamico It looks like the solution breaks other code, see zopefoundation/Zope#1287. I will revert this fix and cut a new release tomorrow, that way there is time to develop a better solution.

From my reading of zopefoundation/Zope#1285 I believe a "safer" fix is to look through the ZMI DMTL templates and change the way str is accessed so it is explicitly pulled from the namespace (_) instead of just an unqualified access.

@dataflake
Copy link
Member

I have now reverted this PR and released DocumentTemplate 5.3. I'll look at Zope ZMI DTML fixes instead when I have a moment.

@dataflake
Copy link
Member

Just for reference, this is a complete traceback that you get when clicking on a folder's Properties tab, the one in zopefoundation/Zope#1287 was truncated:

2026-02-25 09:31:41,124 ERROR   [waitress:437][waitress-0] Exception while serving /fldr/manage_propertiesForm
Traceback (most recent call last):
  File "/Users/jens/src/.eggs/v5/waitress-3.0.2-py3.13-macosx-26.1-arm64.egg/waitress/channel.py", line 430, in service
    task.service()
    ~~~~~~~~~~~~^^
  File "/Users/jens/src/.eggs/v5/waitress-3.0.2-py3.13-macosx-26.1-arm64.egg/waitress/task.py", line 167, in service
    self.execute()
    ~~~~~~~~~~~~^^
  File "/Users/jens/src/.eggs/v5/waitress-3.0.2-py3.13-macosx-26.1-arm64.egg/waitress/task.py", line 435, in execute
    app_iter = self.channel.server.application(environ, start_response)
  File "/Users/jens/src/.eggs/v5/Paste-3.10.1-py3.13-macosx-26.1-arm64.egg/paste/translogger.py", line 76, in __call__
    return self.application(environ, replacement_start_response)
           ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jens/src/zope/Zope/src/ZPublisher/httpexceptions.py", line 30, in __call__
    return self.application(environ, start_response)
           ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jens/src/zope/Zope/src/ZPublisher/WSGIPublisher.py", line 388, in publish_module
    response = _publish(request, new_mod_info)
  File "/Users/jens/src/zope/Zope/src/ZPublisher/WSGIPublisher.py", line 282, in publish
    result = mapply(obj,
                    request.args,
    ...<5 lines>...
                    request,
                    bind=1)
  File "/Users/jens/src/zope/Zope/src/ZPublisher/mapply.py", line 98, in mapply
    return debug(object, args, context)
  File "/Users/jens/src/zope/Zope/src/ZPublisher/WSGIPublisher.py", line 68, in call_object
    return obj(*args)
  File "/Users/jens/src/zope/Zope/src/Shared/DC/Scripts/Bindings.py", line 335, in __call__
    return self._bindAndExec(args, kw, None)
           ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "/Users/jens/src/zope/Zope/src/Shared/DC/Scripts/Bindings.py", line 372, in _bindAndExec
    return self._exec(bound_data, args, kw)
           ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jens/src/zope/Zope/src/App/special_dtml.py", line 225, in _exec
    result = render_blocks(self._v_blocks, ns,
                           encoding=encoding)
  File "/Users/jens/src/.eggs/v5/DocumentTemplate-5.2-py3.13-macosx-26.3-arm64.egg/DocumentTemplate/_DocumentTemplate.py", line 144, in render_blocks
    render_blocks_(blocks, rendered, md, encoding)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jens/src/.eggs/v5/DocumentTemplate-5.2-py3.13-macosx-26.3-arm64.egg/DocumentTemplate/_DocumentTemplate.py", line 227, in render_blocks_
    render_blocks_(block, rendered, md, encoding)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jens/src/.eggs/v5/DocumentTemplate-5.2-py3.13-macosx-26.3-arm64.egg/DocumentTemplate/_DocumentTemplate.py", line 245, in render_blocks_
    block = block(md)
  File "/Users/jens/src/.eggs/v5/DocumentTemplate-5.2-py3.13-macosx-26.3-arm64.egg/DocumentTemplate/DT_In.py", line 763, in renderwob
    append(render(section, md, encoding=self.encoding))
           ~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jens/src/.eggs/v5/DocumentTemplate-5.2-py3.13-macosx-26.3-arm64.egg/DocumentTemplate/_DocumentTemplate.py", line 144, in render_blocks
    render_blocks_(blocks, rendered, md, encoding)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jens/src/.eggs/v5/DocumentTemplate-5.2-py3.13-macosx-26.3-arm64.egg/DocumentTemplate/_DocumentTemplate.py", line 245, in render_blocks_
    block = block(md)
  File "/Users/jens/src/.eggs/v5/DocumentTemplate-5.2-py3.13-macosx-26.3-arm64.egg/DocumentTemplate/DT_Let.py", line 85, in render
    return render_blocks(self.section, md, encoding=self.encoding)
  File "/Users/jens/src/.eggs/v5/DocumentTemplate-5.2-py3.13-macosx-26.3-arm64.egg/DocumentTemplate/_DocumentTemplate.py", line 144, in render_blocks
    render_blocks_(blocks, rendered, md, encoding)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jens/src/.eggs/v5/DocumentTemplate-5.2-py3.13-macosx-26.3-arm64.egg/DocumentTemplate/_DocumentTemplate.py", line 166, in render_blocks_
    t = md[t]
        ~~^^^
  File "/Users/jens/src/.eggs/v5/DocumentTemplate-5.2-py3.13-macosx-26.3-arm64.egg/DocumentTemplate/_DocumentTemplate.py", line 374, in __getitem__
    return self.getitem(name, call=1)
           ~~~~~~~~~~~~^^^^^^^^^^^^^^
  File "/Users/jens/src/.eggs/v5/DocumentTemplate-5.2-py3.13-macosx-26.3-arm64.egg/DocumentTemplate/_DocumentTemplate.py", line 398, in getitem
    return e()
TypeError: id() takes exactly one argument (0 given)

@dataflake
Copy link
Member

As I suspected, the problem was unqualified access of str within ZMI DTML templates. In DTML both of these (seem) to do the same thing:

  • <dtml-var "str(XXX)">
  • <dtml-var "_.str(XXX)">

But as soon as there is a ZODB object named str in the ZODB hierarchy "above" this template it will be acquired and used in the first invocation. The second invocation does the right thing, it accesses str through the namespace object _, which does not trigger acquisition.

This issue must have existed for about 7 years and the culprit is - me: zopefoundation/Zope@1f706e2 🤦🙈

Fix has been applied to Zope master in zopefoundation/Zope@cc404ab, it was simple enough to forego a PR. It will be released with Zope 6.

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.

2 participants