Skip to content

Commit 171fabb

Browse files
committed
Mask API tokens in log messages
Keep first 10 chars visible for identification while hiding the rest. Addresses CodeQL security warning about logging sensitive data.
1 parent 1b57c72 commit 171fabb

File tree

2 files changed

+45
-2
lines changed

2 files changed

+45
-2
lines changed

posthog/request.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import logging
3+
import re
34
from dataclasses import dataclass
45
from datetime import date, datetime
56
from gzip import GzipFile
@@ -14,6 +15,11 @@
1415
from posthog.version import VERSION
1516

1617

18+
def _mask_tokens_in_url(url: str) -> str:
19+
"""Mask token values in URLs for safe logging, keeping first 10 chars visible."""
20+
return re.sub(r"(token=)([^&]{10})[^&]*", r"\1\2...", url)
21+
22+
1723
@dataclass
1824
class GetResponse:
1925
"""Response from a GET request with ETag support."""
@@ -196,15 +202,17 @@ def get(
196202

197203
res = _session.get(full_url, headers=headers, timeout=timeout)
198204

205+
masked_url = _mask_tokens_in_url(full_url)
206+
199207
# Handle 304 Not Modified
200208
if res.status_code == 304:
201-
log.debug(f"GET {full_url} returned 304 Not Modified")
209+
log.debug(f"GET {masked_url} returned 304 Not Modified")
202210
response_etag = res.headers.get("ETag")
203211
return GetResponse(data=None, etag=response_etag or etag, not_modified=True)
204212

205213
# Handle normal response
206214
data = _process_response(
207-
res, success_message=f"GET {full_url} completed successfully"
215+
res, success_message=f"GET {masked_url} completed successfully"
208216
)
209217
response_etag = res.headers.get("ETag")
210218
return GetResponse(data=data, etag=response_etag, not_modified=False)

posthog/test/test_request.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
DatetimeSerializer,
1212
GetResponse,
1313
QuotaLimitError,
14+
_mask_tokens_in_url,
1415
batch_post,
1516
decide,
1617
determine_server_host,
@@ -19,6 +20,40 @@
1920
from posthog.test.test_utils import TEST_API_KEY
2021

2122

23+
@pytest.mark.parametrize(
24+
"url, expected",
25+
[
26+
# Token with params after - masks keeping first 10 chars
27+
(
28+
"https://example.com/api/flags?token=phc_abc123xyz789&send_cohorts",
29+
"https://example.com/api/flags?token=phc_abc123...&send_cohorts",
30+
),
31+
# Token at end of URL
32+
(
33+
"https://example.com/api/flags?token=phc_abc123xyz789",
34+
"https://example.com/api/flags?token=phc_abc123...",
35+
),
36+
# No token - unchanged
37+
(
38+
"https://example.com/api/flags?other=value",
39+
"https://example.com/api/flags?other=value",
40+
),
41+
# Short token (<10 chars) - unchanged
42+
(
43+
"https://example.com/api/flags?token=short",
44+
"https://example.com/api/flags?token=short",
45+
),
46+
# Exactly 10 char token - gets ellipsis
47+
(
48+
"https://example.com/api/flags?token=1234567890",
49+
"https://example.com/api/flags?token=1234567890...",
50+
),
51+
],
52+
)
53+
def test_mask_tokens_in_url(url, expected):
54+
assert _mask_tokens_in_url(url) == expected
55+
56+
2257
class TestRequests(unittest.TestCase):
2358
def test_valid_request(self):
2459
res = batch_post(

0 commit comments

Comments
 (0)