Skip to content
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

Biometric prompt, Jailbreak / Root detection and screenshot prevention #8645

Open
wants to merge 28 commits into
base: main
Choose a base branch
from

Conversation

enahum
Copy link
Contributor

@enahum enahum commented Mar 6, 2025

Summary

Added a security manager class to control the new security features introduced on a per-server level in addition to the app level managed by EMM providers.

This features are:

  • Prompt for biometric authentication
  • Jailbreak / root detection
  • Screen capture prevention

These features will work on a per-server basis only if not set with a EMM provider.

In order to prevent screenshots on iOS (the way it works is detailed in the library PR) we are adding a nativeID to views on screens and components where shielded means that it should be protected and skip.shielded means that we won't be protecting this view (for screens like the ones in the login flow)

Related PR's:
Server: mattermost/mattermost#30411
Library: mattermost/react-native-emm#18

Ticket Link

https://mattermost.atlassian.net/browse/MM-63362
https://mattermost.atlassian.net/browse/MM-63363
https://mattermost.atlassian.net/browse/MM-63364

Checklist

  • Added or updated unit tests (required for all new features)
  • Includes text changes and localization file updates
  • Have tested against the 5 core themes to ensure consistency between them.
  • Have run E2E tests by adding label E2E iOS tests for PR.

Device Information

This PR was tested on:
Android 14 & 15 emulators
iPhone 16 Pro and iPad 13 Pro iOS 18

Screenshots

Will add if requested

Release Note

Added the ability to configure per server (enterprise and premium) prompting for biometric authentication, preventing the use of the app if the device is jailbroken / rooted and prevent screen captures

promises.push(promise);
} catch (error) {
logError('initializeSecurityManager', error);
continue;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this allow the app to bypass security restrictions because it faced some arbitrary issue in obtaining database or config?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in theory, yes.. but here is the deal.. you only get to this point when the app is capable of showing the home screen, this means that the database is there and the config too, but the code is being defensive and capable of adding the error in the logs in case that happens.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And in that case would I able to access the server?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, cause by default there is no restriction

logError(`Switch to Server with url ${serverUrl} not found`);
return;
}
if (server.lastActiveAt) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for allowing at least logging into the server from a jailbroken device? Can you please add a comment explaining the purpose of this check?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

server.lastActiveAt means that is an existing session on that server and is not a server that was logged out.

for example, if you have two servers in your app but logout from one of them and you want to connect to it again, it will return lastActiveAt as 0 meaning it does not hit this condition, if the server has an active session, then it will perform the necessary checks before allowing you to switch to it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.

/**
* Checks if the device is Jailbroken or Rooted.
*/
isDeviceJailbroken = async (server: string, siteName?: string) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the usage of isDeviceJailbroken in this PR that I can find do not pass siteName. So, whats the purpose? Can you add docs explaining it and how it affects the result of this function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, I'm fixing this.. this should have siteName set when connecting to a new server that has this setting enabled.

The reason to use siteName is to be able to go through the check without having to add the server security config into memory if you are unable to conform with the security policies before connecting. and the Site name is being used in the Alert box that is displayed in case of failure.


const locale = DEFAULT_LOCALE;
const translations = getTranslations(locale);
if (config?.JailbreakProtection || siteName) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know the reason behind this, but if JailbreakProtection is disabled, why do we still check for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above comment

return;
}

const isActive = appState === 'active';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - create constants for these states.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need, this are protected by typescript set by AppStateStatus which is defined as type AppStateStatus = "active" | "background" | "inactive" | "unknown" | "extension"

if (this.activeServer) {
const config = this.getServerConfig(this.activeServer);
if (config && config.Biometrics && isMainActivity()) {
const authExpired = this.backgroundSince > 0 && (Date.now() - this.backgroundSince) >= toMilliseconds({minutes: 5});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if the app is in background for >5 mins, I can still see the push notifications, respond to messages via notification quick replies and potentially even accept incoming calls, all without opening Mattermost, right? I don't know what action sends the app into 'active' state so I might be wrong. Worth checking and documenting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well you could most definitely reply to notifications without opening the app and this PR does not cover the case when sharing content through the share extensions...

But we won't be covering those cases, at least not now.. for the notifications though, the user can set the device to not show the notifications content or actions if not authenticated, but this in fact a user setting on their own device and not controlled by us, not sure if we can prompt for any sort of biometric authentication before sending a reply directly from the notification.

Joining a call on the other hand, you do need to open the app.

The active state is set when the app is in the foreground.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, far enough. As long as we know and communicate the limitations, its fine. This will anyways go through security team as well.

const messages = defineMessages({
not_secured_vendor_ios: {
id: 'mobile.managed.not_secured.ios.vendor',
defaultMessage: 'his device must be secured with biometrics or passcode to use {vendor}.\n\nGo to Settings > Face ID & Passcode.',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo - 'his device must be -> 'This device must be

@harshilsharma63
Copy link
Member

@enahum where can I grab an iOS build for this to try out locally? This is very exciting!

@enahum
Copy link
Contributor Author

enahum commented Mar 10, 2025

@enahum where can I grab an iOS build for this to try out locally? This is very exciting!

you have two options

  1. build it locally to be able to run it
  2. if your device is registered within the Mattermost org on the Apple portal, you can build the PR and install it

Copy link
Contributor

@larkox larkox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is only one change required (the one about the component overwriting the loading).

The rest are just potential improvements.

@@ -269,6 +270,7 @@ const ChannelBookmarkAddOrEdit = ({
edges={edges}
style={styles.content}
testID='channel_bookmark.screen'
nativeID={SecurityManager.getShieldScreenId(componentId)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems something we will need to add for every screen we create from now on. Is there any way to enforce it (by linting rule, for example)?

Or what about to add a wrapper directly on the screen builder like we have withDatabase and so on?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the thing is that not necessarily this is applied in the screens folder but it should be somewhere in the component tree of a screen, although the changes in the emm library have a fallback in case none is found.

Also, a HOC may not be what we want cause is not always the first "View" that has this id

Copy link
Member

@harshilsharma63 harshilsharma63 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than what @larkox mentioned and communicating the known limitations with security team and customers, it all looks great! Hopefully we can have something similar for webapp when an admin access system console, like ask for a PIN or something.

@yasserfaraazkhan yasserfaraazkhan added the Build Apps for PR Build the mobile app for iOS and Android to test label Mar 10, 2025
@enahum enahum requested a review from larkox March 10, 2025 13:16
@jwilander jwilander removed their request for review March 11, 2025 20:00
@jwilander jwilander removed the 2: Dev Review Requires review by a core commiter label Mar 11, 2025
@amyblais amyblais added this to the v2.27.0 milestone Mar 12, 2025
@yasserfaraazkhan yasserfaraazkhan added Build Apps for PR Build the mobile app for iOS and Android to test and removed Build Apps for PR Build the mobile app for iOS and Android to test labels Mar 12, 2025
@ambyjkl
Copy link

ambyjkl commented Mar 12, 2025

Mattermost mobile is absolutely broken beyond belief and super slow and sluggish on a flagship Android phone and I come here to see you are investing resources that would be better spent fixing the app into making rooted users', such as myself, life harder. Words cannot describe how disappointed I am.

@amyblais amyblais added the CherryPick/Approved Meant for the quality or patch release tracked in the milestone label Mar 13, 2025
@yasserfaraazkhan yasserfaraazkhan added Build Apps for PR Build the mobile app for iOS and Android to test and removed Build Apps for PR Build the mobile app for iOS and Android to test labels Mar 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3: QA Review Requires review by a QA tester Build Apps for PR Build the mobile app for iOS and Android to test CherryPick/Approved Meant for the quality or patch release tracked in the milestone release-note
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants