Repository navigation
test(frontend): cover PII masking edge cases - #2177
Open
Nife-tanny wants to merge 2 commits into
Open
Nife-tanny wants to merge 2 commits into
Nife-tanny wants to merge 2 commits into
Conversation
Add table-driven tests for maskRecipient (email/phone/name) and maskAddress covering realistic values, short identifiers around each reveal threshold, Unicode (accented, CJK, RTL, astral, emoji ZWJ and skin-tone sequences, combining marks), already-masked input, malformed and wrong-type input, whitespace, mixed case and very long input. Fix the masking bugs the tests exposed: - index by code point so astral characters are never split into lone surrogate halves - maskAddress no longer reveals every character of a 10-char input - non-string input returns "***" instead of throwing Run the piiMask suite in the frontend CI job, which is currently a stub. Closes LabsCrypt#2146 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…edge-cases # Conflicts: # .github/workflows/ci.yml
This branch has not been deployed
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.
Summary
Adds deterministic, table-driven tests for
frontend/src/app/utils/piiMask.ts(maskRecipientforemail/phone/name, andmaskAddress). They cover success, short-identifier, Unicode, already-masked, failure/error-contract and boundary paths. Adds a CI step so the suite actually runs. The tests exposed three small masking bugs, which are fixed minimally (see Bug fixes).The existing 15 assertions are kept unchanged. Suite: 152 passing + 2
it.todo(previously 15).Closes #2146
Bug fixes in
piiMask.ts(minimal; ASCII output unchanged)value[0],slice(-1),.length), so astral characters were cut in half. For example,maskRecipient("😀", "name")returned"\uD83D***\uDE00", and"Bob😀"ended in a lone low surrogate. All four masking paths now split withArray.from(code points). A "character" is now a Unicode code point. Combining marks and ZWJ sequences count per code point, so output never contains a lone surrogate.maskAddressrevealed every character at the threshold. A 10-character input passed the< 10guard and came back asfirst 6 + "..." + last 4, which is all 10 characters. The guard is now<= 10, so at least one character is always hidden. Real 56-character StellarG…/C…addresses are unaffected.TypeError. Numbers, booleans, objects and symbols crashed some paths (e.g..indexOf/.slice is not a function) and returned inconsistent values on others (e.g.[]as phone →"****"). Both functions now return"***"for any non-string, the same as for empty/nullish input. I added short JSDoc so this never-throws contract is documented.Call sites:
maskRecipient/maskAddressare not imported anywhere infrontend/srcoutside this file and its test, so no runtime behavior changes. The 113 non-Unicode/non-wrong-type tests pass against both the original and the fixed implementation. That confirms ASCII output is byte-for-byte unchanged.Test cases by category
emailphonenamemaskAddressco.uk, subdomain, mixed case(415) 555-0199G…account,C…contract, lower case"", 1, 2, 3 chars; output fixed-width for any length ≥2***, >10 always hides ≥1 char; explicit 10/11 boundary𠮷, ZWJ family***,***@***never reveal more+xx...****34,****idempotentJ***e,*idempotent;***→*****@, whitespace-only,@,a@, no dot in domain, empty label, multiple@,@@null,undefined,0, number,NaN, booleans, object, arrays, symbol →"***", never throws; unknown field type →"***"Every Unicode case asserts both the exact output and that it contains no lone surrogate.
Not applicable:
piiMask.tshas no role or permission logic. The admin/operator gate for unmasking lives insrc/app/api/recipients/[id]/reveal/route.ts, which doesn't usepiiMask, so it's out of scope here.Follow-up (marked
it.todo, not changed here)Two existing assertions show every character of very short values. Only the length is hidden by the fixed-width
***:maskRecipient("Jo", "name")→"J***o"(also e.g."李娜"→"李***娜", a common name length in CJK)maskRecipient("a@b.com", "email")→"a***@b***.com"These are existing documented behavior, so I left them for maintainers to decide on. They're marked with
it.todoin the test file.CI
The
frontendjob inci.ymlis currently a stub (echo "Frontend lint, typecheck, tests, and build passed", since d077546). That means no frontend tests run in CI today. Restoring the whole job would turn CI red because of the pre-existing failures listed below. So this PR adds Node 22 setup,npm ci, and a step that runsnpx jest src/app/utils/piiMask.test.ts. That way these tests are enforced without blocking on unrelated breakage. The full job can replace this once the suite is green.Testing done
Run locally in
frontend/(Node 24,engines: >=22):npx jest src/app/utils/piiMask.test.ts×5, plus--randomizeand a run withTZ=Asia/Tokyo LC_ALL=ar_EG.UTF-8piiMask.tsnpx jest(full suite)main(main: 363 passed, the same 14 failed)npx prettier --checkon changed files (incl.ci.yml)npx eslinton changed filesnpm run typecheckmain, none in changed filesnpm run buildPre-existing failures on
main(unrelated, not touched)reveal.route.test.ts,StepFinalSignature.test.tsx,session.test.ts,useToastStore.test.tsimportvitest, which isn't installed.StepFinalSignature.onchain.test.tsxcan't resolve../../utils/soroban.soroban.networkSigner.test.tsxfails withNETWORK_PASSPHRASES is not defined.LoanRepaymentForm.payoff.test.tsxhas 10 failures.WalletProvider.tsx(NETWORK_PASSPHRASESundefined), duplicateextensionFeeinuseApi.ts, plus type errors in the test files above.npm run lint(prettier --check .): 8 unformatted files, e.g.useApi.ts,CollateralActionModal.tsx,e2e/accessibility.spec.ts.Checklist
CONTRIBUTING.mddocument.main)🤖 Generated with Claude Code