fix(config): close the last two redaction holes -- env() in headers, URL userinfo (#1207) - #306
Open
wshallwshall wants to merge 2 commits into
Open
fix(config): close the last two redaction holes -- env() in headers, URL userinfo (#1207)#306wshallwshall wants to merge 2 commits into
wshallwshall wants to merge 2 commits into
Conversation
…URL userinfo (#1207)
Both sit INSIDE surfaces the redactor already claimed to handle, which is why
neither was visible from the outside.
(b) The headers branch had no EnvRef arm, so an env() ref in a headers table came
back as the RAW object with its `default` intact -- not JSON-safe, and the default
is a FALLBACK SECRET by construction. The same env() on a top-level credential
correctly emits {"env": key} with the default dropped. A hole inside the one
container the header rule already covered.
THE DEFAULT IS NOW DROPPED FOR EVERY HEADER, not only credential-shaped ones.
The measured instance used X-Vendor-Thing, which matches no substring in the
name rule -- so gating the EnvRef arm on that rule would have left this exact
case open. A header sourced from env() is a credential by intent; nobody
env()-refs a Content-Type. The name heuristic is the wrong gate here and it is
precisely the gate that failed.
(c) url="https://user:SECRET@host" was returned verbatim by both serializers while
proxy_password on the SAME object masked -- safe in the typed field, disclosed in
the URL beside it. Only the password half of the userinfo is replaced: the user,
host and path survive, because an operator diagnosing a connection needs to see
which account and which host, and masking the whole URL destroys the view rather
than protecting it. A control test asserts a URL WITHOUT userinfo is left
byte-identical, since a masker that rewrites every URL would satisfy the leak
assertions while mangling ordinary configuration.
`proxy` is another parameter-to-setting rename, noticed in passing: the parameter
is `proxy`, the emitted setting is `proxy_url`. Same boundary with_signing crosses
(#1106), which is why the URL rule is a NAME set plus a suffix rule rather than a
suffix rule alone.
95 tests green across the redaction and connection-API suites.
Both found by the asvs-tracking-rework session's assessment of ASVS 15.3.1,
alongside the odbc_params disclosure fixed as #1206. With these closed, all three
surfaces holding that cell at partial are addressed and it is due a re-read -- by
that session, not me, since I authored all three fixes.
PROCESS NOTE AGAINST MYSELF: the code comments here cited #1207 BEFORE the number
was allocated. It happened to be next, so nothing collided -- but "happened to be
next" is exactly the reasoning alloc.ps1 exists to eliminate, and two sessions
doing it at once is the documented failure. Allocate, then write.
proxy -> proxy_url makes THREE measured instances of one shape: a factory takes a credential PARAMETER and emits it under a different SETTING name, while every redaction control operates on the setting name. private_key -> sign_private_key was #1106; proxy -> proxy_url is harmless only because the URL rule happens to cover it, which is luck rather than design. Neither existing guard covers the MAPPING. One reads parameter names, an abstraction level from where redaction operates -- that is how #1106 walked through it. The other reads emitted settings end to end and proves the OUTCOME per factory, not that the parameter arrived anywhere at all. Filed into this branch rather than its own, to avoid a fourth three-way conflict on docs/BACKLOG.md while the engine queue is serialized; it is the route-onward from the same finding as #1207. Raised by the asvs-tracking-rework session, filed before it was forgotten.
wshallwshall
enabled auto-merge (squash)
August 9, 2026 15:45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Both sit inside surfaces the redactor already claimed to handle, which is why neither was visible from outside.
(b)
env()ref in a headers tableThe headers branch had no
EnvRefarm, so anenv()ref came back as the raw object with itsdefaultintact — not JSON-safe, and the default is a fallback secret by construction. The sameenv()on a top-level credential correctly emits{"env": key}with the default dropped.The default is now dropped for every header, not only credential-shaped ones. The measured instance used
X-Vendor-Thing, which matches no substring in the name rule — so gating theEnvRefarm on that rule would have left this exact case open. A header sourced fromenv()is a credential by intent; nobodyenv()-refs aContent-Type. The name heuristic is the wrong gate here, and it is precisely the gate that failed.(c) Credential in URL userinfo
url="https://user:SECRET@host"was returned verbatim by both serializers whileproxy_passwordon the same object masked — safe in the typed field, disclosed in the URL beside it.Only the password half is replaced; the user, host and path survive. An operator diagnosing a connection needs to see which account and which host, and masking the whole URL would destroy the view rather than protect it. A control test asserts a URL without userinfo is left byte-identical — a masker that rewrote every URL would satisfy the leak assertions while silently mangling ordinary configuration.
Measured, both serializers
Noticed in passing
proxyis another parameter-to-setting rename: the factory parameter isproxy, the emitted setting isproxy_url. Same boundarywith_signingcrosses (private_keytosign_private_key, #1106) — which is why the URL rule is a name set plus a suffix rule rather than a suffix rule alone.95 tests green across the redaction and connection-API suites.
Provenance and what it unblocks
Both found by the parallel
asvs-tracking-reworksession's independent assessment of ASVS 15.3.1, alongside theodbc_paramsdisclosure fixed as #1206.With these closed, all three surfaces holding that cell at
partialare addressed and it is due a re-read — by that session, not by me, since I authored all three fixes.Process note against myself
The code comments here cited
#1207before the number was allocated. It happened to be next, so nothing collided — but "happened to be next" is exactly the reasoningalloc.ps1exists to eliminate, and two sessions doing it simultaneously is the documented failure mode. Allocate, then write.