watchdock-errors 0.4.0__tar.gz → 0.5.0__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.
- {watchdock_errors-0.4.0/src/watchdock_errors.egg-info → watchdock_errors-0.5.0}/PKG-INFO +2 -2
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/README.md +1 -1
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors/event.py +21 -5
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors/integrations/fastapi.py +9 -1
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors/utils.py +55 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0/src/watchdock_errors.egg-info}/PKG-INFO +2 -2
- watchdock_errors-0.5.0/src/watchdock_errors.egg-info/scm_version.json +8 -0
- watchdock_errors-0.4.0/src/watchdock_errors.egg-info/scm_version.json +0 -8
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/.github/workflows/ci.yml +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/.github/workflows/release.yml +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/.gitignore +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/pyproject.toml +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/setup.cfg +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors/__init__.py +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors/client.py +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors/config.py +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors/integrations/__init__.py +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors/integrations/celery.py +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors/integrations/django.py +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors.egg-info/SOURCES.txt +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors.egg-info/dependency_links.txt +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors.egg-info/requires.txt +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors.egg-info/scm_file_list.json +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors.egg-info/top_level.txt +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/tests/test_client.py +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/tests/test_event.py +0 -0
- {watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: watchdock-errors
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Application error tracking SDK for the Watchdock platform
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: error-tracking,observability,watchdock
|
|
@@ -138,7 +138,7 @@ When `init()` is called, the SDK sends a one-time, fire-and-forget ping to the p
|
|
|
138
138
|
|
|
139
139
|
## PII scrubbing
|
|
140
140
|
|
|
141
|
-
By default, `Authorization`, `Cookie`, and `X-Api-Key` headers are stripped
|
|
141
|
+
By default, `Authorization`, `Cookie`, `Set-Cookie`, and `X-Api-Key` headers are stripped, the request body is not sent, and sensitive-looking query-param values (names containing `token`, `secret`, `password`, `auth`, `key`, `session`, `credential`, `otp`, `pin`, `ssn`, etc.) are redacted in both `query_params` and the captured `url` itself. Set `send_pii=True` to disable scrubbing.
|
|
142
142
|
|
|
143
143
|
Use the `before_send` hook for custom scrubbing:
|
|
144
144
|
|
|
@@ -116,7 +116,7 @@ When `init()` is called, the SDK sends a one-time, fire-and-forget ping to the p
|
|
|
116
116
|
|
|
117
117
|
## PII scrubbing
|
|
118
118
|
|
|
119
|
-
By default, `Authorization`, `Cookie`, and `X-Api-Key` headers are stripped
|
|
119
|
+
By default, `Authorization`, `Cookie`, `Set-Cookie`, and `X-Api-Key` headers are stripped, the request body is not sent, and sensitive-looking query-param values (names containing `token`, `secret`, `password`, `auth`, `key`, `session`, `credential`, `otp`, `pin`, `ssn`, etc.) are redacted in both `query_params` and the captured `url` itself. Set `send_pii=True` to disable scrubbing.
|
|
120
120
|
|
|
121
121
|
Use the `before_send` hook for custom scrubbing:
|
|
122
122
|
|
|
@@ -4,10 +4,17 @@ import datetime
|
|
|
4
4
|
import logging
|
|
5
5
|
|
|
6
6
|
from .config import SDKConfig
|
|
7
|
-
from .utils import extract_stacktrace, extract_trace_id, get_server_info
|
|
7
|
+
from .utils import extract_stacktrace, extract_trace_id, get_server_info, sanitize_url_and_query_params
|
|
8
8
|
|
|
9
9
|
logger = logging.getLogger("watchdock_errors")
|
|
10
10
|
|
|
11
|
+
#: Lowercased for comparison -- header casing isn't consistent across integrations (e.g.
|
|
12
|
+
#: Django's request.headers is Title-Case, but raw ASGI scope headers used by the FastAPI
|
|
13
|
+
#: integration are always lowercase per spec), so matching by exact Title-Case key silently
|
|
14
|
+
#: let Authorization/Cookie/X-Api-Key through unscrubbed for any integration that doesn't
|
|
15
|
+
#: happen to produce Django's casing.
|
|
16
|
+
_SENSITIVE_HEADER_NAMES = {"authorization", "cookie", "set-cookie", "x-api-key"}
|
|
17
|
+
|
|
11
18
|
|
|
12
19
|
def build_event(
|
|
13
20
|
exc: BaseException | None,
|
|
@@ -75,10 +82,19 @@ def build_event(
|
|
|
75
82
|
else:
|
|
76
83
|
req = dict(request_context.get("request", {}))
|
|
77
84
|
req.pop("body", None)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
req["headers"] = {
|
|
86
|
+
key: value
|
|
87
|
+
for key, value in req.get("headers", {}).items()
|
|
88
|
+
if key.lower() not in _SENSITIVE_HEADER_NAMES
|
|
89
|
+
}
|
|
90
|
+
if "url" in req or "query_params" in req:
|
|
91
|
+
sanitized_url, sanitized_params = sanitize_url_and_query_params(
|
|
92
|
+
req.get("url", ""), req.get("query_params")
|
|
93
|
+
)
|
|
94
|
+
if "url" in req:
|
|
95
|
+
req["url"] = sanitized_url
|
|
96
|
+
if "query_params" in req:
|
|
97
|
+
req["query_params"] = sanitized_params
|
|
82
98
|
event["request"] = req
|
|
83
99
|
|
|
84
100
|
if config.before_send is not None:
|
{watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors/integrations/fastapi.py
RENAMED
|
@@ -10,6 +10,7 @@ Usage:
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
from typing import TYPE_CHECKING
|
|
13
|
+
from urllib.parse import parse_qs
|
|
13
14
|
|
|
14
15
|
import watchdock_errors
|
|
15
16
|
|
|
@@ -37,15 +38,22 @@ class WatchdockASGIMiddleware:
|
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
def _build_request_context(scope: dict) -> dict:
|
|
41
|
+
# ASGI header names are always lowercase bytes per spec (unlike Django's Title-Case
|
|
42
|
+
# request.headers) -- header scrubbing is case-insensitive, so this doesn't need to
|
|
43
|
+
# match Django's casing, just be a dict of decoded strings.
|
|
40
44
|
headers = {k.decode(): v.decode() for k, v in scope.get("headers", [])}
|
|
41
45
|
query_string = scope.get("query_string", b"").decode()
|
|
46
|
+
# dict, not the raw query string -- matches the shape every other integration produces
|
|
47
|
+
# (and what sanitize_url_and_query_params/the frontend expect). Single-value params
|
|
48
|
+
# come through as a plain string rather than a 1-item list, matching Django's shape.
|
|
49
|
+
query_params = {key: values[0] if len(values) == 1 else values for key, values in parse_qs(query_string).items()}
|
|
42
50
|
|
|
43
51
|
return {
|
|
44
52
|
"request": {
|
|
45
53
|
"method": scope.get("method", ""),
|
|
46
54
|
"url": _build_url(scope),
|
|
47
55
|
"headers": headers,
|
|
48
|
-
"query_params":
|
|
56
|
+
"query_params": query_params,
|
|
49
57
|
}
|
|
50
58
|
}
|
|
51
59
|
|
|
@@ -65,6 +65,61 @@ def get_server_info() -> dict:
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
|
|
68
|
+
#: Substring match (case-insensitive) against query-param names. Deliberately broad —
|
|
69
|
+
#: over-redacting an innocuous param (e.g. "sort_key") is a much smaller cost than
|
|
70
|
+
#: leaking a reset token, session id, or api key sitting in a URL.
|
|
71
|
+
_SENSITIVE_QUERY_PARAM_SUBSTRINGS = (
|
|
72
|
+
"token",
|
|
73
|
+
"secret",
|
|
74
|
+
"password",
|
|
75
|
+
"passwd",
|
|
76
|
+
"pwd",
|
|
77
|
+
"auth",
|
|
78
|
+
"key",
|
|
79
|
+
"session",
|
|
80
|
+
"credential",
|
|
81
|
+
"otp",
|
|
82
|
+
"pin",
|
|
83
|
+
"ssn",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _is_sensitive_param(name: str) -> bool:
|
|
88
|
+
lowered = name.lower()
|
|
89
|
+
return any(substring in lowered for substring in _SENSITIVE_QUERY_PARAM_SUBSTRINGS)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def sanitize_url_and_query_params(url: str, query_params: dict | None) -> tuple[str, dict]:
|
|
93
|
+
"""Redact sensitive query-param values from both ``query_params`` and ``url`` itself.
|
|
94
|
+
|
|
95
|
+
Headers and the request body get their own scrubbing, but a URL is often the most
|
|
96
|
+
PII-dense thing captured by default -- password-reset tokens, magic-link codes,
|
|
97
|
+
``?api_key=...``, session ids -- and previously passed through untouched even with
|
|
98
|
+
PII collection off. Rebuilds the url's query string from the redacted params rather
|
|
99
|
+
than just deleting it, so the two never disagree and the path (still useful for
|
|
100
|
+
grouping/debugging) is preserved.
|
|
101
|
+
"""
|
|
102
|
+
from urllib.parse import urlencode, urlsplit, urlunsplit
|
|
103
|
+
|
|
104
|
+
sanitized_params: dict = {}
|
|
105
|
+
for key, value in (query_params or {}).items():
|
|
106
|
+
if _is_sensitive_param(key):
|
|
107
|
+
sanitized_params[key] = ["[REDACTED]"] * len(value) if isinstance(value, list) else "[REDACTED]"
|
|
108
|
+
else:
|
|
109
|
+
sanitized_params[key] = value
|
|
110
|
+
|
|
111
|
+
if not url:
|
|
112
|
+
return url, sanitized_params
|
|
113
|
+
|
|
114
|
+
parts = urlsplit(url)
|
|
115
|
+
if parts.query:
|
|
116
|
+
# doseq=True so list-valued params (e.g. Django QueryDict-style) re-encode correctly.
|
|
117
|
+
new_query = urlencode(sanitized_params, doseq=True)
|
|
118
|
+
url = urlunsplit((parts.scheme, parts.netloc, parts.path, new_query, parts.fragment))
|
|
119
|
+
|
|
120
|
+
return url, sanitized_params
|
|
121
|
+
|
|
122
|
+
|
|
68
123
|
def extract_trace_id(headers: dict | None) -> str | None:
|
|
69
124
|
"""
|
|
70
125
|
Pull a correlation ID off incoming request headers so this event can be
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: watchdock-errors
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Application error tracking SDK for the Watchdock platform
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: error-tracking,observability,watchdock
|
|
@@ -138,7 +138,7 @@ When `init()` is called, the SDK sends a one-time, fire-and-forget ping to the p
|
|
|
138
138
|
|
|
139
139
|
## PII scrubbing
|
|
140
140
|
|
|
141
|
-
By default, `Authorization`, `Cookie`, and `X-Api-Key` headers are stripped
|
|
141
|
+
By default, `Authorization`, `Cookie`, `Set-Cookie`, and `X-Api-Key` headers are stripped, the request body is not sent, and sensitive-looking query-param values (names containing `token`, `secret`, `password`, `auth`, `key`, `session`, `credential`, `otp`, `pin`, `ssn`, etc.) are redacted in both `query_params` and the captured `url` itself. Set `send_pii=True` to disable scrubbing.
|
|
142
142
|
|
|
143
143
|
Use the `before_send` hook for custom scrubbing:
|
|
144
144
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors/integrations/__init__.py
RENAMED
|
File without changes
|
{watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors/integrations/celery.py
RENAMED
|
File without changes
|
{watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors/integrations/django.py
RENAMED
|
File without changes
|
|
File without changes
|
{watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors.egg-info/requires.txt
RENAMED
|
File without changes
|
{watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors.egg-info/scm_file_list.json
RENAMED
|
File without changes
|
{watchdock_errors-0.4.0 → watchdock_errors-0.5.0}/src/watchdock_errors.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|