kotlin-security
Secure-by-default patterns for Kotlin JVM and Android. Load for security, auth, injection, deserialization, WebView, content providers.
$ npx -y skills add notque/vexjoy-agent --agent claude-codeHow it fires
How this agent gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
Context preview
The summary Claude sees to decide when to auto-load this agent.
Secure-by-default patterns for Kotlin JVM and Android. Load for security, auth, injection, deserialization, WebView, content providers.
Agent definition
kotlin-security.mdKotlin Secure Implementation Patterns
Secure-by-default patterns for Kotlin JVM and Android. Load for security, auth, injection, deserialization, WebView, content providers.
---
Disable Jackson Default Typing
No default typing. Use explicit `@JsonTypeInfo` with closed subtype allowlist when polymorphic deser is needed.
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
// Correct: no default typing, Kotlin module for data class support
val mapper = ObjectMapper().registerKotlinModule()
// When polymorphic deserialization is needed, use sealed classes
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes(
JsonSubTypes.Type(Circle::class, name = "circle"),
JsonSubTypes.Type(Rectangle::class, name = "rectangle"),
)
sealed interface Shape
data class Circle(val radius: Double) : Shape
data class Rectangle(val width: Double, val height: Double) : Shape**Why**: `enableDefaultTyping()` lets attackers specify deserialized class. Known RCE gadget chains (C3P0, Spring). CVE-2021-44228, CVE-2022-22965.
**Detection**:
rg -n 'enableDefaultTyping|activateDefaultTyping|Id\.CLASS' . --type kotlin
rg -n 'JsonTypeInfo' . --type kotlin
---
Validate Android Intent Extras
Explicit intents for internal communication. Validate all extras from implicit intents/deep links.
// Correct: explicit intent for internal navigation
val intent = Intent(context, TargetActivity::class.java).apply {
putExtra("orderId", orderId)
}
startActivity(intent)
// Correct: validate extras from external sources
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Validate intent source when receiving from external apps
val orderId = intent.getStringExtra("orderId")
?: return finish() // Missing required extra
// Validate format before use
if (!orderId.matches(Regex("^[a-f0-9-]{36}$"))) {
return finish() // Invalid format
}
loadOrder(orderId)
}**Why**: Implicit intents deliver attacker-controlled data — unexpected types, missing values, malicious strings.
**Detection**:
rg -n 'getStringExtra|getIntExtra|getParcelableExtra' . --type kotlin
rg -n 'Intent\(.*ACTION' . --type kotlin
---
Configure WebView Security Defaults
Disable JS by default. Enable only for trusted content with URL allowlists.
import android.webkit.WebView
import android.webkit.WebViewClient
// Correct: secure WebView configuration
webView.apply {
settings.javaScriptEnabled = false // Default; enable only when needed
settings.allowFileAccess = false
settings.allowContentAccess = false
settings.domStorageEnabled = false
// Override URL loading to enforce allowlist
webViewClient = object : WebViewClient() {
private val allowedHosts = setOf("app.example.com", "help.example.com")
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
return request.url.host !in allowedHosts // Block navigation to unknown hosts
}
}
}
// When JavaScript IS needed for trusted content
webView.settings.javaScriptEnabled = true
// NEVER add JavaScript interfaces that expose sensitive operations
// webView.addJavascriptInterface(...) // Avoid unless strictly necessary**Why**: Unrestricted JS + navigation = arbitrary code execution. `addJavascriptInterface` exposes Kotlin methods. `allowFileAccess` enables local file reads.
**Detection**:
rg -n 'javaScriptEnabled\s*=\s*true|addJavascriptInterface' . --type kotlin
rg -n 'allowFileAccess\s*=\s*true|allowContentAccess\s*=\s*true' . --type kotlin
---
Prevent Content Provider Path Traversal
Validate paths with `canonicalFile` + `startsWith` containment check.
import android.content.ContentProvider
import android.os.ParcelFileDescriptor
import java.io.File
class SecureFileProvider : ContentProvider() {
private val baseDir by lazy {
File(context!!.filesDir, "shared").also { it.mkdirs() }
}
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
val requestedPath = uri.lastPathSegment
?: throw SecurityException("missing path")
// Resolve and verify containment
val target = File(baseDir, requestedPath).canonicalFile
if (!target.path.startsWith(baseDir.canonicalPath + File.separator)) {
throw SecurityException("path traversal attempt: $requestedPath")
}
if (!target.exists()) {
throw FileNotFoundException("file not found")
}
return ParcelFileDescriptor.open(target, ParcelFileDescriptor.MODE_READ_ONLY)
}
}**Why**: URI path segments with `../` escape the intended directory. `canonicalFile` resolves traversal; `startsWith` enforces containment.
**Detection**:
rg -n 'openFile|ContentProvider' . --type kotlin
rg -n 'canonicalFile|canonicalPath' . --type kotlin
---
Handle Coroutine Exceptions Without Swallowing Security Failures
Structured concurrency preserves exception propagation. Never silently catch security exceptions.
import kotlinx.coroutines.*
// Correct: structured concurrency preserves exception propagation
suspend fun processSecureRequest(request: Request): Response =
coroutineScope { // Cancels all children if any fails
val authResult = async { verifyAuth(request) }
val data = async { fetchData(request) }
// Auth failure propagates and cancels data fetch
val user = authResult.await()
val result = data.await()
Response(user, result)
}
// Correct: supervisor scope for independent operations with logging
suspend fun batchProcess(items: List<Item>) = supervisorScope {
items.map { item ->
async {
try {
processItem(item)
} catch (e: SecurityException) {Read more
Kotlin Secure Implementation Patterns
Secure-by-default patterns for Kotlin JVM and Android. Load for security, auth, injection, deserialization, WebView, content providers.
---
Disable Jackson Default Typing
No default typing. Use explicit `@JsonTypeInfo` with closed subtype allowlist when polymorphic deser is needed.
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
// Correct: no default typing, Kotlin module for data class support
val mapper = ObjectMapper().registerKotlinModule()
// When polymorphic deserialization is needed, use sealed classes
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes(
JsonSubTypes.Type(Circle::class, name = "circle"),
JsonSubTypes.Type(Rectangle::class, name = "rectangle"),
)
sealed interface Shape
data class Circle(val radius: Double) : Shape
data class Rectangle(val width: Double, val height: Double) : Shape**Why**: `enableDefaultTyping()` lets attackers specify deserialized class. Known RCE gadget chains (C3P0, Spring). CVE-2021-44228, CVE-2022-22965.
**Detection**:
rg -n 'enableDefaultTyping|activateDefaultTyping|Id\.CLASS' . --type kotlin rg -n 'JsonTypeInfo' . --type kotlin
---
Validate Android Intent Extras
Explicit intents for internal communication. Validate all extras from implicit intents/deep links.
// Correct: explicit intent for internal navigation
val intent = Intent(context, TargetActivity::class.java).apply {
putExtra("orderId", orderId)
}
startActivity(intent)
// Correct: validate extras from external sources
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Validate intent source when receiving from external apps
val orderId = intent.getStringExtra("orderId")
?: return finish() // Missing required extra
// Validate format before use
if (!orderId.matches(Regex("^[a-f0-9-]{36}$"))) {
return finish() // Invalid format
}
loadOrder(orderId)
}**Why**: Implicit intents deliver attacker-controlled data — unexpected types, missing values, malicious strings.
**Detection**:
rg -n 'getStringExtra|getIntExtra|getParcelableExtra' . --type kotlin rg -n 'Intent\(.*ACTION' . --type kotlin
---
Configure WebView Security Defaults
Disable JS by default. Enable only for trusted content with URL allowlists.
import android.webkit.WebView
import android.webkit.WebViewClient
// Correct: secure WebView configuration
webView.apply {
settings.javaScriptEnabled = false // Default; enable only when needed
settings.allowFileAccess = false
settings.allowContentAccess = false
settings.domStorageEnabled = false
// Override URL loading to enforce allowlist
webViewClient = object : WebViewClient() {
private val allowedHosts = setOf("app.example.com", "help.example.com")
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
return request.url.host !in allowedHosts // Block navigation to unknown hosts
}
}
}
// When JavaScript IS needed for trusted content
webView.settings.javaScriptEnabled = true
// NEVER add JavaScript interfaces that expose sensitive operations
// webView.addJavascriptInterface(...) // Avoid unless strictly necessary**Why**: Unrestricted JS + navigation = arbitrary code execution. `addJavascriptInterface` exposes Kotlin methods. `allowFileAccess` enables local file reads.
**Detection**:
rg -n 'javaScriptEnabled\s*=\s*true|addJavascriptInterface' . --type kotlin rg -n 'allowFileAccess\s*=\s*true|allowContentAccess\s*=\s*true' . --type kotlin
---
Prevent Content Provider Path Traversal
Validate paths with `canonicalFile` + `startsWith` containment check.
import android.content.ContentProvider
import android.os.ParcelFileDescriptor
import java.io.File
class SecureFileProvider : ContentProvider() {
private val baseDir by lazy {
File(context!!.filesDir, "shared").also { it.mkdirs() }
}
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
val requestedPath = uri.lastPathSegment
?: throw SecurityException("missing path")
// Resolve and verify containment
val target = File(baseDir, requestedPath).canonicalFile
if (!target.path.startsWith(baseDir.canonicalPath + File.separator)) {
throw SecurityException("path traversal attempt: $requestedPath")
}
if (!target.exists()) {
throw FileNotFoundException("file not found")
}
return ParcelFileDescriptor.open(target, ParcelFileDescriptor.MODE_READ_ONLY)
}
}**Why**: URI path segments with `../` escape the intended directory. `canonicalFile` resolves traversal; `startsWith` enforces containment.
**Detection**:
rg -n 'openFile|ContentProvider' . --type kotlin rg -n 'canonicalFile|canonicalPath' . --type kotlin
---
Handle Coroutine Exceptions Without Swallowing Security Failures
Structured concurrency preserves exception propagation. Never silently catch security exceptions.
import kotlinx.coroutines.*
// Correct: structured concurrency preserves exception propagation
suspend fun processSecureRequest(request: Request): Response =
coroutineScope { // Cancels all children if any fails
val authResult = async { verifyAuth(request) }
val data = async { fetchData(request) }
// Auth failure propagates and cancels data fetch
val user = authResult.await()
val result = data.await()
Response(user, result)
}
// Correct: supervisor scope for independent operations with logging
suspend fun batchProcess(items: List<Item>) = supervisorScope {
items.map { item ->
async {
try {
processItem(item)
} catch (e: SecurityException) {Essays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.
Repo: notque/vexjoy-agent
Other agents on vexjoy-agent.
- ansible-automation-engineer
Ansible automation: playbooks, roles, collections, Molecule testing, Vault security.
Open agent - modules
**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes **Version range**: ansible-core 2.14+ / Ansible Collections (community.general 7.0+) **Generated**: 2026-04-04 — verify against current Ansible
Open agent - testing
**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**: Molecule 6.0+ / ansible-lint 6.0+ / ansible-core 2.14+ **Generated**: 2026-04-04 — verify against current Molecule and ansible-lint documentation
Open agent - base-instructions
Universal operational rules injected by /do at agent dispatch. Domain-specific rules live in each agent's .md file.
Open agent - communication-patterns
**Scope**: Failure modes in agent output style — over-reporting, self-congratulation, verbose narration, and hedging. Covers what to detect and how to fix each. **Version range**: all versions **Generated**: 2026-05-11
Open agent - combat-effects-upgrade
Zero-dependency combat visual upgrades: CSS particle replacement, Framer Motion combat juice, CSS 3D card transforms.
Open agent

