Skip to content

kotlin-security-testing

```kotlin // BAD -- hardcoded secret val jwtSecret = "super-secret-key-do-not-share"

From plugin
vexjoy-agent
413198 skills198 agents10 commands86 hooks
Install
$ npx -y skills add notque/vexjoy-agent --agent claude-code

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

```kotlin // BAD -- hardcoded secret val jwtSecret = "super-secret-key-do-not-share"

Agent definition

kotlin-security-testing.md

Kotlin Security & Testing Reference

Security

Secrets via Environment Variables

// BAD -- hardcoded secret
val jwtSecret = "super-secret-key-do-not-share"

// GOOD -- environment variable with fail-fast on missing
val jwtSecret: String = System.getenv("JWT_SECRET")
    ?: throw IllegalStateException("JWT_SECRET environment variable must be set")

// GOOD -- using requireNotNull for cleaner message
val dbPassword: String = requireNotNull(System.getenv("DB_PASSWORD")) {
    "DB_PASSWORD environment variable must be set before starting the application"
}

Exposed DSL -- Parameterized Queries Only

// BAD -- SQL injection via string interpolation
fun findByEmail(email: String): AppUser? {
    return transaction {
        exec("SELECT * FROM users WHERE email = '\$email'") { rs -> parseRow(rs) } // NEVER
    }
}

// GOOD -- Exposed DSL uses parameterized queries automatically
fun findByEmail(email: String): AppUser? = transaction {
    Users.select { Users.email eq email }
        .singleOrNull()
        ?.let { row -> Users.toAppUser(row) }
}

// GOOD -- if raw SQL is necessary, use explicit parameters
fun findByDomain(domain: String): List<AppUser> = transaction {
    exec("SELECT * FROM users WHERE email LIKE ?", listOf(stringParam("%@\$domain"))) { rs ->
        generateSequence { if (rs.next()) toAppUser(rs) else null }.toList()
    }
}

Ktor JWT Authentication

fun Application.configureSecurity() {
    val secret = requireNotNull(System.getenv("JWT_SECRET")) { "JWT_SECRET must be set" }
    val issuer = requireNotNull(System.getenv("JWT_ISSUER")) { "JWT_ISSUER must be set" }
    val audience = requireNotNull(System.getenv("JWT_AUDIENCE")) { "JWT_AUDIENCE must be set" }

    authentication {
        jwt("auth-jwt") {
            realm = "MyApp"
            verifier(
                JWT.require(Algorithm.HMAC256(secret))
                    .withIssuer(issuer)
                    .withAudience(audience)
                    .build()
            )
            validate { credential ->
                // Validate audience, issuer, AND subject -- all three required
                if (credential.payload.audience.contains(audience) &&
                    credential.payload.issuer == issuer &&
                    credential.payload.subject != null
                ) {
                    JWTPrincipal(credential.payload)
                } else {
                    null // authentication fails
                }
            }
        }
    }
}

Null Safety as a Security Property

`!!` on externally-sourced data (HTTP params, DB results, env vars) is a security vulnerability — converts compile-time safety into runtime NPE triggerable by adversarial input. Treat as critical defect.

---

Pattern Corrections

| Pattern | Why It's Wrong | Detection | Fix | |---------|---------------|-----------|-----| | `!!` operator | Bypasses null safety; runtime NPE | `grep -rn '!!' src/` | Use `?.`, `?:`, `require()`, `checkNotNull()` | | Nested scope functions | Unreadable, hard to debug; `this` ambiguity | Code review | Extract intermediate `val`s; use single scope function per expression | | `var` when `val` works | Accidental mutation, harder to reason about | detekt: `VarCouldBeVal` | Declare `val`; use `copy()` for updates | | `MutableList`/`MutableMap` in signatures | Exposes mutation capability beyond intent | Code review | Use `List`/`Map`; return immutable copy if needed | | `GlobalScope.launch` | Uncancellable; leaks coroutines at shutdown | `grep -rn 'GlobalScope' src/` | Use `viewModelScope`, `lifecycleScope`, or explicit scope | | Blocking call without `Dispatchers.IO` | Starves coroutine thread pool; hangs | Code review | Wrap in `withContext(Dispatchers.IO) { ... }` | | Platform type passthrough | Silently nullable; NPE at arbitrary call site | Code review; detekt | Annotate at Java boundary or guard with `?:` | | String interpolation in SQL | SQL injection | grep for `exec(` with string interpolation | Use Exposed DSL or explicit `?` parameters | | Hardcoded secrets | Credential leak | grep for `password =` string literals | `System.getenv()` with `requireNotNull()` | | `else` on sealed `when` | Hides missing cases for new subtypes | Code review | Remove `else`; let compiler enforce exhaustiveness | | Java-style getters/setters | Verbose; ignores Kotlin property syntax | Code review | Use Kotlin properties directly |

---

Testing

Kotest Styles

Choose per context, keep consistent within a module:

// StringSpec -- simple, flat tests
class UserValidatorTest : StringSpec({
    "should reject email without @ symbol" {
        val validator = UserValidator()
        validator.validate("notanemail") shouldBe ValidationResult.Invalid("Invalid email format")
    }
})

// FunSpec -- grouping related tests
class OrderServiceTest : FunSpec({
    val mockRepo = mockk<OrderRepository>()
    val service = OrderService(mockRepo)

    test("create order persists to repository") {
        every { mockRepo.save(any()) } returns Unit
        service.createOrder(orderRequest)
        verify(exactly = 1) { mockRepo.save(any()) }
    }
})

// BehaviorSpec -- Given/When/Then for complex scenarios
class PaymentProcessorTest : BehaviorSpec({
    Given("a valid payment request") {
        val processor = PaymentProcessor(mockk())
        When("the card is authorized") {
            Then("the order status transitions to PAID") { }
        }
    }
})

MockK

// Mock and stub
val repo = mockk<AccountRepository>()
every { repo.findById(1L) } returns AppUser(id = UserId(1L), name = "Alice", email = "alice@example.com")
every { repo.findById(99L) } returns null

// Capture arguments
val slot = slot<AppUser>()
every { repo.save(capture(slot)) } returns Unit
service.createUser("Bob")
assertEquals("Bob", slot.captured.name)

// Verify interactions
verify(exactly = 1) { repo.save(any()) }
confirmVerified(repo)

// Suspend functio
Read more
Ships withvexjoy-agent

Essays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.

Get the whole plugin, auto-invoked