fix: mask_proxy and mask_url no longer leak credentials in non-4-part specs or url fragments

Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
2026-07-03 19:08:39 -04:00
parent 33ade498da
commit 718c8a79b0
3 changed files with 98 additions and 26 deletions
+17 -5
View File
@@ -12,15 +12,15 @@ Small sync helpers shared across projects. Base is stdlib only — **no dependen
## Install
```
commons @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.3.4
commons @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.3.5
# 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.4
commons[addr] @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.3.5
```
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.
Drop the `@v0.3.4` suffix from the line above to install the latest unpinned.
Drop the `@v0.3.5` suffix from the line above to install the latest unpinned.
## timing
@@ -129,16 +129,28 @@ provider("4111²111111111234") # "VISA" (unicode digit lookalikes ignored, n
mask_url("https://u:pw@api.x/v2?apiKey=SECRET&ip=8.8.8.8")
# -> "https://api.x/v2?apiKey=***&ip=8.8.8.8" (userinfo dropped, secret query masked)
mask_url("redis://:pw@127.0.0.1:6379/0") # -> "redis://127.0.0.1:6379/0"
mask_url("https://x/cb#access_token=SECRET&token_type=bearer")
# -> "https://x/cb#access_token=***&token_type=bearer" (oauth implicit-grant fragment masked)
mask_proxy("1.2.3.4:8080:user:supersecret") # -> "1.2.3.4:8080:user:****"
mask_proxy("1.2.3.4:8080") # -> "1.2.3.4:8080" (no auth, untouched)
mask_proxy("user:supersecret@1.2.3.4:8080") # -> "1.2.3.4:8080" (userinfo shape, also masked)
```
`mask_url` strips `user:pass@` userinfo and replaces the values of sensitive query
params (`apiKey`, `token`, `password`, `secret`, …; override via `keys=`) with `***`.
Non-sensitive query values are re-percent-encoded on the way out, so a value with a
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.
the rebuilt URL. The URL fragment is masked the same way as the query when it is
genuinely `key=value&key=value` shaped (e.g. an OAuth implicit-grant callback) — a
fragment that only incidentally contains `=` (an SPA hash route like `#/page?x=1`) is
left untouched rather than risking a lossy rewrite of a non-secret value, and a plain
anchor (`#section`) always passes through unchanged.
`mask_proxy` bullets the password of a `host:port:user:password` spec, and a plain
`host:port` (no auth, including a bracketed IPv6 host) passes through unchanged. Any
other credential-bearing shape — `user:pass@host:port` userinfo, a `scheme://`-prefixed
URL, or a colon spec with more than 4 parts — is masked rather than ever returned
verbatim; it never logs a password in the clear.
## retry