Kotlin earned its reputation on null safety: the type system distinguishes String from String? and forces you to handle absence, which erases the single most common source of JVM crashes. It is easy to let that safety bleed into a false sense of security. Null safety is not security. A Kotlin service compiles to JVM bytecode and inherits every JVM supply-chain CVE — Log4Shell, the Jackson deserialization gadget chains — and a Kotlin Android app inherits the entire mobile attack surface: exported components, WebView JavaScript bridges, and plaintext local storage. Kotlin has even shipped its own advisories, including CVE-2020-29582, where kotlin-stdlib's temporary-file helpers created world-readable files on Unix systems until the fix in 1.4.21. This guide covers the security work the type system does not do for you.
Does Kotlin's null safety make my code secure?
No — it removes a class of reliability bugs, not a class of security bugs. Injection, insecure deserialization, weak storage, and vulnerable dependencies are all fully reachable from perfectly null-safe Kotlin. Two Kotlin-specific advisories are worth knowing. CVE-2020-29582 in kotlin-stdlib gave world-readable permissions to files produced by createTempFile and createTempDir, exposing their contents to any local user until 1.4.21. CVE-2022-24329 covered Kotlin before 1.6.0 being unable to lock dependencies for Multiplatform Gradle projects, leaving resolved versions unpinned. Both are dependency-hygiene problems dressed as language features — which is the theme of everything below.
How should Kotlin Android apps handle WebView and storage?
These are the two places Android apps leak most. A WebView with JavaScript enabled and a native bridge is a direct route from web content into your app's objects:
// DANGEROUS -- exposes native methods to any loaded page
webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(NativeBridge(), "Android")
webView.loadUrl(untrustedUrl)
If you must bridge, only enable JavaScript for content you fully control, never call addJavascriptInterface on a WebView that loads third-party URLs, and prefer WebViewAssetLoader for bundled content. For storage, plaintext SharedPreferences is readable on a rooted or backed-up device:
// SAFE -- encrypted at rest, keys held in the Android Keystore
val prefs = EncryptedSharedPreferences.create(
context, "secure_prefs", masterKey,
PrefKeyEncryptionScheme.AES256_SIV,
PrefValueEncryptionScheme.AES256_GCM
)
Secrets and tokens belong in EncryptedSharedPreferences or the Android Keystore, never in plaintext preferences, log statements, or hardcoded string constants that survive in the compiled APK.
What about exported components and deserialization?
An Android component marked android:exported="true" — the required, explicit default for anything with an intent filter since API 31 — can be invoked by any app on the device. Treat every exported activity, service, and receiver as an untrusted entry point: validate the intent, check the caller, and never trust extras as if they came from your own code. On the serialization side, prefer kotlinx.serialization with explicit types over Java's Serializable and ObjectInputStream, which drag the full JVM gadget-chain risk into a Kotlin app. When you consume JSON through Jackson, keep it patched and leave default typing disabled — the jackson-databind deserialization CVEs, such as CVE-2019-12384, apply identically whether the calling code is Java or Kotlin.
How do I secure the Kotlin dependency supply chain?
Through the build tool. Gradle is where Kotlin projects resolve dozens of transitive artifacts, and Gradle has first-class controls that most teams leave off. Enable dependency locking and dependency verification so a compromised mirror cannot swap an artifact:
# Generate a lockfile and a checksum/signature verification file
./gradlew dependencies --write-locks
./gradlew --write-verification-metadata sha256,pgp help
Commit gradle.lockfile and verification-metadata.xml, pin the Kotlin and plugin versions explicitly, and fail the build when a resolved artifact's checksum does not match. This is the control CVE-2022-24329 was about — without locked, verified versions, the build trusts whatever a repository returns.
Kotlin security checklist
| Practice | Why it matters |
|---|---|
| Treat null safety as reliability, not security | Injection and deserialization are still reachable |
Never addJavascriptInterface on third-party WebView content | Bridges native code to attacker pages |
| Store secrets in EncryptedSharedPreferences/Keystore | Plaintext prefs are readable off-device |
| Validate every exported component's intent and caller | Exported components are untrusted entry points |
Prefer kotlinx.serialization over Java Serializable | Avoids JVM gadget-chain deserialization |
| Enable Gradle dependency locking + verification | Closes dependency-confusion and mirror-swap risk |
Keep kotlin-stdlib and Jackson patched | Fixes CVE-2020-29582 and databind CVEs |
How Safeguard Helps
Safeguard's software composition analysis resolves the full Gradle graph behind a Kotlin service or Android app — the transitive Jackson, kotlin-stdlib, and AndroidX artifacts included — and layers call-path reachability so a JVM CVE your code actually reaches is prioritized over one that never executes. Griffin, our AI analysis engine, reviews new dependency versions for the behavioral markers of a compromised release before your build consumes them, and auto-fix raises a tested pull request when a safe upgrade exists. For teams weighing options, the Safeguard vs Snyk comparison covers where the reachability model differs. Null safety, encrypted storage, and locked-down WebViews stay your responsibility; Safeguard secures the dependency tree underneath them.
Bring your Kotlin supply chain under control — start for free or read the documentation.
Frequently Asked Questions
Is Kotlin more secure than Java?
Kotlin removes whole categories of null-pointer and mutability bugs, which improves reliability, but it compiles to the same JVM bytecode and pulls from the same Maven ecosystem, so it inherits every JVM supply-chain and deserialization risk Java has. In security terms the two languages share an attack surface; Kotlin's advantage is ergonomic, not a security boundary.
Where should an Android app store authentication tokens?
In EncryptedSharedPreferences or the Android Keystore, which bind key material to hardware-backed storage where available. Plaintext SharedPreferences, local files, and hardcoded constants are all recoverable from a rooted device or a device backup, so none of them are acceptable for tokens or secrets.
Why is addJavascriptInterface dangerous?
It exposes native Kotlin and Java methods to whatever page the WebView loads. If that page is attacker-controlled, or loaded over plaintext HTTP that can be tampered with in transit, the attacker calls your native methods directly. Only bridge to content you fully control, and never combine addJavascriptInterface with loading arbitrary third-party URLs.
How do I lock Kotlin dependencies against tampering?
Use Gradle's dependency locking and dependency verification. Generate a gradle.lockfile to pin resolved versions and a verification-metadata.xml with SHA-256 checksums and PGP signatures, commit both, and fail the build on any mismatch. This closes the dependency-confusion gap that CVE-2022-24329 described.