CVE-2025-54433
Description
Bugsink is a self-hosted error tracking service. In versions 1.4.2 and below, 1.5.0 through 1.5.4, 1.6.0 through 1.6.3, and 1.7.0 through 1.7.3, ingestion paths construct file locations directly from untrusted event_id input without validation. A specially crafted event_id can result in paths outside the intended directory, potentially allowing file overwrite or creation in arbitrary locations. Submitting such input requires access to a valid DSN, potentially exposing them. If Bugsink runs in a container, the effect is confined to the container’s filesystem. In non-containerized setups, the overwrite may affect other parts of the system accessible to that user. This is fixed in versions 1.4.3, 1.5.5, 1.6.4 and 1.7.4.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
bugsinkPyPI | >= 1.7.0, < 1.7.4 | 1.7.4 |
bugsinkPyPI | >= 1.6.0, < 1.6.4 | 1.6.4 |
bugsinkPyPI | >= 1.5.0, < 1.5.5 | 1.5.5 |
bugsinkPyPI | < 1.4.3 | 1.4.3 |
Affected products
1Patches
12f3092091815aee1f4a94d819bcc240f781255b0d0bba4480b94aa8a5c96cenvelope event_id check: on-parse
2 files changed · +12 −1
ingest/filestore.py+4 −1 modified@@ -8,7 +8,10 @@ def get_filename_for_event_id(event_id): # implemented. However, counterpoint: when doing stress tests, it was quite hard to get a serious backlog going # (snappea was very well able to play catch-up). So this might not be necessary. - # ensure that event_id is a uuid, and remove dashes if present + # ensure that event_id is a uuid, and remove dashes if present; also doubles as a security-check (event_id is + # user-provided (but at this point already validated to be a valid UUID), but b/c of the below the + # security-implications of os.path.join can be understood right here in the code without needing to inspect all + # call-sites). event_id_normalized = uuid.UUID(event_id).hex return os.path.join(get_settings().INGEST_STORE_BASE_DIR, event_id_normalized)
ingest/views.py+8 −0 modified@@ -1,3 +1,4 @@ +import uuid import hashlib import os import logging @@ -618,6 +619,13 @@ def factory(item_headers): # payload's event_id), so we can rely on it having been set. if "event_id" not in envelope_headers: raise ParseError("event_id not found in envelope headers") + + try: + # validate that the event_id is a valid UUID as per the spec (validate at the edge) + uuid.UUID(envelope_headers["event_id"]) + except ValueError: + raise ParseError("event_id in envelope headers is not a valid UUID") + filename = get_filename_for_event_id(envelope_headers["event_id"]) os.makedirs(os.path.dirname(filename), exist_ok=True) return MaxDataWriter("MAX_EVENT_SIZE", open(filename, 'wb'))
c341687bd655envelope event_id check: on-parse
2 files changed · +12 −1
ingest/filestore.py+4 −1 modified@@ -8,7 +8,10 @@ def get_filename_for_event_id(event_id): # implemented. However, counterpoint: when doing stress tests, it was quite hard to get a serious backlog going # (snappea was very well able to play catch-up). So this might not be necessary. - # ensure that event_id is a uuid, and remove dashes if present + # ensure that event_id is a uuid, and remove dashes if present; also doubles as a security-check (event_id is + # user-provided (but at this point already validated to be a valid UUID), but b/c of the below the + # security-implications of os.path.join can be understood right here in the code without needing to inspect all + # call-sites). event_id_normalized = uuid.UUID(event_id).hex return os.path.join(get_settings().INGEST_STORE_BASE_DIR, event_id_normalized)
ingest/views.py+8 −0 modified@@ -1,3 +1,4 @@ +import uuid import hashlib import os import logging @@ -611,6 +612,13 @@ def factory(item_headers): # payload's event_id), so we can rely on it having been set. if "event_id" not in envelope_headers: raise ParseError("event_id not found in envelope headers") + + try: + # validate that the event_id is a valid UUID as per the spec (validate at the edge) + uuid.UUID(envelope_headers["event_id"]) + except ValueError: + raise ParseError("event_id in envelope headers is not a valid UUID") + filename = get_filename_for_event_id(envelope_headers["event_id"]) os.makedirs(os.path.dirname(filename), exist_ok=True) return MaxDataWriter("MAX_EVENT_SIZE", open(filename, 'wb'))
211ddf76758cenvelope event_id check: on-parse
2 files changed · +12 −1
ingest/filestore.py+4 −1 modified@@ -8,7 +8,10 @@ def get_filename_for_event_id(event_id): # implemented. However, counterpoint: when doing stress tests, it was quite hard to get a serious backlog going # (snappea was very well able to play catch-up). So this might not be necessary. - # ensure that event_id is a uuid, and remove dashes if present + # ensure that event_id is a uuid, and remove dashes if present; also doubles as a security-check (event_id is + # user-provided (but at this point already validated to be a valid UUID), but b/c of the below the + # security-implications of os.path.join can be understood right here in the code without needing to inspect all + # call-sites). event_id_normalized = uuid.UUID(event_id).hex return os.path.join(get_settings().INGEST_STORE_BASE_DIR, event_id_normalized)
ingest/views.py+8 −0 modified@@ -1,3 +1,4 @@ +import uuid import os import logging import io @@ -602,6 +603,13 @@ def factory(item_headers): # payload's event_id), so we can rely on it having been set. if "event_id" not in envelope_headers: raise ParseError("event_id not found in envelope headers") + + try: + # validate that the event_id is a valid UUID as per the spec (validate at the edge) + uuid.UUID(envelope_headers["event_id"]) + except ValueError: + raise ParseError("event_id in envelope headers is not a valid UUID") + filename = get_filename_for_event_id(envelope_headers["event_id"]) os.makedirs(os.path.dirname(filename), exist_ok=True) return MaxDataWriter("MAX_EVENT_SIZE", open(filename, 'wb'))
c87217bd5651envelope event_id check: on-parse
2 files changed · +12 −1
ingest/filestore.py+4 −1 modified@@ -8,7 +8,10 @@ def get_filename_for_event_id(event_id): # implemented. However, counterpoint: when doing stress tests, it was quite hard to get a serious backlog going # (snappea was very well able to play catch-up). So this might not be necessary. - # ensure that event_id is a uuid, and remove dashes if present + # ensure that event_id is a uuid, and remove dashes if present; also doubles as a security-check (event_id is + # user-provided (but at this point already validated to be a valid UUID), but b/c of the below the + # security-implications of os.path.join can be understood right here in the code without needing to inspect all + # call-sites). event_id_normalized = uuid.UUID(event_id).hex return os.path.join(get_settings().INGEST_STORE_BASE_DIR, event_id_normalized)
ingest/views.py+8 −0 modified@@ -1,3 +1,4 @@ +import uuid import hashlib import os import logging @@ -611,6 +612,13 @@ def factory(item_headers): # payload's event_id), so we can rely on it having been set. if "event_id" not in envelope_headers: raise ParseError("event_id not found in envelope headers") + + try: + # validate that the event_id is a valid UUID as per the spec (validate at the edge) + uuid.UUID(envelope_headers["event_id"]) + except ValueError: + raise ParseError("event_id in envelope headers is not a valid UUID") + filename = get_filename_for_event_id(envelope_headers["event_id"]) os.makedirs(os.path.dirname(filename), exist_ok=True) return MaxDataWriter("MAX_EVENT_SIZE", open(filename, 'wb'))
1001726f4389Ingestion: ensure event_id is a dashless uuid before using as a filename
1 file changed · +5 −1
ingest/filestore.py+5 −1 modified@@ -1,3 +1,4 @@ +import uuid import os from bugsink.app_settings import get_settings @@ -7,4 +8,7 @@ def get_filename_for_event_id(event_id): # implemented. However, counterpoint: when doing stress tests, it was quite hard to get a serious backlog going # (snappea was very well able to play catch-up). So this might not be necessary. - return os.path.join(get_settings().INGEST_STORE_BASE_DIR, event_id) + # ensure that event_id is a uuid, and remove dashes if present + event_id_normalized = uuid.UUID(event_id).hex + + return os.path.join(get_settings().INGEST_STORE_BASE_DIR, event_id_normalized)
2c41fbe3881bIngestion: ensure event_id is a dashless uuid before using as a filename
1 file changed · +5 −1
ingest/filestore.py+5 −1 modified@@ -1,3 +1,4 @@ +import uuid import os from bugsink.app_settings import get_settings @@ -7,4 +8,7 @@ def get_filename_for_event_id(event_id): # implemented. However, counterpoint: when doing stress tests, it was quite hard to get a serious backlog going # (snappea was very well able to play catch-up). So this might not be necessary. - return os.path.join(get_settings().INGEST_STORE_BASE_DIR, event_id) + # ensure that event_id is a uuid, and remove dashes if present + event_id_normalized = uuid.UUID(event_id).hex + + return os.path.join(get_settings().INGEST_STORE_BASE_DIR, event_id_normalized)
53cf1a17a3e9Ingestion: ensure event_id is a dashless uuid before using as a filename
1 file changed · +5 −1
ingest/filestore.py+5 −1 modified@@ -1,3 +1,4 @@ +import uuid import os from bugsink.app_settings import get_settings @@ -7,4 +8,7 @@ def get_filename_for_event_id(event_id): # implemented. However, counterpoint: when doing stress tests, it was quite hard to get a serious backlog going # (snappea was very well able to play catch-up). So this might not be necessary. - return os.path.join(get_settings().INGEST_STORE_BASE_DIR, event_id) + # ensure that event_id is a uuid, and remove dashes if present + event_id_normalized = uuid.UUID(event_id).hex + + return os.path.join(get_settings().INGEST_STORE_BASE_DIR, event_id_normalized)
55a155003d0bIngestion: ensure event_id is a dashless uuid before using as a filename
1 file changed · +5 −1
ingest/filestore.py+5 −1 modified@@ -1,3 +1,4 @@ +import uuid import os from bugsink.app_settings import get_settings @@ -7,4 +8,7 @@ def get_filename_for_event_id(event_id): # implemented. However, counterpoint: when doing stress tests, it was quite hard to get a serious backlog going # (snappea was very well able to play catch-up). So this might not be necessary. - return os.path.join(get_settings().INGEST_STORE_BASE_DIR, event_id) + # ensure that event_id is a uuid, and remove dashes if present + event_id_normalized = uuid.UUID(event_id).hex + + return os.path.join(get_settings().INGEST_STORE_BASE_DIR, event_id_normalized)
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
11- github.com/advisories/GHSA-q78p-g86f-jg6qghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-54433ghsaADVISORY
- github.com/bugsink/bugsink/commit/1001726f4389e982c486cdd5fa81941cb46cfc33nvdWEB
- github.com/bugsink/bugsink/commit/211ddf76758c808c095b5f836c363f148d934d21nvdWEB
- github.com/bugsink/bugsink/commit/2c41fbe3881bdea83399a7f9fdc8cff198ae089fnvdWEB
- github.com/bugsink/bugsink/commit/53cf1a17a3e96f7c83c7451fd56f980a09d0c9b0nvdWEB
- github.com/bugsink/bugsink/commit/55a155003d0b416ea008c5e7dcde85130ad21d9bnvdWEB
- github.com/bugsink/bugsink/commit/b94aa8a5c96ce8cdd9711b6beb4e518264993ac2nvdWEB
- github.com/bugsink/bugsink/commit/c341687bd655543730c812db35c29199f788be6bnvdWEB
- github.com/bugsink/bugsink/commit/c87217bd565122ba70af90436e3ab2cd9bee658fnvdWEB
- github.com/bugsink/bugsink/security/advisories/GHSA-q78p-g86f-jg6qnvdWEB
News mentions
0No linked articles in our index yet.