refactor: simplify masking to dumb mask_url/mask_proxy + add stable_id
The selective-key URL/proxy machinery (_mask_qs, SENSITIVE_QUERY_KEYS, _split_host, _bullet_trailing_fields) tried to redact tokens inside arbitrary URL structure and leaked on shapes it didn't anticipate (SPA-route fragments, colon-passwords). Replace with purpose-honest functions that cannot leak or over-mask: - mask_url: strips query + fragment, keeps scheme/host/path. Does not parse or hunt for sensitive params - a secret in a URL is the caller's bug. - mask_proxy: partial user-reportable proxy id - drops any userinfo credentials and masks an IPv4 host's middle octets (A.***.***.D, port kept); non-IPv4 host passes through; no parseable host raises ValueError. - stable_id: deterministic sha256-derived id from ordered str parts (\x00-joined, regenerable); empty/non-str part or non-positive length raises ValueError. credit/cvv/phantom/provider unchanged. Export stable_id from the facade. Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
@@ -144,7 +144,7 @@ Display helpers only — they format a value for showing; they are **not a secur
|
||||
control** (the underlying value is unchanged and still needs proper handling).
|
||||
|
||||
```python
|
||||
from commons import credit, cvv, phantom, provider, mask_url, mask_proxy
|
||||
from commons import credit, cvv, phantom, provider, mask_url, mask_proxy, stable_id
|
||||
|
||||
credit("4111 1111 1111 1234") # "•••• •••• •••• 1234"
|
||||
cvv("123") # "•••"
|
||||
@@ -153,32 +153,35 @@ phantom("1234567890") # "••••••••••" (len <= 10
|
||||
provider("4111111111111111") # "VISA" (VISA/MC/AMEX/UPAY/DISC/JCB/DNRS/UNKW)
|
||||
provider("4111²111111111234") # "VISA" (unicode digit lookalikes ignored, never raises)
|
||||
|
||||
# redact credentials in a connection string before logging it
|
||||
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)
|
||||
# strip query + fragment off a url before logging it
|
||||
mask_url("https://u:pw@api.x/v2?apiKey=SECRET&ip=8.8.8.8") # -> "https://u:pw@api.x/v2"
|
||||
mask_url("https://api.x/v2") # -> "https://api.x/v2" (unchanged)
|
||||
|
||||
# partial, user-reportable proxy id: creds stripped, ipv4 middle octets masked
|
||||
mask_proxy("172.58.32.1:8080") # -> "172.***.***.1:8080"
|
||||
mask_proxy("user:supersecret@172.58.32.1:8080") # -> "172.***.***.1:8080" (creds gone)
|
||||
mask_proxy("proxy.host.net:8080") # -> "proxy.host.net:8080" (hostname untouched)
|
||||
|
||||
# deterministic, regenerable id from ordered parts
|
||||
stable_id("profit_lounge", "user_123") # -> "d1f4…" (same 16 hex chars every call)
|
||||
```
|
||||
|
||||
`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. 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_url` drops the query and fragment, keeping scheme/host/path. It does **not** parse
|
||||
or hunt for sensitive params — a secret in a URL is the caller's bug, not this function's
|
||||
to detect — so it structurally cannot leak a query param and cannot over-mask a legit one.
|
||||
A URL with no query/fragment is returned unchanged. (Note: `user:pass@` userinfo is part of
|
||||
the authority and is **not** stripped; keep credentials out of the URL you log.)
|
||||
|
||||
`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.
|
||||
`mask_proxy` yields a partial identifier a user can safely report for debugging. Any
|
||||
`user:pass@` userinfo (or a `scheme://` prefix) is dropped entirely, then an IPv4 host
|
||||
`A.B.C.D` is masked to `A.***.***.D` with the port kept in full; a non-IPv4 host (hostname
|
||||
or IPv6) passes through unchanged with only its credentials stripped. A spec with no
|
||||
parseable host raises `ValueError`.
|
||||
|
||||
`stable_id` derives a fixed-length hex id from ordered string parts via SHA-256 — same
|
||||
inputs always produce the same id, no state, so it's regenerable anywhere. Parts join with
|
||||
a `\x00` separator so `("ab","c")` and `("a","bc")` differ; an empty or non-str part, or a
|
||||
non-positive `length`, raises `ValueError`.
|
||||
|
||||
## retry
|
||||
|
||||
|
||||
Reference in New Issue
Block a user