Mobile SDK security
This page is written for a security reviewer assessing the SentiOne Automate Mobile SDK (iOS and Android) before it is embedded in an application. It describes what the SDK and the platform do with credentials and data, and which protections are configuration decisions you own.
For the configuration steps themselves, see Adding the Mobile SDK Channel (iOS & Android); for the backend that terminates these connections, see WebChat.
Scope
The SDK is a soft client: a transport layer between the host application and the Automate backend. It contains no dialogue logic, and it opens no network destination other than that backend — no third-party analytics, telemetry, or push service.
Out of scope, and owned by the host application: the user interface, storage of any conversation history, background execution, crash reporting, analytics, and runtime permissions. The SDK provides none of these and requires none of them.
Authentication
Four independent mechanisms are involved. The first two identify the application, the third identifies the session, and the fourth — optional — identifies the user.
| Mechanism | Answers | Secret? |
|---|---|---|
| App Key | which channel and bot | yes — treat as a secret |
| Platform identity | which application binary | no — publicly derivable |
| Session token | which session | yes — server-minted |
| Client access token | which end user | yes — issued by your identity provider |
App Key
Generated by Automate when the channel is created, and sent on every call. It identifies the channel, and through it the platform, the registered application identity, and the bot.
Treat it as a secret. It is a credential, not a public identifier: 32 random characters from a cryptographic generator, unique to one channel, revocable, published nowhere, and masked like a password in Automate's own interface. Handle it as you would any API credential — inject it at build time, keep it out of source control, logs, tickets and chat, and rotate the channel if it is exposed. Keys are also environment-bound, so one issued on a given Automate environment is rejected by any other.
A secret that ships inside an app cannot be as confidential as a server-side oneBecause the key is embedded in the application binary, a determined attacker who has the app can extract it. That is a property of every credential shipped in a distributed client, and no handling discipline removes it.
The platform is therefore designed not to depend on the key's confidentiality alone. The platform identity check below binds the key to one specific signed build, so an extracted key cannot be used from another application; and where the identity of the end user matters, external JWT is the control that establishes it. Guard the key as a secret, and do not treat possession of it as proof of anything on its own.
Platform identity
On the initial handshake the SDK reports the identity of the running application, and the backend compares it against the values registered on the channel. A mismatch is refused with UNAUTHORIZED_PLATFORM, and no session is created.
| Platform | Reported | Registered on the channel |
|---|---|---|
| Android | Package name and the SHA-256 fingerprint of the signing certificate | Package Name, SHA-256 certificate fingerprint |
| iOS | Bundle identifier and Apple Developer Team ID | Bundle ID, Apple Team ID |
This is an anti-abuse binding, not a secret: an APK's signing fingerprint is derivable from any copy of the app. Its purpose is to stop a leaked App Key being used from a different or re-signed application.
Two consequences worth planning for:
- A build signed with a different certificate — a debug build, or an app re-signed by Google Play App Signing — presents a different fingerprint and is refused. Register the certificate that signs the app as delivered, and use a separate channel for development builds.
- On Android, an app with multiple signers or a rotated signing key has several fingerprints, and the SDK reports only the first one the platform returns. That is the one to register.
Session token
The handshake runs without a session token; on success the backend mints one, and the SDK attaches it to every subsequent call. Identity is therefore kept separate from session: the credential that proves who is calling is not the credential that identifies the conversation.
The token is held in memory for the session's lifetime and is not persisted by the SDK. A host application may store it to resume a conversation after a dropped connection — that is its own decision and implementation.
Session tokens are signed with the backend's application secretRotating the WebChat application secret invalidates every outstanding mobile session token, and all backend instances behind a load balancer must share the same secret. See WebChat.
Client access token — optional
A channel is configured in one of two authentication models:
- Anonymous. Any installation of the registered application may start a conversation, and no user credential is sent. Appropriate for a public assistant that does not need to know who is asking.
- External JWT. The application must present a token issued by your identity provider on every call. Appropriate when the bot serves signed-in users only, or when the flow needs to know who the user is.
For external-JWT channels the backend validates each token before allowing the call:
| Check | Behaviour |
|---|---|
| Signature | Verified against a key fetched from the channel's JWKS endpoint. Accepted algorithms: RS256, RS384, RS512, ES256, ES384, ES512. |
| Expiry | An exp claim is required; an expired token is refused. A small clock-skew tolerance is applied. |
| Audience | The token's aud must contain the value configured on the channel. |
| Principal | The configured user identifier claim must be present and non-empty. |
| Session ownership | A session may not be continued with a token belonging to a different principal. |
Each failure carries its own reason code — CLIENT_ACCESS_TOKEN_MISSING, _MALFORMED, _EXPIRED, _SIGNATURE_INVALID, _AUDIENCE_MISMATCH, PRINCIPAL_CLAIM_MISSING, PRINCIPAL_MISMATCH — so a rejection can be diagnosed without guesswork.
Two properties are worth calling out:
- Long-lived streams cannot outlive their credential. The event stream the SDK keeps open is bounded by the token's expiry and re-checked as it approaches. The application supplies a refreshed token in-band on the open stream, which extends its lifetime; if none arrives, the stream is terminated with
CLIENT_ACCESS_TOKEN_EXPIRED. - Mismatched models are rejected, not ignored. A token sent to an anonymous channel is refused with
CLIENT_ACCESS_TOKEN_NOT_SUPPORTED, and a missing token on an external-JWT channel withCLIENT_ACCESS_TOKEN_MISSING. Neither degrades silently.
The SDK never obtains, renews, caches, or persists this credential. It reads the current value from the host application before each call; refresh is entirely host-driven.
JWKS endpoints must be publicly reachableThe channel form rejects a JWKS URL whose host resolves to a loopback, private, or internal address, because the backend has to reach it. On-premise installations where the identity provider is internal can have this relaxed by an administrator — see Admin panel.
Transport security
All traffic runs over TLS on gRPC/HTTP2 to a single destination. The negotiated TLS version and cipher suite are bounded by the platform transport's defaults, with obsolete protocols excluded; the SDK does not lower that floor, so the effective protection also depends on your server's TLS configuration.
Certificate pinning is on by default for the SentiOne-hosted environments, pinning the connection to SPKI SHA-256 public-key hashes. An application may supply its own pins, which replace the built-in set. Where no pins exist for a host the connection still uses TLS validated against the platform trust store, and a pin mismatch fails the connection outright. On iOS the switch can only ever disable pinning, never force it on where the environment would not pin — useful for inspecting traffic through a debugging proxy, and deliberately not a way to weaken a production build into a false sense of assurance.
Mutual TLS is supported for endpoints behind a client-certificate proxy, as is a privately signed chain. The SDK never parses, stores, or logs key material. Note that a custom trust anchor will not match the built-in pins — pin the proxy instead, or disable pinning for that endpoint.
Cleartext is available only for local development. Both SDKs refuse a plaintext transport for the SentiOne-hosted environments, and reject the combination of mutual TLS with a cleartext connection outright, since a client certificate is meaningless without TLS.
SDK version control
The handshake checks the SDK version the application embeds, which gives the platform a remote kill-switch for builds that must no longer connect:
- below the configured minimum, the handshake is refused and the application cannot start a conversation;
- below the configured deprecation threshold, the handshake succeeds and is reported as deprecated, with a hint intended for developer logs.
Both thresholds are server-side configuration — see WebChat.
Data handling
What leaves the device
Everything the SDK transmits goes to the Automate backend over the single secured channel described above: the App Key, SDK version, platform and OS version, interface language, the platform identity (handshake only), the session token, the client access token where applicable, the user's messages and tapped quick replies, an optional display name, any host-supplied context data, and — only when the application uses speech — captured microphone audio and synthesis text.
What is stored on the device
Neither SDK writes any of it to device storage. There are no preference, file, or database writes; audio is streamed rather than written to disk. Credentials are not cached: the client access token is read from the host application per call, and the session token lives only in memory for the session.
Anything persisted on the device — conversation history, or the session token for later resume — is the host application's own decision, outside the SDK. On iOS the Keychain is the appropriate place for the latter.
Sensitive context data
Host-supplied context entries can be flagged as sensitive. Such entries are masked in the SDK's own logs before a log line is formed, and the flag travels with the entry to the backend. When a channel is configured to pass the external JWT into the bot flow, the platform marks that token as sensitive on the same mechanism rather than treating it as ordinary context data.
Consider carefully whether the flow genuinely needs the token's claims before enabling that option: leaving it off keeps them out of conversation data entirely.
Encryption at rest
Channel configuration that constitutes a credential is encrypted in the backend database rather than stored in the clear — the voice provider parameters and the handover configuration are both held encrypted.
Logging and telemetry
- No telemetry. Neither SDK embeds analytics or crash reporting, and neither emits data to any destination but the backend.
- Logging is off by default, and enabling it in a production environment raises a warning.
- Secrets are never logged, even with logging enabled. Session tokens, client access tokens and App Keys are recorded by presence — and at most length — never by value; message content and PII are excluded; sensitive context entries are masked; audio is summarised in aggregate rather than frame by frame.
Host application responsibilities
The SDK deliberately leaves the following to the integrator:
| Responsibility | Note |
|---|---|
| Obtaining and refreshing the client access token | The SDK reads the current value and never renews it |
| Persisting conversation history, or the session token for resume | The SDK stores nothing |
| Obfuscation of the shipped application | On Android the integrator's own R8 pass shrinks and obfuscates the application and SDK together |
| Background operation | Keeping the event stream alive while the app is minimised needs a host-side system service; the SDK uses no push mechanism |
| Crash reporting and analytics | None are embedded |
| Runtime permissions | Microphone access is requested by the host, and only needed for speech-to-text |
| Release validation | Building and testing the release configuration against the host's own setup |
Deployment considerations
Points that affect the security of the mobile channel but are settled on the server side, not in the app:
- TLS termination. The backend serves gRPC on the same port as its HTTP API and does not terminate TLS itself; termination happens at the ingress, which must pass HTTP/2 through.
- gRPC server reflection is enabled, which lets tooling enumerate the service without a copy of the contract. It exposes no data and no call bypasses authentication, but treat it as you would any service-description endpoint when deciding what to expose beyond the cluster.
- The application secret signs mobile session tokens, as noted above.
- JWKS reachability determines whether external-JWT channels can authenticate at all; if the endpoint is unreachable the backend reports
AUTH_PROVIDER_UNAVAILABLEand the call can be retried.
Related pages
- Adding the Mobile SDK Channel (iOS & Android) — channel configuration, field by field
- WebChat — the backend serving the gRPC API, and its configuration
- Security — platform-wide security features
- Vulnerabilities policy — how vulnerabilities in components and dependencies are handled
Updated about 24 hours ago
