AccessControl 7.4__tar.gz → 7.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. {accesscontrol-7.4 → accesscontrol-7.6}/.manylinux-install.sh +3 -10
  2. {accesscontrol-7.4 → accesscontrol-7.6}/.pre-commit-config.yaml +14 -1
  3. {accesscontrol-7.4 → accesscontrol-7.6}/CHANGES.rst +44 -0
  4. accesscontrol-7.6/COPYRIGHT.txt +1 -0
  5. {accesscontrol-7.4 → accesscontrol-7.6}/PKG-INFO +47 -4
  6. {accesscontrol-7.4 → accesscontrol-7.6}/docs/AccessControl.rst +14 -14
  7. {accesscontrol-7.4 → accesscontrol-7.6}/pyproject.toml +16 -16
  8. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/SecurityInfo.py +114 -2
  9. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/SimpleObjectPolicies.py +8 -2
  10. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/__init__.py +2 -1
  11. accesscontrol-7.6/src/AccessControl/frozen.py +57 -0
  12. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/meta.zcml +1 -1
  13. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/actual_python.py +2 -2
  14. accesscontrol-7.6/src/AccessControl/tests/public_module/__init__.py +11 -0
  15. accesscontrol-7.6/src/AccessControl/tests/testModuleSecurity.py +335 -0
  16. accesscontrol-7.6/src/AccessControl/tests/test_frozen.py +124 -0
  17. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl.egg-info/PKG-INFO +47 -4
  18. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl.egg-info/SOURCES.txt +2 -0
  19. {accesscontrol-7.4 → accesscontrol-7.6}/tox.ini +0 -2
  20. accesscontrol-7.4/COPYRIGHT.txt +0 -1
  21. accesscontrol-7.4/src/AccessControl/tests/public_module/__init__.py +0 -7
  22. accesscontrol-7.4/src/AccessControl/tests/testModuleSecurity.py +0 -113
  23. {accesscontrol-7.4 → accesscontrol-7.6}/.manylinux.sh +0 -0
  24. {accesscontrol-7.4 → accesscontrol-7.6}/CONTRIBUTING.md +0 -0
  25. {accesscontrol-7.4 → accesscontrol-7.6}/HISTORY.rst +0 -0
  26. {accesscontrol-7.4 → accesscontrol-7.6}/LICENSE.txt +0 -0
  27. {accesscontrol-7.4 → accesscontrol-7.6}/MANIFEST.in +0 -0
  28. {accesscontrol-7.4 → accesscontrol-7.6}/README.rst +0 -0
  29. {accesscontrol-7.4 → accesscontrol-7.6}/buildout.cfg +0 -0
  30. {accesscontrol-7.4 → accesscontrol-7.6}/docs/requestmethod.rst +0 -0
  31. {accesscontrol-7.4 → accesscontrol-7.6}/include/Acquisition/Acquisition.h +0 -0
  32. {accesscontrol-7.4 → accesscontrol-7.6}/include/ExtensionClass/ExtensionClass.h +0 -0
  33. {accesscontrol-7.4 → accesscontrol-7.6}/include/ExtensionClass/_compat.h +0 -0
  34. {accesscontrol-7.4 → accesscontrol-7.6}/setup.cfg +0 -0
  35. {accesscontrol-7.4 → accesscontrol-7.6}/setup.py +0 -0
  36. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/AuthEncoding.py +0 -0
  37. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/ImplC.py +0 -0
  38. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/ImplPython.py +0 -0
  39. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/Implementation.py +0 -0
  40. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/Owned.py +0 -0
  41. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/Permission.py +0 -0
  42. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/PermissionMapping.py +0 -0
  43. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/PermissionRole.py +0 -0
  44. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/Permissions.py +0 -0
  45. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/Role.py +0 -0
  46. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/SecurityManagement.py +0 -0
  47. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/SecurityManager.py +0 -0
  48. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/SpecialUsers.py +0 -0
  49. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/User.py +0 -0
  50. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/ZopeGuards.py +0 -0
  51. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/ZopeSecurityPolicy.py +0 -0
  52. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/_compat.h +0 -0
  53. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/cAccessControl.c +0 -0
  54. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/class_init.py +0 -0
  55. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/configure.zcml +0 -0
  56. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/interfaces.py +0 -0
  57. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/logger_wrapper.py +0 -0
  58. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/metaconfigure.py +0 -0
  59. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/owner.py +0 -0
  60. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/permissions.zcml +0 -0
  61. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/requestmethod.py +0 -0
  62. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/rolemanager.py +0 -0
  63. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/safe_formatter.py +0 -0
  64. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/security.py +0 -0
  65. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tainted.py +0 -0
  66. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/__init__.py +0 -0
  67. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/mixed_module/__init__.py +0 -0
  68. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/mixed_module/submodule/__init__.py +0 -0
  69. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/private_module/__init__.py +0 -0
  70. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/private_module/submodule/__init__.py +0 -0
  71. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/public_module/submodule/__init__.py +0 -0
  72. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/testClassSecurityInfo.py +0 -0
  73. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/testImplementation.py +0 -0
  74. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/testPermissionMapping.py +0 -0
  75. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/testPermissionRole.py +0 -0
  76. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/testSecurityManager.py +0 -0
  77. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/testZCML.py +0 -0
  78. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/testZopeGuards.py +0 -0
  79. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/testZopeSecurityPolicy.py +0 -0
  80. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/test_owner.py +0 -0
  81. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/test_requestmethod.py +0 -0
  82. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/test_rolemanager.py +0 -0
  83. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/test_safe_formatter.py +0 -0
  84. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/test_safeiter.py +0 -0
  85. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/test_tainted.py +0 -0
  86. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/test_userfolder.py +0 -0
  87. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/tests/test_users.py +0 -0
  88. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/unauthorized.py +0 -0
  89. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/userfolder.py +0 -0
  90. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl/users.py +0 -0
  91. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl.egg-info/dependency_links.txt +0 -0
  92. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl.egg-info/entry_points.txt +0 -0
  93. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl.egg-info/requires.txt +0 -0
  94. {accesscontrol-7.4 → accesscontrol-7.6}/src/AccessControl.egg-info/top_level.txt +0 -0
@@ -28,7 +28,6 @@ yum -y install libffi-devel
28
28
 
29
29
  tox_env_map() {
30
30
  case $1 in
31
- *"cp310"*) echo 'py310';;
32
31
  *"cp311"*) echo 'py311';;
33
32
  *"cp312"*) echo 'py312';;
34
33
  *"cp313"*) echo 'py313';;
@@ -41,19 +40,13 @@ tox_env_map() {
41
40
  # Compile wheels
42
41
  for PYBIN in /opt/python/*/bin; do
43
42
  if \
44
- [[ "${PYBIN}" == *"cp310/"* ]] || \
45
43
  [[ "${PYBIN}" == *"cp311/"* ]] || \
46
44
  [[ "${PYBIN}" == *"cp312/"* ]] || \
47
45
  [[ "${PYBIN}" == *"cp313/"* ]] || \
48
46
  [[ "${PYBIN}" == *"cp314/"* ]] || \
49
- [[ "${PYBIN}" == *"cp315/"* ]] ; then
50
- if [[ "${PYBIN}" == *"cp315/"* ]] ; then
51
- "${PYBIN}/pip" install --pre -e /io/
52
- "${PYBIN}/pip" wheel /io/ --pre -w wheelhouse/
53
- else
54
- "${PYBIN}/pip" install -e /io/
55
- "${PYBIN}/pip" wheel /io/ -w wheelhouse/
56
- fi
47
+ [[ "${PYBIN}" == *"cp315/"* ]] ; then
48
+ "${PYBIN}/pip" install -e /io/
49
+ "${PYBIN}/pip" wheel /io/ -w wheelhouse/
57
50
  if [ `uname -m` == 'aarch64' ]; then
58
51
  cd /io/
59
52
  ${PYBIN}/pip install tox
@@ -2,6 +2,15 @@
2
2
  # https://github.com/zopefoundation/meta/tree/master/src/zope/meta/c-code
3
3
  minimum_pre_commit_version: '3.6'
4
4
  repos:
5
+ - repo: https://github.com/pre-commit/pre-commit-hooks
6
+ rev: v6.0.0
7
+ hooks:
8
+ - id: check-case-conflict
9
+ - id: check-merge-conflict
10
+ - id: check-toml
11
+ - id: check-yaml
12
+ - id: end-of-file-fixer
13
+ - id: trailing-whitespace
5
14
  - repo: https://github.com/pycqa/isort
6
15
  rev: "8.0.1"
7
16
  hooks:
@@ -15,7 +24,7 @@ repos:
15
24
  rev: v3.21.2
16
25
  hooks:
17
26
  - id: pyupgrade
18
- args: [--py310-plus]
27
+ args: [--py311-plus]
19
28
  - repo: https://github.com/isidentical/teyit
20
29
  rev: 0.4.3
21
30
  hooks:
@@ -27,3 +36,7 @@ repos:
27
36
  - id: flake8
28
37
  additional_dependencies:
29
38
  - flake8-debugger == 4.1.2
39
+ - repo: https://github.com/sphinx-contrib/sphinx-lint
40
+ rev: v1.0.2
41
+ hooks:
42
+ - id: sphinx-lint
@@ -4,9 +4,53 @@ Change log
4
4
  For changes before version 3.0, see ``HISTORY.rst``.
5
5
 
6
6
 
7
+ 7.6 (2026-10-30)
8
+ ----------------
9
+
10
+ - Add ``freeze_security_declarations``. Call it once all code has been
11
+ loaded, for example when Zope has started. Afterwards, any change to
12
+ module security declarations, for example with ``allow_module`` or
13
+ ``ModuleSecurityInfo``, raises ``Forbidden``. The same is true for
14
+ ``allow_class``, ``allow_type`` and other changes to
15
+ ``ContainerAssertions``. Dangerous modules like ``os``, ``subprocess``,
16
+ ``AccessControl`` itself, and third-party network libraries like
17
+ ``requests`` and ``httpx``, that are
18
+ fully accessible from restricted code, for example with ``allow_module``,
19
+ are logged as a warning. Pass ``dangerous_modules`` to change which
20
+ modules are dangerous. Calling it a second time does nothing. Call
21
+ ``check_dangerous_modules`` to check other modules, also after the freeze.
22
+
23
+ - Remove module security declaration for ``sets``.
24
+ This module only existed in Python 2.
25
+
26
+ - Add additional safeguards to ``SecurityInfo.allow_class`` / ``allow_module``
27
+ and ``SimpleObjectPolicies.allow_type`` to prevent use from untrusted code.
28
+
29
+ - Add support for Python 3.15.
30
+
31
+ - Drop support for Python 3.10.
32
+
33
+
34
+ 7.5 (2026-08-20)
35
+ ----------------
36
+
37
+ - Add final support for Python 3.15.
38
+
39
+ - Switch to PyPI Trusted Publishing.
40
+
41
+
7
42
  7.4 (2026-07-13)
8
43
  ----------------
9
44
 
45
+ - Prevent information disclosure through the ``format`` and ``format_map``
46
+ methods of ``str`` subclasses: the guards which route these methods to the
47
+ safe formatter only covered ``str`` itself, so a subclass instance could be
48
+ used to read otherwise inaccessible objects via attribute access and
49
+ subscription in a format string
50
+ (fixes `GHSA-pq59-9fq7-m886
51
+ <https://github.com/zopefoundation/AccessControl/security/advisories/GHSA-pq59-9fq7-m886>`_,
52
+ CVE-2026-77401).
53
+
10
54
  - Fix a parity defect between the Python and C implementations of
11
55
  ``guarded_getattr``: the pure-Python implementation keyed the container
12
56
  assertion on ``type(value.__self__)`` instead of ``type(inst)``, so under
@@ -0,0 +1 @@
1
+ Zope Foundation and Contributors
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AccessControl
3
- Version: 7.4
3
+ Version: 7.6
4
4
  Summary: Security framework for Zope
5
5
  Author-email: Zope Foundation and contributors <zope-dev@zope.dev>
6
6
  Maintainer-email: Plone Foundation and contributors <zope-dev@zope.dev>
@@ -16,14 +16,13 @@ Classifier: Framework :: Zope :: 5
16
16
  Classifier: Operating System :: OS Independent
17
17
  Classifier: Programming Language :: Python
18
18
  Classifier: Programming Language :: Python :: 3
19
- Classifier: Programming Language :: Python :: 3 :: Only
20
- Classifier: Programming Language :: Python :: 3.10
21
19
  Classifier: Programming Language :: Python :: 3.11
22
20
  Classifier: Programming Language :: Python :: 3.12
23
21
  Classifier: Programming Language :: Python :: 3.13
24
22
  Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3.15
25
24
  Classifier: Programming Language :: Python :: Implementation :: CPython
26
- Requires-Python: >=3.10
25
+ Requires-Python: >=3.11
27
26
  Description-Content-Type: text/x-rst
28
27
  License-File: LICENSE.txt
29
28
  Requires-Dist: Acquisition
@@ -73,9 +72,53 @@ Change log
73
72
  For changes before version 3.0, see ``HISTORY.rst``.
74
73
 
75
74
 
75
+ 7.6 (2026-10-30)
76
+ ----------------
77
+
78
+ - Add ``freeze_security_declarations``. Call it once all code has been
79
+ loaded, for example when Zope has started. Afterwards, any change to
80
+ module security declarations, for example with ``allow_module`` or
81
+ ``ModuleSecurityInfo``, raises ``Forbidden``. The same is true for
82
+ ``allow_class``, ``allow_type`` and other changes to
83
+ ``ContainerAssertions``. Dangerous modules like ``os``, ``subprocess``,
84
+ ``AccessControl`` itself, and third-party network libraries like
85
+ ``requests`` and ``httpx``, that are
86
+ fully accessible from restricted code, for example with ``allow_module``,
87
+ are logged as a warning. Pass ``dangerous_modules`` to change which
88
+ modules are dangerous. Calling it a second time does nothing. Call
89
+ ``check_dangerous_modules`` to check other modules, also after the freeze.
90
+
91
+ - Remove module security declaration for ``sets``.
92
+ This module only existed in Python 2.
93
+
94
+ - Add additional safeguards to ``SecurityInfo.allow_class`` / ``allow_module``
95
+ and ``SimpleObjectPolicies.allow_type`` to prevent use from untrusted code.
96
+
97
+ - Add support for Python 3.15.
98
+
99
+ - Drop support for Python 3.10.
100
+
101
+
102
+ 7.5 (2026-08-20)
103
+ ----------------
104
+
105
+ - Add final support for Python 3.15.
106
+
107
+ - Switch to PyPI Trusted Publishing.
108
+
109
+
76
110
  7.4 (2026-07-13)
77
111
  ----------------
78
112
 
113
+ - Prevent information disclosure through the ``format`` and ``format_map``
114
+ methods of ``str`` subclasses: the guards which route these methods to the
115
+ safe formatter only covered ``str`` itself, so a subclass instance could be
116
+ used to read otherwise inaccessible objects via attribute access and
117
+ subscription in a format string
118
+ (fixes `GHSA-pq59-9fq7-m886
119
+ <https://github.com/zopefoundation/AccessControl/security/advisories/GHSA-pq59-9fq7-m886>`_,
120
+ CVE-2026-77401).
121
+
79
122
  - Fix a parity defect between the Python and C implementations of
80
123
  ``guarded_getattr``: the pure-Python implementation keyed the container
81
124
  assertion on ``type(value.__self__)`` instead of ``type(inst)``, so under
@@ -30,15 +30,15 @@ Types of access
30
30
 
31
31
  A "type of access" is a named grouping of 0 or more of the
32
32
  permissions defined by an object. All objects have one predefined
33
- type of access called Full Access (all permissions defined by that
34
- object). A user who has the special role "Manager" always has Full
35
- Access to all objects at or below the level in the object hierarchy
33
+ type of access called Full Access (all permissions defined by that
34
+ object). A user who has the special role "Manager" always has Full
35
+ Access to all objects at or below the level in the object hierarchy
36
36
  at which the user is defined.
37
37
 
38
38
  New types of access may be defined as combinations of the
39
39
  various permissions defined by a given object. These new
40
40
  types of access may be defined by the programmer, or by
41
- users at runtime.
41
+ users at runtime.
42
42
 
43
43
 
44
44
 
@@ -50,13 +50,13 @@ A role is a name that ties users (authentication of identity)
50
50
  to permissions (authorization for that identity) in the system.
51
51
  Roles may be defined in any Folder (or Folderish) object in the
52
52
  system. Sub folders can make use of roles defined higher in the
53
- hierarchy. These roles can be assigned to users. All users,
54
- including non-authenticated users have the built-in role of
55
- "Anonymous".
53
+ hierarchy. These roles can be assigned to users. All users,
54
+ including non-authenticated users have the built-in role of
55
+ "Anonymous".
56
56
 
57
- Principia objects allow the association of defined roles
58
- with a single "type of access" each, in the context of that
59
- object. A single role is associated with one and only one
57
+ Principia objects allow the association of defined roles
58
+ with a single "type of access" each, in the context of that
59
+ object. A single role is associated with one and only one
60
60
  type of access in the context of a given object.
61
61
 
62
62
 
@@ -191,14 +191,14 @@ Example:
191
191
  )
192
192
 
193
193
  The developer may also predefine useful types of access, by
194
- specifying an __ac_types__ attribute. This should be a tuple of
195
- tuples, where each tuple represents a type of access and contains
196
- a string name as its first element and a list of permission names
194
+ specifying an __ac_types__ attribute. This should be a tuple of
195
+ tuples, where each tuple represents a type of access and contains
196
+ a string name as its first element and a list of permission names
197
197
  as its second element.
198
198
 
199
199
  By default, only "Full Access" is defined (by the RoleManager mixin).
200
200
  If you wish to override __ac_types__ to provide convenient types of
201
- access, you must always be sure to define "Full Access" as containing
201
+ access, you must always be sure to define "Full Access" as containing
202
202
  the names of all possible permissions for your object.
203
203
 
204
204
  Example:
@@ -9,11 +9,11 @@ build-backend = "setuptools.build_meta"
9
9
 
10
10
  [project]
11
11
  name = "AccessControl"
12
- version = "7.4"
12
+ version = "7.6"
13
13
  description = "Security framework for Zope"
14
14
  keywords = ["security", "access", "authorization"]
15
15
  dynamic = ["readme"]
16
- requires-python = ">=3.10"
16
+ requires-python = ">=3.11"
17
17
  license = "ZPL-2.1"
18
18
  authors = [
19
19
  { name = "Zope Foundation and contributors", email = "zope-dev@zope.dev" },
@@ -22,20 +22,19 @@ maintainers = [
22
22
  { name = "Plone Foundation and contributors", email = "zope-dev@zope.dev" },
23
23
  ]
24
24
  classifiers = [
25
- "Development Status :: 6 - Mature",
26
- "Environment :: Web Environment",
27
- "Framework :: Zope",
28
- "Framework :: Zope :: 5",
29
- "Operating System :: OS Independent",
30
- "Programming Language :: Python",
31
- "Programming Language :: Python :: 3",
32
- "Programming Language :: Python :: 3 :: Only",
33
- "Programming Language :: Python :: 3.10",
34
- "Programming Language :: Python :: 3.11",
35
- "Programming Language :: Python :: 3.12",
36
- "Programming Language :: Python :: 3.13",
37
- "Programming Language :: Python :: 3.14",
38
- "Programming Language :: Python :: Implementation :: CPython",
25
+ "Development Status :: 6 - Mature",
26
+ "Environment :: Web Environment",
27
+ "Framework :: Zope",
28
+ "Framework :: Zope :: 5",
29
+ "Operating System :: OS Independent",
30
+ "Programming Language :: Python",
31
+ "Programming Language :: Python :: 3",
32
+ "Programming Language :: Python :: 3.11",
33
+ "Programming Language :: Python :: 3.12",
34
+ "Programming Language :: Python :: 3.13",
35
+ "Programming Language :: Python :: 3.14",
36
+ "Programming Language :: Python :: 3.15",
37
+ "Programming Language :: Python :: Implementation :: CPython",
39
38
  ]
40
39
  dependencies = [
41
40
  "Acquisition",
@@ -108,4 +107,5 @@ readme = {file = ["README.rst", "CHANGES.rst"]}
108
107
 
109
108
  [tool.zest-releaser]
110
109
  create-wheel = false
110
+ extra-message = ""
111
111
  upload-pypi = false
@@ -44,6 +44,7 @@ from logging import getLogger
44
44
  from Acquisition import Implicit
45
45
  from Persistence import Persistent
46
46
 
47
+ from AccessControl import frozen
47
48
  from AccessControl.class_init import InitializeClass
48
49
  from AccessControl.ImplPython import _what_not_even_god_should_do
49
50
 
@@ -258,8 +259,8 @@ class ClassSecurityInformation(ClassSecurityInfo):
258
259
  access = 0
259
260
 
260
261
 
261
- _moduleSecurity = {}
262
- _appliedModuleSecurity = {}
262
+ _moduleSecurity = frozen.FreezableDict()
263
+ _appliedModuleSecurity = frozen.FreezableDict()
263
264
 
264
265
 
265
266
  def secureModule(mname, *imp):
@@ -333,6 +334,17 @@ class _ModuleSecurityInfo(SecurityInfo):
333
334
 
334
335
  return getattr(self, 'access', 0)
335
336
 
337
+ def _setaccess(self, names, access):
338
+ frozen.check_not_frozen()
339
+ super()._setaccess(names, access)
340
+
341
+ setDefaultAccess__roles__ = ACCESS_PRIVATE
342
+
343
+ def setDefaultAccess(self, access):
344
+ """Declare default attribute access policy."""
345
+ frozen.check_not_frozen()
346
+ super().setDefaultAccess(access)
347
+
336
348
  apply__roles__ = ACCESS_PRIVATE
337
349
 
338
350
  def apply(self, dict):
@@ -380,11 +392,111 @@ def allow_module(module_name):
380
392
  dot = module_name.find('.', dot + 1)
381
393
 
382
394
 
395
+ allow_module.__roles__ = ACCESS_PRIVATE
396
+
397
+ # Modules that should never be fully accessible from restricted code.
398
+ # A declaration for one of these, or a submodule, is logged by
399
+ # check_dangerous_modules, which freeze_security_declarations calls.
400
+ # PLEASE NOTE: This listing cannot ever be complete and it cannot guard
401
+ # against malicious or sloppy code exposing other unsafe modules! Its
402
+ # purpose is just informational, it is not a safe catch-all.
403
+ DANGEROUS_MODULES = frozenset((
404
+ # processes and code execution
405
+ '_posixsubprocess', '_thread', 'asyncio', 'atexit', 'bdb', 'builtins',
406
+ 'code', 'codeop', 'concurrent', 'copyreg', 'cProfile', 'ctypes',
407
+ '_ctypes', 'doctest', 'faulthandler', 'gc', 'importlib', 'inspect',
408
+ 'logging', 'marshal', 'multiprocessing', 'nt', 'operator', 'os', 'pdb',
409
+ 'pickle', '_pickle', 'pkgutil', 'posix', 'profile', 'pty', 'pydoc',
410
+ 'runpy', 'shelve', 'signal', 'site', 'subprocess', 'sys', 'threading',
411
+ 'timeit', 'trace', 'traceback', 'types', 'unittest', 'webbrowser',
412
+ 'zipimport',
413
+ # filesystem
414
+ 'bz2', 'codecs', 'compileall', 'configparser', 'dbm', 'ensurepip',
415
+ 'fcntl', 'filecmp', 'fileinput', 'genericpath', 'glob', 'gzip', 'io',
416
+ '_io', 'linecache', 'lzma', 'mailbox', 'mmap', 'netrc', 'ntpath',
417
+ 'pathlib', 'posixpath', 'py_compile', 'shutil', 'sqlite3', 'sysconfig',
418
+ 'tarfile', 'tempfile', 'venv', 'zipapp', 'zipfile',
419
+ # network
420
+ 'ftplib', 'http', 'imaplib', 'poplib', 'select', 'selectors', 'smtplib',
421
+ 'socket', 'socketserver', 'ssl', 'urllib', 'wsgiref', 'xmlrpc',
422
+ # information about the system, or process wide state
423
+ 'getpass', 'grp', 'locale', 'platform', 'pwd', 'resource',
424
+ # Zope: security declarations and policy, switching users or sites,
425
+ # transactions, the database
426
+ 'AccessControl', 'App', 'transaction', 'ZODB', 'Zope2',
427
+ 'zope.component.hooks', 'zope.interface',
428
+ # third-party network libraries: requests from the server itself can
429
+ # reach internal services, and httpx can use local Unix sockets
430
+ 'aiohttp', 'httpx', 'pycurl', 'requests', 'urllib3',
431
+ ))
432
+
433
+
434
+ def freeze_security_declarations(dangerous_modules=DANGEROUS_MODULES):
435
+ """Refuse changes to security declarations from now on.
436
+
437
+ Call this once all code has been loaded, for example when Zope has
438
+ started. Afterwards, ModuleSecurityInfo, allow_module, allow_class and
439
+ allow_type raise Forbidden for any change, so new modules, classes and
440
+ types cannot be opened up to restricted code.
441
+
442
+ Normal class security declarations with ClassSecurityInfo still work,
443
+ because classes may be imported later, for example when loading objects
444
+ from the database.
445
+
446
+ Pending declarations are applied first, because importing a module from
447
+ restricted code would otherwise still need to change them. Dangerous
448
+ modules that are fully accessible are logged as a warning, see
449
+ check_dangerous_modules.
450
+
451
+ Calling this when security declarations are already frozen does nothing.
452
+ """
453
+ if frozen.is_frozen():
454
+ return
455
+ # Most of these modules have likely already been imported, because the
456
+ # declarations are usually made in the module itself or its package.
457
+ # So this is fast.
458
+ for name in sorted(_moduleSecurity):
459
+ try:
460
+ secureModule(name, {}, {})
461
+ except ImportError:
462
+ LOG.info(f'Could not import {name!r} to apply its'
463
+ ' module security declarations.')
464
+
465
+ check_dangerous_modules(dangerous_modules)
466
+ frozen.freeze()
467
+
468
+
469
+ def check_dangerous_modules(dangerous_modules=DANGEROUS_MODULES):
470
+ """Log a warning for dangerous modules accessible from restricted code.
471
+
472
+ Only modules that are fully accessible are logged, for example with
473
+ allow_module, not modules where only specific names are public.
474
+ A submodule of a dangerous module is dangerous as well. Pass
475
+ dangerous_modules to check other modules, for example ``{'my.package'}``.
476
+ """
477
+ for name in sorted(set(_moduleSecurity) | set(_appliedModuleSecurity)):
478
+ modsec = _appliedModuleSecurity.get(name) or _moduleSecurity[name]
479
+ access = getattr(modsec, 'access', 0)
480
+ if not access or isinstance(access, dict):
481
+ # Only specific names are accessible.
482
+ continue
483
+ parts = name.split('.')
484
+ for index in range(1, len(parts) + 1):
485
+ if '.'.join(parts[:index]) in dangerous_modules:
486
+ LOG.warning(
487
+ f'Module {name!r} is accessible from restricted code.')
488
+ break
489
+
490
+
383
491
  def allow_class(Class):
384
492
  """Allow a class and all of its methods to be used from a
385
493
  restricted Script. The argument Class must be a class."""
494
+ frozen.check_not_frozen()
386
495
  Class._security = sec = ClassSecurityInfo()
387
496
  sec.declareObjectPublic()
388
497
  sec.setDefaultAccess(1)
389
498
  sec.apply(Class)
390
499
  InitializeClass(Class)
500
+
501
+
502
+ allow_class.__roles__ = ACCESS_PRIVATE
@@ -61,6 +61,8 @@ from BTrees.OOBTree import OOBTree
61
61
  from BTrees.OOBTree import OOBucket
62
62
  from BTrees.OOBTree import OOSet
63
63
 
64
+ from AccessControl.frozen import FreezableDict
65
+
64
66
 
65
67
  _noroles = [] # this is imported in various places
66
68
  _marker = object()
@@ -84,12 +86,13 @@ _marker = object()
84
86
  # The boolean values behave as above, but the functions do not.
85
87
  # The value returned for attribute access is the result of
86
88
  # calling the function with the object and the attribute name.
87
- ContainerAssertions = {
89
+ # Changes are refused once security declarations are frozen.
90
+ ContainerAssertions = FreezableDict({
88
91
  type(()): 1,
89
92
  bytes: 1,
90
93
  str: 1,
91
94
  range: 1,
92
- }
95
+ })
93
96
 
94
97
  Containers = ContainerAssertions.get
95
98
 
@@ -106,6 +109,9 @@ def allow_type(Type, allowed=1):
106
109
  ContainerAssertions[Type] = allowed
107
110
 
108
111
 
112
+ allow_type.__roles__ = () # Only accessible from trusted code
113
+
114
+
109
115
  for tree_type, has_values in [
110
116
  (OOBTree, 1),
111
117
  (OOBucket, 1),
@@ -22,6 +22,7 @@ from AccessControl.SecurityInfo import ClassSecurityInfo
22
22
  from AccessControl.SecurityInfo import ModuleSecurityInfo
23
23
  from AccessControl.SecurityInfo import allow_class
24
24
  from AccessControl.SecurityInfo import allow_module
25
+ from AccessControl.SecurityInfo import freeze_security_declarations
25
26
  from AccessControl.SecurityInfo import secureModule
26
27
  from AccessControl.SecurityManagement import getSecurityManager
27
28
  from AccessControl.SecurityManagement import setSecurityPolicy
@@ -36,7 +37,7 @@ ModuleSecurityInfo('AccessControl').declarePublic('getSecurityManager') # NOQA
36
37
 
37
38
  # allow imports of utility_builtins
38
39
 
39
- for name in ('string', 'math', 'random', 'sets'):
40
+ for name in ('string', 'math', 'random'):
40
41
  ModuleSecurityInfo(name).setDefaultAccess('allow')
41
42
 
42
43
  ModuleSecurityInfo('DateTime').declarePublic('DateTime') # NOQA: D001
@@ -0,0 +1,57 @@
1
+ ##############################################################################
2
+ #
3
+ # Copyright (c) 2026 Zope Foundation and Contributors.
4
+ #
5
+ # This software is subject to the provisions of the Zope Public License,
6
+ # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
7
+ # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8
+ # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9
+ # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10
+ # FOR A PARTICULAR PURPOSE
11
+ #
12
+ ##############################################################################
13
+ """Refuse changes to security declarations once they are frozen.
14
+
15
+ See AccessControl.SecurityInfo.freeze_security_declarations.
16
+ """
17
+
18
+ from zExceptions import Forbidden
19
+
20
+
21
+ _frozen = False
22
+
23
+
24
+ def freeze():
25
+ global _frozen
26
+ _frozen = True
27
+
28
+
29
+ def is_frozen():
30
+ return _frozen
31
+
32
+
33
+ def check_not_frozen():
34
+ if _frozen:
35
+ raise Forbidden('Security declarations are frozen.')
36
+
37
+
38
+ def _guarded_dict_method(name):
39
+ method = getattr(dict, name)
40
+
41
+ def guarded(self, *args, **kwargs):
42
+ check_not_frozen()
43
+ return method(self, *args, **kwargs)
44
+ return guarded
45
+
46
+
47
+ class FreezableDict(dict):
48
+ """dict that refuses changes once security declarations are frozen."""
49
+
50
+ __setitem__ = _guarded_dict_method('__setitem__')
51
+ __delitem__ = _guarded_dict_method('__delitem__')
52
+ __ior__ = _guarded_dict_method('__ior__')
53
+ clear = _guarded_dict_method('clear')
54
+ pop = _guarded_dict_method('pop')
55
+ popitem = _guarded_dict_method('popitem')
56
+ setdefault = _guarded_dict_method('setdefault')
57
+ update = _guarded_dict_method('update')
@@ -40,7 +40,7 @@
40
40
  name="permission"
41
41
  schema="zope.security.zcml.IPermissionDirective"
42
42
  handler=".security.PermissionDirective"/>
43
-
43
+
44
44
  <meta:directive
45
45
  name="role"
46
46
  usedIn="zope.security.zcml.IPermissionDirective"
@@ -127,9 +127,9 @@ def f7():
127
127
  iter_ = access() # iterate twice on the same view
128
128
  assert list(iter_) == list(iter_)
129
129
 
130
- assert sorted([k for k in getattr(d, meth)()]) == expected[kind]
130
+ assert sorted([*getattr(d, meth)()]) == expected[kind]
131
131
  assert sorted(k for k in getattr(d, meth)()) == expected[kind]
132
- assert {k: v for k, v in d.items()} == d
132
+ assert {**d} == d
133
133
 
134
134
  assert 1 in d
135
135
  assert 1 in d.keys()
@@ -0,0 +1,11 @@
1
+ # test module, public
2
+
3
+ from Acquisition import aq_base # Foil the old ZopeSecurityPolicy # NOQA: F401
4
+
5
+ from AccessControl.SecurityInfo import allow_class # NOQA: F401
6
+ from AccessControl.SecurityInfo import allow_module # NOQA: F401
7
+ from AccessControl.SimpleObjectPolicies import allow_type # NOQA: F401
8
+
9
+
10
+ def pub():
11
+ pass