-
Notifications
You must be signed in to change notification settings - Fork 0
π‘οΈ Sentinel: [High] Fix plaintext password storage in AsyncStorage #54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -146,7 +146,19 @@ export default function PasswordScreen() { | |||||||||||||||||||||||||
| // Load on mount | ||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||
| (async () => { | ||||||||||||||||||||||||||
| const raw = await AsyncStorage.getItem(PASSWORDS_KEY); | ||||||||||||||||||||||||||
| let raw = null; | ||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||
| raw = await SecureStore.getItemAsync(PASSWORDS_KEY); | ||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||
| if (__DEV__) console.warn('Failed to read from SecureStore', e); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Fallback for legacy installs | ||||||||||||||||||||||||||
| if (!raw) { | ||||||||||||||||||||||||||
| raw = await AsyncStorage.getItem(PASSWORDS_KEY); | ||||||||||||||||||||||||||
| // If we found legacy data, we'll try to migrate it on next save | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (raw) setEntries(JSON.parse(raw)); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| if (raw) setEntries(JSON.parse(raw)); | |
| if (raw) { | |
| try { | |
| setEntries(JSON.parse(raw)); | |
| } catch (e) { | |
| if (__DEV__) console.warn('Invalid stored passwords data; clearing saved value', e); | |
| await SecureStore.deleteItemAsync(PASSWORDS_KEY).catch(() => {}); | |
| await AsyncStorage.removeItem(PASSWORDS_KEY).catch(() => {}); | |
| setEntries([]); | |
| Alert.alert('Errore', 'I dati salvati delle password non sono validi e sono stati reimpostati.'); | |
| } | |
| } |
Copilot
AI
Apr 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
persist updates React state (setEntries(next)) before confirming the SecureStore write succeeded. If SecureStore.setItemAsync throws (e.g., size limits), the UI will show the new/edited entries and callers (like saveModal) will still close the modal, but the data wonβt survive an app restart. Consider only updating state after a successful secure save, or have persist return a success boolean / throw so callers can keep the modal open and avoid presenting unsaved data as saved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The legacy AsyncStorage fallback loads plaintext passwords into memory but does not migrate them into SecureStore immediately, so the plaintext copy remains on disk until the user performs a save. To fully remediate the vulnerability for legacy users, migrate as soon as AsyncStorage data is detected: attempt
SecureStore.setItemAsyncwith the legacy value, and on successAsyncStorage.removeItem(PASSWORDS_KEY)(handling SecureStore failures gracefully).