|
1 | | -## [6.1.0](https://github.com/mcodex/react-native-sensitive-info/compare/v6.0.0...v6.1.0) (2026-04-28) |
2 | | - |
3 | | -### Added |
4 | | - |
5 | | -* **biometric availability:** New `biometryStatus` field on `SecurityAvailability` (`'available' | 'notEnrolled' | 'notAvailable' | 'lockedOut' | 'unknown'`) disambiguates *hardware missing*, *hardware present but no enrollment*, and *currently usable*. The legacy `biometry` boolean stays as a backward-compatible alias for `biometryStatus === 'available'`. Mapped natively from `LAError` codes on iOS and `BiometricManager.canAuthenticate` results on Android. |
6 | | -* **policy precheck:** New `canUseAccessControl(policy, levels?)` and `canUseAccessControlSync(policy, levels)` predict whether a given `AccessControl` policy will succeed on the current device. When a `SecurityAvailability` snapshot is supplied (the sync variant always requires one), they are a pure TS mapping with no native call; if `levels` is omitted from `canUseAccessControl`, it first fetches the current snapshot via `getSupportedSecurityLevels()`. |
7 | | -* **foreground auto-refresh:** `useSecurityAvailability({ refreshOnForeground: true })` subscribes to `AppState` and refetches on `active` transitions (debounced ~500 ms, unsubscribes on unmount). Covers the *user leaves to enroll a fingerprint and returns* flow without manual `refetch()`. |
8 | | -* **enrollment listener:** New `useBiometryStatusWatcher(onChange)` hook fires only on actual `BiometryStatus` transitions (not on every render or refetch). Lives in its own module for tree-shaking. |
9 | | - |
10 | | -All additions are non-breaking; apps reading only the `biometry` boolean continue to work unchanged. |
11 | | - |
12 | | -### Fixed |
13 | | - |
14 | | -* **ios:** `getItem` no longer triggers a second Face ID / Touch ID prompt for biometry-protected entries. The lazy re-encryption path that runs after a successful authenticated read used to call `SecItemUpdate` against the same Keychain item to refresh its key-version metadata; iOS treats that as a separate authorization gate, prompting the user a second time. Biometric items now skip the lazy refresh entirely and are upgraded only by an explicit `setItem` (full overwrite, single user-initiated write) or by `rotateKeys({ reEncryptEagerly: true })`. Non-biometric items continue to be upgraded silently. |
15 | | -* **android:** Same double-prompt regression on `getItem` for entries whose Keystore key was created with `setUserAuthenticationRequired(true)`. Lazy re-encryption inside `getItem` allocated a new key alias for the active version and `Cipher.init` on that fresh key required its own biometric authorization, surfacing as a second prompt right after the read. The lazy refresh now skips entries with `requiresAuthentication == true` (or any biometry-class access policy); explicit `setItem` and `rotateKeys({ reEncryptEagerly: true })` still upgrade them. |
16 | | -* **ios:** `setItem` no longer returns `errSecDuplicateItem` ("The specified item already exists in the keychain") when the caller toggles `iosSynchronizable` between writes or when iCloud Keychain restores an entry between our delete and add. The internal upsert helper now wipes prior entries with `kSecAttrSynchronizableAny` and absorbs the iCloud-restore race with a single bounded retry. Bundle ID + access group already scope the partition, so the overwrite never crosses an app or sharing boundary. |
17 | | - |
18 | | -### Docs |
19 | | - |
20 | | -* Clarify `SecurityAvailability.secureEnclave` semantics: it now documents the cross-platform behaviour (Secure Enclave on iOS / mirrors `strongBox` on Android) so consumers can gate "hardware-backed key" UX without branching on `Platform.OS`. |
21 | | -* Clarify that `canUseAccessControl(policy, levels?)` only skips the native call when a snapshot is supplied; if `levels` is omitted it fetches one via `getSupportedSecurityLevels()`. |
22 | | -* Update the Android `requiresBiometricAuth` doc comment so it matches the actual classification (`devicePasscode` entries are auth-gated via `entry.requiresAuthentication` and are skipped by the lazy refresh). |
23 | | - |
24 | | -## [6.0.0](https://github.com/mcodex/react-native-sensitive-info/compare/v6.0.0-rc.12...v6.0.0) (2026-04-28) |
25 | | - |
26 | | -First stable release of the Nitro-based v6 line. Promotes `6.0.0-rc.12` to GA with no API changes — the release notes below summarize everything new since the v5 line. |
| 1 | +## [6.1.0](https://github.com/mcodex/react-native-sensitive-info/compare/v6.0.0-rc.12...v6.1.0) (2026-04-28) |
27 | 2 |
|
28 | 3 | ### Features |
29 | 4 |
|
30 | | -* **rotation:** Add versioned key rotation via `rotateKeys()` and `getKeyVersion()` with lazy re-encryption on read. New `useKeyRotation` hook exposes the same flow declaratively. |
31 | | -* **security hardening:** Defense-in-depth pass — non-breaking, applied transparently to new writes and via lazy upgrade on rotation: |
32 | | - - HMAC-SHA256 integrity tag bound to every entry's metadata + ciphertext, surfaced on `StorageMetadata.integrityTag`. Tampering with SharedPreferences/Keychain attributes now raises `IntegrityViolationError` (`E_INTEGRITY_VIOLATION`) before any biometric prompt is shown. |
33 | | - - AES-GCM AAD on Android binds ciphertext to `service|key|v<version>`, defeating cross-entry swap attacks. |
34 | | - - `setUnlockedDeviceRequired(true)` on every Android Keystore key (API 28+), mirroring iOS's `kSecAttrAccessibleWhenUnlocked` semantics. |
35 | | - - Plaintext byte buffers are zeroized after use on both platforms. |
36 | | - - Constant-time HMAC comparison via `MessageDigest.isEqual` / manual `UInt8` XOR fold. |
37 | | - - Backwards compatible: entries written by earlier versions decode without verification and are upgraded on the next write or rotation. |
38 | | -* **errors:** New typed error classes (`SensitiveInfoError`, `NotFoundError`, `AuthenticationCanceledError`, `IntegrityViolationError`, `KeyInvalidatedError`, `RotationFailedError`) with `code` discriminants and `instanceof` predicates. Importable from the `react-native-sensitive-info/errors` subpath. |
39 | | -* **tree-shaking:** `"sideEffects": false` everywhere; the package now publishes three focused subpath entries (`.`, `/hooks`, `/errors`). The default export has been removed — import only the helpers you use. |
40 | | -* **nitro 0.35:** Regenerated against `nitrogen@0.35.5` and `react-native-nitro-modules@0.35.5`. |
41 | | -* **tooling:** Migrated linting/formatting from ESLint + Prettier to **Biome 2**. Single config at `biome.json`, faster CI runs. |
42 | | - |
43 | | -### Refactor (KISS · DRY · SRP) |
44 | | - |
45 | | -* Introduced `useAsyncQuery` (read-only hooks) and `useMutation` (mutation hooks) primitives. `useHasSecret`, `useSecretItem`, `useSecureOperation`, `useKeyRotation`, and `useSecureStorage` now compose the same lifecycle/abort/error-handling pipeline — no duplicated state machines. |
46 | | -* `useSecureStorage` shrunk from ~230 LOC to ~180 LOC and reuses the shared abort + auth-cancel semantics; behaviour is unchanged. |
47 | | -* Test fixtures consolidated in `src/__tests__/__mocks__/fixtures.ts` (`buildTestItem`, `buildTestMetadata`). |
48 | | -* Removed redundant re-exports from `src/internal/errors.ts`. |
49 | | - |
50 | | -### Breaking changes |
| 5 | +* add AccessControlCard and DiagnosticsCard components; remove unused components ([6701d84](https://github.com/mcodex/react-native-sensitive-info/commit/6701d84226b97cf587064c596f871e8395aa7250)) |
| 6 | +* implement integrity hardening for sensitive data storage ([60b2cf5](https://github.com/mcodex/react-native-sensitive-info/commit/60b2cf5c8520cb40f917a698f326a239861e5d10)) |
51 | 7 |
|
52 | | -* The default export is gone. Use named imports: `import { setItem } from 'react-native-sensitive-info'`. |
53 | | -* React hooks are no longer re-exported from the package root — import them from `react-native-sensitive-info/hooks`. |
54 | | - |
55 | | -### Notes |
56 | | - |
57 | | -* **iOS rotation** updates the Keychain metadata via `SecItemUpdate`, preserving the original access-control attributes while bumping `keyVersion`. |
58 | | -* **Android rotation** mints a fresh per-entry Keystore alias (`SensitiveInfo_<hash>_v<version>`) during lazy or eager re-encryption and deletes the stale alias after a successful rewrite. |
59 | | -* Version state lives in a non-secret registry (`SharedPreferences` on Android, `UserDefaults` on iOS). Delete the app's data to reset. |
| 8 | +### Bug Fixes |
60 | 9 |
|
| 10 | +* update module resolver alias to correctly map source directory for subpath imports ([106621e](https://github.com/mcodex/react-native-sensitive-info/commit/106621e8d4c0cba81987196054a971628b4a36d8)) |
61 | 11 | ## [6.0.0-rc.12](https://github.com/mcodex/react-native-sensitive-info/compare/v6.0.0-rc.11...v6.0.0-rc.12) (2025-12-16) |
62 | 12 |
|
63 | 13 | ### Features |
|
0 commit comments