Skip to content

Commit 23abc9f

Browse files
NoahStappblink1073
authored andcommitted
PYTHON-5449 - Do not attach invalid document in exception message (#2539)
(cherry picked from commit 266caf0)
1 parent a71c96d commit 23abc9f

File tree

5 files changed

+54
-20
lines changed

5 files changed

+54
-20
lines changed

bson/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1009,7 +1009,7 @@ def _dict_to_bson(
10091009
try:
10101010
elements.append(_element_to_bson(key, value, check_keys, opts))
10111011
except InvalidDocument as err:
1012-
raise InvalidDocument(f"Invalid document {doc} | {err}") from err
1012+
raise InvalidDocument(f"Invalid document: {err}", doc) from err
10131013
except AttributeError:
10141014
raise TypeError(f"encoder expected a mapping type but got: {doc!r}") from None
10151015

bson/_cbsonmodule.c

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,11 +1645,11 @@ static int write_raw_doc(buffer_t buffer, PyObject* raw, PyObject* _raw_str) {
16451645
}
16461646

16471647

1648-
/* Update Invalid Document error message to include doc.
1648+
/* Update Invalid Document error to include doc as a property.
16491649
*/
16501650
void handle_invalid_doc_error(PyObject* dict) {
16511651
PyObject *etype = NULL, *evalue = NULL, *etrace = NULL;
1652-
PyObject *msg = NULL, *dict_str = NULL, *new_msg = NULL;
1652+
PyObject *msg = NULL, *new_msg = NULL, *new_evalue = NULL;
16531653
PyErr_Fetch(&etype, &evalue, &etrace);
16541654
PyObject *InvalidDocument = _error("InvalidDocument");
16551655
if (InvalidDocument == NULL) {
@@ -1659,26 +1659,22 @@ void handle_invalid_doc_error(PyObject* dict) {
16591659
if (evalue && PyErr_GivenExceptionMatches(etype, InvalidDocument)) {
16601660
PyObject *msg = PyObject_Str(evalue);
16611661
if (msg) {
1662-
// Prepend doc to the existing message
1663-
PyObject *dict_str = PyObject_Str(dict);
1664-
if (dict_str == NULL) {
1665-
goto cleanup;
1666-
}
1667-
const char * dict_str_utf8 = PyUnicode_AsUTF8(dict_str);
1668-
if (dict_str_utf8 == NULL) {
1669-
goto cleanup;
1670-
}
16711662
const char * msg_utf8 = PyUnicode_AsUTF8(msg);
16721663
if (msg_utf8 == NULL) {
16731664
goto cleanup;
16741665
}
1675-
PyObject *new_msg = PyUnicode_FromFormat("Invalid document %s | %s", dict_str_utf8, msg_utf8);
1666+
PyObject *new_msg = PyUnicode_FromFormat("Invalid document: %s", msg_utf8);
1667+
if (new_msg == NULL) {
1668+
goto cleanup;
1669+
}
1670+
// Add doc to the error instance as a property.
1671+
PyObject *new_evalue = PyObject_CallFunctionObjArgs(InvalidDocument, new_msg, dict, NULL);
16761672
Py_DECREF(evalue);
16771673
Py_DECREF(etype);
16781674
etype = InvalidDocument;
16791675
InvalidDocument = NULL;
1680-
if (new_msg) {
1681-
evalue = new_msg;
1676+
if (new_evalue) {
1677+
evalue = new_evalue;
16821678
} else {
16831679
evalue = msg;
16841680
}
@@ -1689,7 +1685,7 @@ void handle_invalid_doc_error(PyObject* dict) {
16891685
PyErr_Restore(etype, evalue, etrace);
16901686
Py_XDECREF(msg);
16911687
Py_XDECREF(InvalidDocument);
1692-
Py_XDECREF(dict_str);
1688+
Py_XDECREF(new_evalue);
16931689
Py_XDECREF(new_msg);
16941690
}
16951691

bson/errors.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
"""Exceptions raised by the BSON package."""
1616
from __future__ import annotations
1717

18+
from typing import Any, Optional
19+
1820

1921
class BSONError(Exception):
2022
"""Base class for all BSON exceptions."""
@@ -31,6 +33,17 @@ class InvalidStringData(BSONError):
3133
class InvalidDocument(BSONError):
3234
"""Raised when trying to create a BSON object from an invalid document."""
3335

36+
def __init__(self, message: str, document: Optional[Any] = None) -> None:
37+
super().__init__(message)
38+
self._document = document
39+
40+
@property
41+
def document(self) -> Any:
42+
"""The invalid document that caused the error.
43+
44+
..versionadded:: 4.16"""
45+
return self._document
46+
3447

3548
class InvalidId(BSONError):
3649
"""Raised when trying to create an ObjectId from invalid data."""

doc/changelog.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
Changelog
22
=========
33

4+
Changes in Version 4.15.2 (2025/XX/YY)
5+
--------------------------------------
6+
7+
Version 4.15.3 is a bug fix release.
8+
9+
- Removed invalid documents from :class:`bson.errors.InvalidDocument` error messages as
10+
doing so may leak sensitive user data.
11+
Instead, invalid documents are stored in :attr:`bson.errors.InvalidDocument.document`.
12+
13+
Issues Resolved
14+
...............
15+
16+
See the `PyMongo 4.15.3 release notes in JIRA`_ for the list of resolved issues
17+
in this release.
18+
19+
.. _PyMongo 4.15.3 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=47293
20+
421
Changes in Version 4.15.2 (2025/10/01)
522
--------------------------------------
623

@@ -16,6 +33,7 @@ in this release.
1633

1734
.. _PyMongo 4.15.2 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=47186
1835

36+
1937
Changes in Version 4.15.1 (2025/09/16)
2038
--------------------------------------
2139

test/test_bson.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,7 +1163,7 @@ def __repr__(self):
11631163
):
11641164
encode({"t": Wrapper(1)})
11651165

1166-
def test_doc_in_invalid_document_error_message(self):
1166+
def test_doc_in_invalid_document_error_as_property(self):
11671167
class Wrapper:
11681168
def __init__(self, val):
11691169
self.val = val
@@ -1173,10 +1173,11 @@ def __repr__(self):
11731173

11741174
self.assertEqual("1", repr(Wrapper(1)))
11751175
doc = {"t": Wrapper(1)}
1176-
with self.assertRaisesRegex(InvalidDocument, f"Invalid document {doc}"):
1176+
with self.assertRaisesRegex(InvalidDocument, "Invalid document:") as cm:
11771177
encode(doc)
1178+
self.assertEqual(cm.exception.document, doc)
11781179

1179-
def test_doc_in_invalid_document_error_message_mapping(self):
1180+
def test_doc_in_invalid_document_error_as_property_mapping(self):
11801181
class MyMapping(abc.Mapping):
11811182
def keys(self):
11821183
return ["t"]
@@ -1192,6 +1193,11 @@ def __len__(self):
11921193
def __iter__(self):
11931194
return iter(["t"])
11941195

1196+
def __eq__(self, other):
1197+
if isinstance(other, MyMapping):
1198+
return True
1199+
return False
1200+
11951201
class Wrapper:
11961202
def __init__(self, val):
11971203
self.val = val
@@ -1201,8 +1207,9 @@ def __repr__(self):
12011207

12021208
self.assertEqual("1", repr(Wrapper(1)))
12031209
doc = MyMapping()
1204-
with self.assertRaisesRegex(InvalidDocument, f"Invalid document {doc}"):
1210+
with self.assertRaisesRegex(InvalidDocument, "Invalid document:") as cm:
12051211
encode(doc)
1212+
self.assertEqual(cm.exception.document, doc)
12061213

12071214

12081215
class TestCodecOptions(unittest.TestCase):

0 commit comments

Comments
 (0)