From 247de5e0c5062a792eb378e50e13e692885ee486 Mon Sep 17 00:00:00 2001
From: David Lord <davidism@gmail.com>
Date: Tue, 14 Jan 2025 13:43:41 -0800
Subject: [PATCH 1/4] use global contributing guide

Remove the per-project files so we don't have to keep them in sync.
GitHub's UI links to everything except the contributing guide, so add a
section about that to the readme.
---
 CONTRIBUTING.rst | 222 -----------------------------------------------
 README.md        |   8 ++
 2 files changed, 8 insertions(+), 222 deletions(-)
 delete mode 100644 CONTRIBUTING.rst

diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
deleted file mode 100644
index 5f835032f..000000000
--- a/CONTRIBUTING.rst
+++ /dev/null
@@ -1,222 +0,0 @@
-How to contribute to Jinja
-==========================
-
-Thank you for considering contributing to Jinja!
-
-
-Support questions
------------------
-
-Please don't use the issue tracker for this. The issue tracker is a
-tool to address bugs and feature requests in Jinja itself. Use one of
-the following resources for questions about using Jinja or issues with
-your own code:
-
--   The ``#get-help`` channel on our Discord chat:
-    https://discord.gg/pallets
--   The mailing list flask@python.org for long term discussion or larger
-    issues.
--   Ask on `Stack Overflow`_. Search with Google first using:
-    ``site:stackoverflow.com jinja {search term, exception message, etc.}``
-
-.. _Stack Overflow: https://stackoverflow.com/questions/tagged/jinja?tab=Frequent
-
-
-Reporting issues
-----------------
-
-Include the following information in your post:
-
--   Describe what you expected to happen.
--   If possible, include a `minimal reproducible example`_ to help us
-    identify the issue. This also helps check that the issue is not with
-    your own code.
--   Describe what actually happened. Include the full traceback if there
-    was an exception.
--   List your Python and Jinja versions. If possible, check if this
-    issue is already fixed in the latest releases or the latest code in
-    the repository.
-
-.. _minimal reproducible example: https://stackoverflow.com/help/minimal-reproducible-example
-
-
-Submitting patches
-------------------
-
-If there is not an open issue for what you want to submit, prefer
-opening one for discussion before working on a PR. You can work on any
-issue that doesn't have an open PR linked to it or a maintainer assigned
-to it. These show up in the sidebar. No need to ask if you can work on
-an issue that interests you.
-
-Include the following in your patch:
-
--   Use `Black`_ to format your code. This and other tools will run
-    automatically if you install `pre-commit`_ using the instructions
-    below.
--   Include tests if your patch adds or changes code. Make sure the test
-    fails without your patch.
--   Update any relevant docs pages and docstrings. Docs pages and
-    docstrings should be wrapped at 72 characters.
--   Add an entry in ``CHANGES.rst``. Use the same style as other
-    entries. Also include ``.. versionchanged::`` inline changelogs in
-    relevant docstrings.
-
-.. _Black: https://black.readthedocs.io
-.. _pre-commit: https://pre-commit.com
-
-
-First time setup
-~~~~~~~~~~~~~~~~
-
--   Download and install the `latest version of git`_.
--   Configure git with your `username`_ and `email`_.
-
-    .. code-block:: text
-
-        $ git config --global user.name 'your name'
-        $ git config --global user.email 'your email'
-
--   Make sure you have a `GitHub account`_.
--   Fork Jinja to your GitHub account by clicking the `Fork`_ button.
--   `Clone`_ the main repository locally.
-
-    .. code-block:: text
-
-        $ git clone https://github.com/pallets/jinja
-        $ cd jinja
-
--   Add your fork as a remote to push your work to. Replace
-    ``{username}`` with your username. This names the remote "fork", the
-    default Pallets remote is "origin".
-
-    .. code-block:: text
-
-        $ git remote add fork https://github.com/{username}/jinja
-
--   Create a virtualenv.
-
-    .. code-block:: text
-
-        $ python3 -m venv env
-        $ . env/bin/activate
-
-    On Windows, activating is different.
-
-    .. code-block:: text
-
-        > env\Scripts\activate
-
--   Upgrade pip and setuptools.
-
-    .. code-block:: text
-
-        $ python -m pip install --upgrade pip setuptools
-
--   Install the development dependencies, then install Jinja in editable
-    mode.
-
-    .. code-block:: text
-
-        $ pip install -r requirements/dev.txt && pip install -e .
-
--   Install the pre-commit hooks.
-
-    .. code-block:: text
-
-        $ pre-commit install
-
-.. _latest version of git: https://git-scm.com/downloads
-.. _username: https://docs.github.com/en/github/using-git/setting-your-username-in-git
-.. _email: https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address
-.. _GitHub account: https://github.com/join
-.. _Fork: https://github.com/pallets/jinja/fork
-.. _Clone: https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#step-2-create-a-local-clone-of-your-fork
-
-
-Start coding
-~~~~~~~~~~~~
-
--   Create a branch to identify the issue you would like to work on. If
-    you're submitting a bug or documentation fix, branch off of the
-    latest ".x" branch.
-
-    .. code-block:: text
-
-        $ git fetch origin
-        $ git checkout -b your-branch-name origin/3.0.x
-
-    If you're submitting a feature addition or change, branch off of the
-    "main" branch.
-
-    .. code-block:: text
-
-        $ git fetch origin
-        $ git checkout -b your-branch-name origin/main
-
--   Using your favorite editor, make your changes,
-    `committing as you go`_.
--   Include tests that cover any code changes you make. Make sure the
-    test fails without your patch. Run the tests as described below.
--   Push your commits to your fork on GitHub and
-    `create a pull request`_. Link to the issue being addressed with
-    ``fixes #123`` in the pull request.
-
-    .. code-block:: text
-
-        $ git push --set-upstream fork your-branch-name
-
-.. _committing as you go: https://dont-be-afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes
-.. _create a pull request: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request
-
-
-Running the tests
-~~~~~~~~~~~~~~~~~
-
-Run the basic test suite with pytest.
-
-.. code-block:: text
-
-    $ pytest
-
-This runs the tests for the current environment, which is usually
-sufficient. CI will run the full suite when you submit your pull
-request. You can run the full test suite with tox if you don't want to
-wait.
-
-.. code-block:: text
-
-    $ tox
-
-
-Running test coverage
-~~~~~~~~~~~~~~~~~~~~~
-
-Generating a report of lines that do not have test coverage can indicate
-where to start contributing. Run ``pytest`` using ``coverage`` and
-generate a report.
-
-.. code-block:: text
-
-    $ pip install coverage
-    $ coverage run -m pytest
-    $ coverage html
-
-Open ``htmlcov/index.html`` in your browser to explore the report.
-
-Read more about `coverage <https://coverage.readthedocs.io>`__.
-
-
-Building the docs
-~~~~~~~~~~~~~~~~~
-
-Build the docs in the ``docs`` directory using Sphinx.
-
-.. code-block:: text
-
-    $ cd docs
-    $ make html
-
-Open ``_build/html/index.html`` in your browser to view the docs.
-
-Read more about `Sphinx <https://www.sphinx-doc.org/en/stable/>`__.
diff --git a/README.md b/README.md
index f4aa7cbea..d1a6870d0 100644
--- a/README.md
+++ b/README.md
@@ -47,3 +47,11 @@ allow the maintainers to devote more time to the projects, [please
 donate today][].
 
 [please donate today]: https://palletsprojects.com/donate
+
+## Contributing
+
+See our [detailed contributing documentation][contrib] for many ways to
+contribute, including reporting issues, requesting features, asking or answering
+questions, and making PRs.
+
+[contrib]: https://palletsprojects.com/contributing/

From 033c20015c7ca899ab52eb921bb0f08e6d3dd145 Mon Sep 17 00:00:00 2001
From: David Lord <davidism@gmail.com>
Date: Wed, 5 Mar 2025 09:50:59 -0800
Subject: [PATCH 2/4] start version 3.1.6

---
 CHANGES.rst            | 5 +++++
 src/jinja2/__init__.py | 2 +-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index e1b339198..f1956ec12 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,5 +1,10 @@
 .. currentmodule:: jinja2
 
+Version 3.1.6
+-------------
+
+Unreleased
+
 Version 3.1.5
 -------------
 
diff --git a/src/jinja2/__init__.py b/src/jinja2/__init__.py
index d669f295b..e24d34fd2 100644
--- a/src/jinja2/__init__.py
+++ b/src/jinja2/__init__.py
@@ -35,4 +35,4 @@
 from .utils import pass_eval_context as pass_eval_context
 from .utils import select_autoescape as select_autoescape
 
-__version__ = "3.1.5"
+__version__ = "3.1.6.dev"

From 065334d1ee5b7210e1a0a93c37238c86858f2af7 Mon Sep 17 00:00:00 2001
From: David Lord <davidism@gmail.com>
Date: Wed, 5 Mar 2025 10:08:48 -0800
Subject: [PATCH 3/4] attr filter uses env.getattr

---
 CHANGES.rst            |  4 ++++
 src/jinja2/filters.py  | 37 ++++++++++++++++---------------------
 tests/test_security.py | 10 ++++++++++
 3 files changed, 30 insertions(+), 21 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index f1956ec12..605a04fd9 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,6 +5,10 @@ Version 3.1.6
 
 Unreleased
 
+-   The ``|attr`` filter does not bypass the environment's attribute lookup,
+    allowing the sandbox to apply its checks. :ghsa:`cpwx-vrp4-4pq7`
+
+
 Version 3.1.5
 -------------
 
diff --git a/src/jinja2/filters.py b/src/jinja2/filters.py
index e5b5a00c5..2bcba4fbd 100644
--- a/src/jinja2/filters.py
+++ b/src/jinja2/filters.py
@@ -6,6 +6,7 @@
 import typing
 import typing as t
 from collections import abc
+from inspect import getattr_static
 from itertools import chain
 from itertools import groupby
 
@@ -1411,31 +1412,25 @@ def do_reverse(value: t.Union[str, t.Iterable[V]]) -> t.Union[str, t.Iterable[V]
 def do_attr(
     environment: "Environment", obj: t.Any, name: str
 ) -> t.Union[Undefined, t.Any]:
-    """Get an attribute of an object.  ``foo|attr("bar")`` works like
-    ``foo.bar`` just that always an attribute is returned and items are not
-    looked up.
+    """Get an attribute of an object. ``foo|attr("bar")`` works like
+    ``foo.bar``, but returns undefined instead of falling back to ``foo["bar"]``
+    if the attribute doesn't exist.
 
     See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
     """
+    # Environment.getattr will fall back to obj[name] if obj.name doesn't exist.
+    # But we want to call env.getattr to get behavior such as sandboxing.
+    # Determine if the attr exists first, so we know the fallback won't trigger.
     try:
-        name = str(name)
-    except UnicodeError:
-        pass
-    else:
-        try:
-            value = getattr(obj, name)
-        except AttributeError:
-            pass
-        else:
-            if environment.sandboxed:
-                environment = t.cast("SandboxedEnvironment", environment)
-
-                if not environment.is_safe_attribute(obj, name, value):
-                    return environment.unsafe_undefined(obj, name)
-
-            return value
-
-    return environment.undefined(obj=obj, name=name)
+        # This avoids executing properties/descriptors, but misses __getattr__
+        # and __getattribute__ dynamic attrs.
+        getattr_static(obj, name)
+    except AttributeError:
+        # This finds dynamic attrs, and we know it's not a descriptor at this point.
+        if not hasattr(obj, name):
+            return environment.undefined(obj=obj, name=name)
+
+    return environment.getattr(obj, name)
 
 
 @typing.overload
diff --git a/tests/test_security.py b/tests/test_security.py
index 864d5f7f9..3a1378192 100644
--- a/tests/test_security.py
+++ b/tests/test_security.py
@@ -190,3 +190,13 @@ def run(value, arg):
 
         with pytest.raises(SecurityError):
             t.render()
+
+    def test_attr_filter(self) -> None:
+        env = SandboxedEnvironment()
+        t = env.from_string(
+            """{{ "{0.__call__.__builtins__[__import__]}"
+                  | attr("format")(not_here) }}"""
+        )
+
+        with pytest.raises(SecurityError):
+            t.render()

From 15206881c006c79667fe5154fe80c01c65410679 Mon Sep 17 00:00:00 2001
From: David Lord <davidism@gmail.com>
Date: Wed, 5 Mar 2025 11:51:17 -0800
Subject: [PATCH 4/4] release version 3.1.6

---
 CHANGES.rst            | 2 +-
 src/jinja2/__init__.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index 605a04fd9..2844c4c01 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -3,7 +3,7 @@
 Version 3.1.6
 -------------
 
-Unreleased
+Released 2025-03-05
 
 -   The ``|attr`` filter does not bypass the environment's attribute lookup,
     allowing the sandbox to apply its checks. :ghsa:`cpwx-vrp4-4pq7`
diff --git a/src/jinja2/__init__.py b/src/jinja2/__init__.py
index e24d34fd2..1a423a3ea 100644
--- a/src/jinja2/__init__.py
+++ b/src/jinja2/__init__.py
@@ -35,4 +35,4 @@
 from .utils import pass_eval_context as pass_eval_context
 from .utils import select_autoescape as select_autoescape
 
-__version__ = "3.1.6.dev"
+__version__ = "3.1.6"