Claude
Skills
Sign in
Back

kotlin-security

Included with Lifetime
$97 forever

# Kotlin Security Skill

Security

What this skill does

# Kotlin Security Skill

> **USE WHEN:** Securing Kotlin applications (backend/Android), reviewing code for vulnerabilities, or implementing security best practices.
> **DO NOT USE FOR:** Code quality issues (use kotlin-quality), general Kotlin patterns, UI/UX concerns.

## OWASP Top 10 for Kotlin

### A01: Broken Access Control

```kotlin
// Bad: No authorization check
@GetMapping("/orders/{id}")
fun getOrder(@PathVariable id: Long): Order {
    return orderRepository.findById(id).orElseThrow()
}

// Good: Ownership verification
@GetMapping("/orders/{id}")
fun getOrder(@PathVariable id: Long, @AuthenticationPrincipal user: UserDetails): Order {
    val order = orderRepository.findById(id).orElseThrow { NotFoundException() }

    if (order.userId != user.id && !user.hasRole("ADMIN")) {
        throw AccessDeniedException("Cannot access this order")
    }

    return order
}

// Good: Spring Security method security
@PreAuthorize("hasRole('ADMIN') or @orderSecurity.isOwner(#id, principal)")
@GetMapping("/orders/{id}")
fun getOrder(@PathVariable id: Long): Order {
    return orderRepository.findById(id).orElseThrow()
}

// Good: Repository-level filtering
interface OrderRepository : JpaRepository<Order, Long> {
    fun findByIdAndUserId(id: Long, userId: Long): Order?
}
```

### A03: Injection Prevention

```kotlin
// Bad: String concatenation in queries
fun findUser(name: String): List<User> {
    return entityManager
        .createQuery("SELECT u FROM User u WHERE u.name = '$name'")
        .resultList as List<User>
}

// Good: Parameterized queries
fun findUser(name: String): List<User> {
    return entityManager
        .createQuery("SELECT u FROM User u WHERE u.name = :name", User::class.java)
        .setParameter("name", name)
        .resultList
}

// Good: Spring Data JPA
interface UserRepository : JpaRepository<User, Long> {
    fun findByEmail(email: String): User?

    @Query("SELECT u FROM User u WHERE u.status = :status")
    fun findByStatus(@Param("status") status: UserStatus): List<User>
}

// Bad: Command injection
fun runCommand(userInput: String) {
    Runtime.getRuntime().exec("ls $userInput")
}

// Good: Avoid shell, use ProcessBuilder with array
fun listDirectory(directory: Path): List<String> {
    require(directory.isAbsolute && directory.exists()) { "Invalid directory" }

    return ProcessBuilder("ls", "-la", directory.toString())
        .redirectErrorStream(true)
        .start()
        .inputStream.bufferedReader().readLines()
}
```

### A04: Cryptographic Failures

```kotlin
// Bad: Weak hashing
val hash = MessageDigest.getInstance("MD5").digest(password.toByteArray())

// Good: BCrypt or Argon2
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder

val encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
val hash = encoder.encode(password)
val valid = encoder.matches(inputPassword, hash)

// Good: Secure random
import java.security.SecureRandom

val secureRandom = SecureRandom()
val token = ByteArray(32).also { secureRandom.nextBytes(it) }
val tokenString = Base64.getUrlEncoder().encodeToString(token)

// Good: AES-GCM encryption
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec

class AesGcmEncryption(private val key: ByteArray) {
    private val cipher = Cipher.getInstance("AES/GCM/NoPadding")

    fun encrypt(plaintext: ByteArray): ByteArray {
        val iv = ByteArray(12).also { SecureRandom().nextBytes(it) }
        cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), GCMParameterSpec(128, iv))
        return iv + cipher.doFinal(plaintext)
    }

    fun decrypt(ciphertext: ByteArray): ByteArray {
        val iv = ciphertext.copyOfRange(0, 12)
        val encrypted = ciphertext.copyOfRange(12, ciphertext.size)
        cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), GCMParameterSpec(128, iv))
        return cipher.doFinal(encrypted)
    }
}
```

### A05: XSS Prevention (Kotlin/JS & Server Templates)

```kotlin
// Good: Thymeleaf auto-escaping (enabled by default)
// In template: th:text="${user.name}" - automatically escaped

// Manual escaping when needed
import org.springframework.web.util.HtmlUtils

val safeHtml = HtmlUtils.htmlEscape(userInput)

// CSP headers in Spring Security
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
    http.headers { headers ->
        headers.contentSecurityPolicy { csp ->
            csp.policyDirectives("default-src 'self'; script-src 'self'")
        }
    }
    return http.build()
}
```

### A07: Authentication Failures

```kotlin
// Good: Spring Security configuration
@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http
            .csrf { it.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) }
            .sessionManagement { session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            }
            .authorizeHttpRequests { auth ->
                auth
                    .requestMatchers("/api/public/**").permitAll()
                    .requestMatchers("/api/admin/**").hasRole("ADMIN")
                    .anyRequest().authenticated()
            }
            .oauth2ResourceServer { it.jwt() }

        return http.build()
    }

    @Bean
    fun passwordEncoder(): PasswordEncoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
}

// Good: Rate limiting
@Component
class RateLimitFilter(
    private val rateLimiter: RateLimiter
) : OncePerRequestFilter() {

    override fun doFilterInternal(
        request: HttpServletRequest,
        response: HttpServletResponse,
        filterChain: FilterChain
    ) {
        val clientIp = request.remoteAddr

        if (!rateLimiter.tryAcquire(clientIp)) {
            response.status = HttpStatus.TOO_MANY_REQUESTS.value()
            return
        }

        filterChain.doFilter(request, response)
    }
}
```

### A08: Software Integrity

```kotlin
// Good: Validate JWT signatures
@Bean
fun jwtDecoder(): JwtDecoder {
    val decoder = NimbusJwtDecoder.withPublicKey(publicKey).build()

    decoder.setJwtValidator(
        DelegatingOAuth2TokenValidator(
            JwtTimestampValidator(),
            JwtIssuerValidator(issuer),
            JwtClaimValidator<List<String>>("aud") { aud ->
                aud.contains(expectedAudience)
            }
        )
    )

    return decoder
}

// Good: Avoid Java serialization, use JSON
import kotlinx.serialization.json.Json
import kotlinx.serialization.Serializable

@Serializable
data class UserDTO(val id: Long, val name: String)

val json = Json { ignoreUnknownKeys = true }
val user = json.decodeFromString<UserDTO>(jsonString)
```

## Kotlin-Specific Security

### Null Safety for Security
```kotlin
// Good: Use null safety to prevent NPE-based bypasses
fun authenticate(token: String?): User {
    val validToken = token ?: throw UnauthorizedException("Token required")

    return tokenService.validate(validToken)
        ?: throw UnauthorizedException("Invalid token")
}

// Good: requireNotNull for security checks
fun processPayment(userId: Long?, amount: BigDecimal?) {
    val validUserId = requireNotNull(userId) { "User ID required" }
    val validAmount = requireNotNull(amount) { "Amount required" }
    require(validAmount > BigDecimal.ZERO) { "Amount must be positive" }

    // Process payment
}
```

### Immutability for Security
```kotlin
// Good: Immutable data prevents tampering
data class PaymentRequest(
    val orderId: Long,
    val amount: BigDecimal,
    val currency: Currency,
) {
    init {
        require(amount > BigDecimal.ZERO) { "Amount must be positive" }
    }
}

// Good: Defensive copying
class SecureConfig(permissions: Set<String>) {
    val permissions: Set<String> = permissions.toSet()  // Immutable copy
}
```

### Sealed Classes for Security States
```kotlin
sealed class 

Related in Security