Enforce Android lifecycle and presentation security (#100)
Android ARM verification / rust (push) Failing after 47m6s
Android ARM verification / android (push) Failing after 8s
Dependency security audit / rustsec (push) Successful in 23s

This commit is contained in:
hugo committed 2026-10-09 18:59:18 +02:00
1 parent c0ba6d43d8
commit a06f39af23
37 files changed
+1129 -110

No files matched your search

+4 -3
View File
@@ -1609,9 +1609,10 @@ impl MobileAuthentication {
use crate::mobile_platform::MobileLifecycleEvent;
match event {
MobileLifecycleEvent::Foreground => {}
MobileLifecycleEvent::Background | MobileLifecycleEvent::DeviceLocked => {
self.manual_lock()?
}
MobileLifecycleEvent::Background
| MobileLifecycleEvent::DeviceLocked
| MobileLifecycleEvent::MemoryPressure
| MobileLifecycleEvent::ProcessRecreated => self.manual_lock()?,
MobileLifecycleEvent::UserActivity => {
if self.state()?.unlocked() {
self.touch_user_activity()?;
+2
View File
@@ -127,6 +127,8 @@ pub enum MobileLifecycleEvent {
Foreground,
Background,
DeviceLocked,
MemoryPressure,
ProcessRecreated,
UserActivity,
Cancel,
}
+8
View File
@@ -177,6 +177,14 @@ impl<B: ClipboardBackend> ClipboardManager<B> {
Ok(disposition)
}
/// Clear only the current copy, without waiting or retaining previous data.
pub fn clear_current_copy(
&mut self,
copied: &SecretBytes,
) -> Result<ClipboardDisposition, ClipboardError> {
self.cleanup(copied, &ClipboardContent::EmptyOrNonText)
}
fn cleanup(
&mut self,
copied: &SecretBytes,
@@ -85,6 +85,28 @@ fn mobile_context_uses_injected_os_backend_clock_and_lifecycle_without_global_co
authentication.lifecycle(MobileLifecycleEvent::Background)?;
assert!(!authentication.state()?.unlocked());
assert!(authentication.entry_editor(editor.id()).is_err());
for event in [
MobileLifecycleEvent::DeviceLocked,
MobileLifecycleEvent::MemoryPressure,
MobileLifecycleEvent::ProcessRecreated,
] {
authentication.unlock_entry(
"email/personal",
Some(SecretBytes::new(
fixture.key("alice")?.passphrase.as_bytes().to_vec(),
)),
)?;
let editor = authentication.begin_entry_editor("email/personal")?;
clock.advance(Duration::from_secs(5));
assert_eq!(
authentication
.lifecycle(MobileLifecycleEvent::Foreground)?
.remaining_seconds(),
25
);
assert!(!authentication.lifecycle(event)?.unlocked());
assert!(authentication.entry_editor(editor.id()).is_err());
}
let baseline_locks = backend.locks();
drop(authentication);
assert!(
+22
View File
@@ -165,6 +165,28 @@ fn clearing_clipboard_never_reads_or_restores_previous_secrets() -> TestResult {
Ok(())
}
#[test]
fn immediate_copy_revocation_clears_only_matching_content() -> TestResult {
let backend = MemoryClipboard::with_text(b"copied secret");
let mut manager = ClipboardManager::new(backend.clone(), ClipboardTimeout::default());
let secret = SecretBytes::new(b"copied secret".to_vec());
assert_eq!(
manager.clear_current_copy(&secret)?,
ClipboardDisposition::Cleared
);
assert_eq!(backend.text(), None);
backend.set_text(b"new user value");
assert_eq!(
manager.clear_current_copy(&secret)?,
ClipboardDisposition::PreservedNewer
);
assert_eq!(
backend.text().as_deref(),
Some(b"new user value".as_slice())
);
Ok(())
}
#[test]
fn clipboard_validation_and_cleanup_failures_are_typed_and_redacted() -> TestResult {
assert_eq!(