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.

MechanismAnswersSecret?
App Keywhich channel and botyes — treat as a secret
Platform identitywhich application binaryno — publicly derivable
Session tokenwhich sessionyes — server-minted
Client access tokenwhich end useryes — 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 one

Because 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.

PlatformReportedRegistered on the channel
AndroidPackage name and the SHA-256 fingerprint of the signing certificatePackage Name, SHA-256 certificate fingerprint
iOSBundle identifier and Apple Developer Team IDBundle 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 secret

Rotating 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:

CheckBehaviour
SignatureVerified against a key fetched from the channel's JWKS endpoint. Accepted algorithms: RS256, RS384, RS512, ES256, ES384, ES512.
ExpiryAn exp claim is required; an expired token is refused. A small clock-skew tolerance is applied.
AudienceThe token's aud must contain the value configured on the channel.
PrincipalThe configured user identifier claim must be present and non-empty.
Session ownershipA 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 with CLIENT_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 reachable

The 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:

ResponsibilityNote
Obtaining and refreshing the client access tokenThe SDK reads the current value and never renews it
Persisting conversation history, or the session token for resumeThe SDK stores nothing
Obfuscation of the shipped applicationOn Android the integrator's own R8 pass shrinks and obfuscates the application and SDK together
Background operationKeeping the event stream alive while the app is minimised needs a host-side system service; the SDK uses no push mechanism
Crash reporting and analyticsNone are embedded
Runtime permissionsMicrophone access is requested by the host, and only needed for speech-to-text
Release validationBuilding 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_UNAVAILABLE and the call can be retried.

Related pages


Did this page help you?