fix(generator): honor "exclude similar characters" for guaranteed characters - #34
Open
TheStreamCode wants to merge 2 commits into
Open
fix(generator): honor "exclude similar characters" for guaranteed characters#34TheStreamCode wants to merge 2 commits into
TheStreamCode wants to merge 2 commits into
Conversation
…racters The password generator filters look-alike characters out of the pool it draws from, but the one character it guarantees per selected set was picked from the unfiltered alphabet. A generated password could therefore still contain i, l, 1, L, o, 0 or O right after the user excluded them; a digits-only password could be returned containing 0 or 1. Filter every alphabet up front and build the draw pool from the filtered sets, so the guarantee and the pool cannot disagree. Empty alphabets are dropped rather than sampled, and the guaranteed prefix is capped at the requested length so the function can no longer return more characters than asked for (reachable only through direct API use: the in-app slider starts at 8). Also document the accepted cryptographic trade-offs in docs/security.md under a new "Known Limitations" section - shared AES/HMAC key, PBKDF2-SHA1 backup KDF, six-digit master PIN keyspace, no unlock throttling, opt-in screenshot protection, transition-based auto-lock, Android 13+ clipboard previews and the trusted OTA channel. No cryptographic behavior changed and no stored data format was touched. Remove the unimported Divider component and the unused JSX.IntrinsicElements augmentation, and correct the nativewind-env.d.ts header: the className augmentations serve react-native-web, not NativeWind, which is not a dependency. Verified with bun run verify: format, typecheck, lint, 195 tests across 29 suites, expo-doctor 20/20. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The generator regression tests added in this branch take the suite from 189 to 195. Only the two README lines that describe current repository state are updated; docs/releases/*.md and docs/pre-build-security-ui-review.md record what was true at their respective releases and are left as the audit trail. 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.
Summary
The password generator filters look-alike characters out of the pool it draws from, but the one character it guarantees per selected set was picked from the unfiltered alphabet. A generated password could therefore still contain
i,l,1,L,o,0orOimmediately after the user excluded them.Worst case, reproduced in a test before the fix:
generatePassword(10, { includeNumbers: true, excludeSimilarCharacters: true })returned5555555515— the guaranteed digit was1, exactly what the option was meant to remove.The fix filters every alphabet up front and builds the draw pool from the filtered sets, so the guarantee and the pool can no longer disagree. Empty alphabets are dropped instead of sampled (
randomInt(0)would throw), and the guaranteed prefix is capped at the requested length so the function cannot return more characters than asked for. That second issue is only reachable through direct API use — the in-app slider starts at 8 and there are at most 4 sets.No cryptographic behavior changed and no stored data format was touched. Vault encryption, key derivation, storage keys and the
KS1/KS1-PW1payload layouts are untouched, so existing vaults and previously exported backups are unaffected.Security review notes
While reviewing the crypto I found trade-offs that are deliberately not changed here, because altering any of them would make existing vaults and exported backups undecryptable. They are now written down in
docs/security.mdunder a new "Known Limitations" section instead of being silently patched:KS1-PW1derives its key with PBKDF2 at 100k iterations using thecrypto-jsdefault (HMAC-SHA1), not Argon2id. Backups are the one artifact that leaves the device, so this is the weakest link in the current design.I attempted to suppress the Android 13+ clipboard preview via the ClipData "is sensitive" extra, but the
expo-clipboardbundled with Expo SDK 57 does not expose it throughSetStringOptions(neither the flat nor the platform-nested form typechecks). That attempt was reverted and replaced with a comment pointing at the documented limitation; it should be revisited on the nextexpo-clipboardupgrade.Cleanup
src/components/ui/divider.tsx— no importers anywhere in the app or the test suite, and its only styling was a leftover Tailwind class.JSX.IntrinsicElementsaugmentation declaring astyleelement asany; no JSX<style>element exists and it only weakened typing globally.nativewind-env.d.tsheader: theclassNameaugmentations exist for react-native-web CSS hooks (src/utils/webScrollFix.ts), not NativeWind or Tailwind, neither of which is a dependency..codex/to the ignored local agent-state directories and folded the redundant trailing.claude/settings.local.jsonrule into the.claude/entry that already covered it (verified both paths are still ignored).Tests
The generator previously had zero test coverage. Added six regression tests: character-set exclusion (both the multi-set and the single-set case), requested length, short-length behavior, empty option set, and alphabet containment. Three of them fail against the previous implementation and pass against this one — verified by reverting the source and re-running.
Validation
bun run verify— exit 0:prettier --check— all matched files use Prettier code styletsc --noEmit— cleaneslint .— cleanjest --ci --coverage --runInBand— 195 passed, 29 suites (was 189)expo-doctor— 20/20 checks passedbun audit --audit-level=critical(the CI gate) — exit 0, no critical advisories. The unfilteredbun run deps:auditreports 48 pre-existing advisories (33 high, 13 moderate, 2 low), all transitive through the Expo/React Native/Jest/ESLint toolchain (picomatch, js-yaml, lodash, minimatch, node-forge, babel). None affectcrypto-js,react-native-argon2,expo-secure-store,expo-clipboard,zodorasync-storage. Pre-existing and unchanged by this PR.Risks
Low. The only behavioral change is in password generation, and it makes the output match what the UI already promised. No migration, no format change, no version bump — the changes are recorded under
## [Unreleased]becauseapp.config.jsversionmirrors the published Play release by this project'"'"'s documented convention, and no release was cut here.Checklist
bun run verifygreengit ls-files keystore .secrets .env* '"'"'*.jks'"'"' ...returns only the non-secret.env.example🤖 Generated with Claude Code