test(redaction): assert the coarse behaviour the source actually has
This asserted that _redact_sensitive recurses into a dict under a
sensitive key — redacting auth.token while leaving auth.type readable.
The source replaces the whole value the moment the KEY matches, so
redacted["config"]["auth"] is a string and indexing ["token"] into it
raises TypeError. Source and test arrived in the same commit, so this
was never drift: it was a disagreement nobody settled.
Settled in favour of the source. Fine-grained redaction has to know
which sub-keys carry a secret, which is a guess about the shape of data
nobody has inspected; matching on the key cannot be wrong that way. Real
configs here are {"auth": {"type": "bearer", "token": "${SOME_KEY}"}},
and the cost of guessing wrong is a credential in a log, which no later
fix undoes. The price is readability, and it is paid deliberately.
Adds a second assertion that the secret appears nowhere in the output by
any path, which is the property actually worth protecting.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -498,7 +498,24 @@ class TestRestApiExecutor:
|
|||||||
assert redacted["normal_field"] == "visible"
|
assert redacted["normal_field"] == "visible"
|
||||||
|
|
||||||
def test_redact_sensitive_nested(self):
|
def test_redact_sensitive_nested(self):
|
||||||
"""Test redaction in nested structures."""
|
"""A dict under a sensitive key is redacted whole, not recursed into.
|
||||||
|
|
||||||
|
This asserted fine-grained recursion — that `auth.token` was replaced
|
||||||
|
while `auth`'s other keys stayed readable — and had never passed. The
|
||||||
|
source redacts the entire value the moment the KEY matches, so
|
||||||
|
`redacted["config"]["auth"]` is the string, and indexing `["token"]`
|
||||||
|
into it raises TypeError.
|
||||||
|
|
||||||
|
Settled in favour of the source. Fine-grained redaction has to know
|
||||||
|
which sub-keys carry the secret, which is a guess about the shape of
|
||||||
|
data nobody has inspected; redacting on the key cannot be wrong that
|
||||||
|
way. Real configs here look like
|
||||||
|
{"auth": {"type": "bearer", "token": "${SOME_API_KEY}"}}, and the cost
|
||||||
|
of guessing wrong is a credential in a log, which no later fix undoes.
|
||||||
|
|
||||||
|
The price is readability: a reader learns that auth was present, not
|
||||||
|
that it was bearer. That is the trade being made deliberately.
|
||||||
|
"""
|
||||||
data = {
|
data = {
|
||||||
"config": {
|
"config": {
|
||||||
"database": "mydb",
|
"database": "mydb",
|
||||||
@@ -513,7 +530,10 @@ class TestRestApiExecutor:
|
|||||||
|
|
||||||
assert redacted["config"]["database"] == "mydb"
|
assert redacted["config"]["database"] == "mydb"
|
||||||
assert redacted["config"]["password"] == "***REDACTED***"
|
assert redacted["config"]["password"] == "***REDACTED***"
|
||||||
assert redacted["config"]["auth"]["token"] == "***REDACTED***"
|
# The whole sub-dict, not a recursed copy of it.
|
||||||
|
assert redacted["config"]["auth"] == "***REDACTED***"
|
||||||
|
# And the secret is nowhere in the output, by any path.
|
||||||
|
assert "bearer123" not in str(redacted)
|
||||||
|
|
||||||
# Helper Function Tests
|
# Helper Function Tests
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user