Repository navigation
Withhold the body of a secret-bearing command when its length will not parse - #19
Merged
Merged
Conversation
…t parse An Lc larger than the bytes present left the data field unsplit, so redact_command's `if data and _is_sensitive(...)` guard was false and the command fell through to mask(), which only masks values passed to register(). An unregistered PIN therefore reached the transcript in the clear - in the file whose own header reads "APDU transcript (secrets redacted)". _parse_command now reports whether it located the data field, and redact_command fails closed for commands that can carry a secret: header plus the size of what was withheld, with no fabricated Lc. CTAP is withheld as a family because clientPIN is identified by the first data byte, which is what the bad length hides. Commands that cannot carry a secret still render in full so raw `apdu send` debugging keeps working, and the case-1 retry-counter probe is untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Withhold the body of a secret-bearing command when its length will not parse
An
Lclarger than the bytes present left the data field unsplit, and the command thentook the path that masks only registered values. An unregistered PIN reached the transcript
in the clear.
Changes
_parse_commandreports a seventh value,data_located, distinguishing "carries no data"from "the length field disagreed with the bytes present".
redact_commandfails closed on the second case for commands that can carry a secret:header plus the size of what was withheld, without inventing an
Lc._may_carry_secretdecides that from the header alone.tests/unit/test_redaction.py, four of them regressions.What leaked
Lcclaims six bytes and three follow. Before:0020008006313233, where313233is theASCII PIN
123. After:00200080<REDACTED:4B>. The same held for CHANGE REFERENCE DATA,for the extended-length form, and for CTAP
clientPIN.These land in the file written by
--apdu-log, whose own header readsAPDU transcript (secrets redacted).Constraints
if data and _is_sensitive(...)was the whole bug. With the length unparsed,dataisempty, so the guard is false and control falls to
mask(), which only masks values passed toRedactor.register. A PIN typed at the prompt is registered; one arriving another way is not.Command-aware redaction is the layer that is supposed to cover that gap, and this is the hole
in it.
CTAP fails closed as a family.
clientPINis identified by the first data byte, which isexactly what an unparseable length hides, so a CTAP message with a bad length is withheld even
if it was really
getInfo. Well-formed CTAP is unaffected and still distinguishes the two.Failing closed is scoped to commands that can carry a secret. A malformed GET DATA still
renders in full, so raw
apdu senddebugging keeps working. Two control tests hold that line,along with one for the case-1 retry-counter probe, which carries no data and must stay
readable since it is what the transcript is most often read for.
No
Lcis fabricated. The withheld form is header plus marker, deliberately differentfrom the well-formed
header + Lc + marker + trailer, because the real length is unknown.Verification
ruff check,ruff format --check,mypy,sphinx-build -W --keep-goingpass. The suitegoes 350 to 357. Before the fix the four regression tests fail and the three controls pass.
Coverage of
secrets/redaction.pyis 93%.🤖 Generated with Claude Code