fix(secrets): mask underscore-adjacent credential key names - #647
Merged
plind-junior merged 4 commits intoJul 30, 2026
Merged
Conversation
_ASSIGNMENT wrapped its keyword alternation in \b...\b, but \b treats _ as a word character, so \btoken\b never matched token inside access_token, and likewise for secret inside client_secret / AWS_SECRET_ACCESS_KEY and password inside DB_PASSWORD. snake_case and SCREAMING_SNAKE_CASE are the dominant real-world shape for these env-vars (.env files, shell export, docker-compose), so this excluded most actual credentials from masking — only a bare token=/secret=/ password= with no prefix or suffix was ever caught. replace the \b boundaries with explicit alphanumeric lookaround so underscore-delimited segments match while true false positives like tokenized= or passwordless= stay excluded. this regex backs both the capture-time guard (a secret that reaches the buffer is permanent) and lifecycle.redact(), the manual remediation backstop for a credential that already reached a durable claim — both were silently missing the common case. Fixes vouchdev#646
two prs independently added a test of the same name asserting the same invariant (archived followup pages excluded from digest) with different fixtures. python silently shadows the first with the second, and ruff's F811 flags it as dead code — this fails ruff check src tests for every pr into test right now, unrelated to what that pr actually changes. rename the first (relative-dates, multi-page fixture) instead of deleting it, so both scenarios keep running.
Contributor
Author
|
added a second, unrelated commit: |
…derscore-boundary # Conflicts: # CHANGELOG.md
…derscore-boundary
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.
What changed
_ASSIGNMENTinsrc/vouch/secrets.pynow delimits its credential-keywordalternation with alphanumeric lookaround (
(?<![A-Za-z0-9])...(?![A-Za-z0-9]))instead of
\b...\b, so it matches keywords inside underscore-delimitednames like
access_token,client_secret,DB_PASSWORD, andAWS_SECRET_ACCESS_KEY.Why
\btreats_as a word character, so\btoken\bnever matchestokeninside
access_token— the_↔ttransition is\w→\w, never aboundary. Same for
secretinsideclient_secretandpasswordinsideDB_PASSWORD. Since snake_case / SCREAMING_SNAKE_CASE is the dominantreal-world shape for credential env-vars (
.envfiles, shellexport,docker-compose), this excluded the majority of actual credentials from
masking — only a bare
token=/secret=/password=with no prefix orsuffix was ever caught.
This regex backs two live call sites: the capture-time guard (a secret
that reaches the buffer is permanent, per the module's own docstring) and
lifecycle.redact(), the manual remediation backstop for a credentialthat already reached a durable claim. Both were silently missing the
common case.
Fixes #646
What might break
Nothing for users with an existing
.vouch/directory — no on-disk shape,kb.*method, or object model change. Behaviorally,mask_secrets()nowredacts more inputs than before (strictly a superset): any text that
previously matched a bare keyword (
token=,password=, etc.) stillmatches identically; text with a snake_case-prefixed/suffixed keyword
(
access_token=,DB_PASSWORD=) now also matches, where it previouslypassed through untouched. No previously-masked case becomes unmasked.
VEP
Not applicable — no object model,
kb.*method, on-disk layout, bundleformat, or audit-log shape change. A regex-correctness fix inside a single
pure string function.
Tests
make check-equivalent locally: ruff clean; mypy clean onsecrets.py; all 19tests/test_secrets.pycases pass (17pre-existing + 2 new). Also ran the directly-affected
test_capture.py,test_capture_answer.py,test_capture_scope.py,test_cli_lifecycle_surface.py, andtest_lifecycle.pysuites —one pre-existing failure (
test_finalize_supersedes_updated_claims)confirmed present identically on
testHEAD before this change(via
git stashcomparison), unrelated tosecrets.pytest_masks_underscore_adjacent_key_names,test_underscore_boundary_change_has_no_new_false_positiveCHANGELOG.mdupdated under## [Unreleased]