fix: mask_url re-encodes non-sensitive query values on rebuild (v0.3.4)
the identity quote_via lambda disabled percent-encoding on re-emit, so a non-sensitive value with a reserved character (e.g. x=%26%3D) came out structurally corrupted (x=&=). re-encode with quote_plus (safe="*" so the masked "***" stays literal) instead of the identity function - display fidelity only, the secret is still always masked to "***" first. Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
@@ -12,15 +12,15 @@ Small sync helpers shared across projects. Base is stdlib only — **no dependen
|
|||||||
## Install
|
## Install
|
||||||
|
|
||||||
```
|
```
|
||||||
commons @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.3.3
|
commons @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.3.4
|
||||||
# async address/geo lookups (fetch_ip / ip_location / fetch_location) need the extra:
|
# async address/geo lookups (fetch_ip / ip_location / fetch_location) need the extra:
|
||||||
commons[addr] @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.3.3
|
commons[addr] @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.3.4
|
||||||
```
|
```
|
||||||
|
|
||||||
The base install pulls **nothing** (stdlib). Only `commons[addr]` adds `aiohttp`, and
|
The base install pulls **nothing** (stdlib). Only `commons[addr]` adds `aiohttp`, and
|
||||||
only for the geo lookups — the pure `commons.addr.ip` utilities ship in base.
|
only for the geo lookups — the pure `commons.addr.ip` utilities ship in base.
|
||||||
|
|
||||||
Drop the `@v0.3.3` suffix from the line above to install the latest unpinned.
|
Drop the `@v0.3.4` suffix from the line above to install the latest unpinned.
|
||||||
|
|
||||||
## timing
|
## timing
|
||||||
|
|
||||||
@@ -135,8 +135,10 @@ mask_proxy("1.2.3.4:8080") # -> "1.2.3.4:8080" (no auth, unt
|
|||||||
|
|
||||||
`mask_url` strips `user:pass@` userinfo and replaces the values of sensitive query
|
`mask_url` strips `user:pass@` userinfo and replaces the values of sensitive query
|
||||||
params (`apiKey`, `token`, `password`, `secret`, …; override via `keys=`) with `***`.
|
params (`apiKey`, `token`, `password`, `secret`, …; override via `keys=`) with `***`.
|
||||||
`mask_proxy` bullets the password of a `host:port:user:password` spec. Non-URL /
|
Non-sensitive query values are re-percent-encoded on the way out, so a value with a
|
||||||
non-conforming input is returned unchanged.
|
reserved character (`&`, `=`, a space, …) round-trips correctly instead of corrupting
|
||||||
|
the rebuilt URL. `mask_proxy` bullets the password of a `host:port:user:password`
|
||||||
|
spec. Non-URL / non-conforming input is returned unchanged.
|
||||||
|
|
||||||
## retry
|
## retry
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "commons"
|
name = "commons"
|
||||||
version = "0.3.3"
|
version = "0.3.4"
|
||||||
description = "small stdlib-based sync helpers: time/timezone deltas, dotted-path dict access, display masking, ip/address tooling, and retry/backoff"
|
description = "small stdlib-based sync helpers: time/timezone deltas, dotted-path dict access, display masking, ip/address tooling, and retry/backoff"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
|||||||
@@ -53,4 +53,4 @@ __all__ = [
|
|||||||
"aretry",
|
"aretry",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "0.3.3"
|
__version__ = "0.3.4"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
DISPLAY only, not a security control: the underlying value is unchanged and still
|
DISPLAY only, not a security control: the underlying value is unchanged and still
|
||||||
needs proper handling (encryption at rest, etc.).
|
needs proper handling (encryption at rest, etc.).
|
||||||
"""
|
"""
|
||||||
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
|
from urllib.parse import parse_qsl, quote_plus, urlencode, urlsplit, urlunsplit
|
||||||
|
|
||||||
SENSITIVE_QUERY_KEYS = frozenset(
|
SENSITIVE_QUERY_KEYS = frozenset(
|
||||||
{"apikey", "api_key", "key", "token", "access_token", "refresh_token",
|
{"apikey", "api_key", "key", "token", "access_token", "refresh_token",
|
||||||
@@ -69,7 +69,10 @@ def mask_url(url: str, *, keys: "frozenset[str] | None" = None) -> str:
|
|||||||
"""redact credentials in a url for logging: drop userinfo, ``***`` sensitive query values
|
"""redact credentials in a url for logging: drop userinfo, ``***`` sensitive query values
|
||||||
|
|
||||||
param-name matching is case-insensitive; ``keys`` overrides the default sensitive-name
|
param-name matching is case-insensitive; ``keys`` overrides the default sensitive-name
|
||||||
set. non-url or unparseable input is returned unchanged.
|
set. non-url or unparseable input is returned unchanged. non-sensitive query values are
|
||||||
|
re-percent-encoded on the way out (``quote_plus``), so a value containing a reserved
|
||||||
|
character (e.g. ``&``, ``=``, a space) round-trips correctly instead of corrupting the
|
||||||
|
rebuilt url.
|
||||||
"""
|
"""
|
||||||
sensitive = SENSITIVE_QUERY_KEYS if keys is None else frozenset(k.lower() for k in keys)
|
sensitive = SENSITIVE_QUERY_KEYS if keys is None else frozenset(k.lower() for k in keys)
|
||||||
try:
|
try:
|
||||||
@@ -86,7 +89,8 @@ def mask_url(url: str, *, keys: "frozenset[str] | None" = None) -> str:
|
|||||||
pairs = parse_qsl(query, keep_blank_values=True)
|
pairs = parse_qsl(query, keep_blank_values=True)
|
||||||
query = urlencode(
|
query = urlencode(
|
||||||
[(k, "***" if k.lower() in sensitive else v) for k, v in pairs],
|
[(k, "***" if k.lower() in sensitive else v) for k, v in pairs],
|
||||||
quote_via=lambda s, *_: s,
|
safe="*",
|
||||||
|
quote_via=quote_plus,
|
||||||
)
|
)
|
||||||
return urlunsplit((parts.scheme, netloc, parts.path, query, parts.fragment))
|
return urlunsplit((parts.scheme, netloc, parts.path, query, parts.fragment))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user