# Ktor CWE-407 Scan — CLEAN **Date:** 2026-03-29 **Target:** Ktor framework (`~/git/ktor/`) **Language:** Kotlin **Version:** main branch (depth-1 clone) ## Scan Scope | Area | Files Checked | |------|--------------| | Server core | `ktor-server/ktor-server-core/common/src/io/ktor/server/routing/` (RoutingResolveContext, RouteSelector, HostsRoutingBuilder, RoutingBuilder), `response/ResponseHeaders.kt`, `engine/BaseApplicationResponse.kt` | | HTTP layer | `ktor-http/common/src/io/ktor/http/` (HttpHeaders, ContentTypes, HeaderValueWithParameters, HttpAuthHeader) | | Plugins (server) | `ktor-server-content-negotiation` (RequestConverter, ResponseConverter), `ktor-server-cors` (CORS.kt, CORSUtils.kt, CORSConfig.kt), `ktor-server-auth` (Authentication, AuthenticationInterceptors) | | Plugins (client) | `ktor-client-core/common/src/io/ktor/client/engine/HttpClientEngine.kt`, `ktor-client-plugins/ktor-client-auth/` | | Utilities | `ktor-utils/common/src/io/ktor/util/StringValues.kt`, `ktor-utils/common/src/io/ktor/util/CaseInsensitiveSet.kt` | | WebSockets | `ktor-shared/ktor-websockets/common/src/io/ktor/websocket/WebSocketExtension.kt` | ## Methodology Searched for Kotlin `List.contains()`, `Collection.contains()`, `.indexOf()`, `.indexOfFirst()`, and `.any { it == x }` calls nested inside per-request loops. Verified the backing type of each collection at the declaration site. ## Findings | Location | Pattern | Collection / Backing Type | Verdict | |----------|---------|--------------------------|---------| | `HttpHeaders.kt:139` | `UnsafeHeadersArray.any { it.equals(header) }` | `Array` of exactly 2 elements | O(2) = constant | | `HttpClientEngine.kt:188–190` | `for (ext in requiredCapabilities) { supportedCapabilities.contains(ext) }` | `supportedCapabilities: Set>` | O(1) | | `CORS.kt:66–78` | `hostsNormalized` and `hostsWithWildcard` lookups per request | Both `HashSet<>` | O(1) | | `CORSUtils.kt:104–105` | `requestHeaders.all { header in allHeadersSet }` | `allHeadersSet: Set` | O(1) per lookup | | `ContentTypes.kt:84–105` | `for (patternName in pattern.parameters) { parameter(patternName) }` — inner scan of `this.parameters` | `List`, Content-Type params bounded at 1-3 entries | O(P²) where P ≤ 3: effectively constant | | `HttpAuthHeader.kt:318` | `parameters.indexOfFirst { it.name == name }` | Called once per challenge construction, not in per-request loop | O(P) isolated | | `ResponseConverter.kt:53–55` | `acceptItems.flatMap { registrations.filter { it.contentType.match(contentType) } }` | O(A×R): A ≤ 5 Accept types, R ≤ 3 registrations | O(15) effectively constant | | `StringValues.kt` | Key lookup via `listForKey(name)` | Hash table (open-addressing with `hashBuckets`/`hashNext`) | O(1) | | `ResponseHeaders.kt:63` | `managedByEngineHeaders.contains(name)` | `Set` | O(1) | ## Notable Non-Defects - **`HttpHeaders.isUnsafe()`**: `UnsafeHeadersArray` is a 2-element compile-time constant array. Even though it is scanned linearly, the bound is fixed at 2 and will never grow with request load. - **`ContentType.match()`** (`ContentTypes.kt:84`): Outer loop over `pattern.parameters`, inner `parameter()` scans `this.parameters`. Both are Content-Type parameter lists, bounded in practice to 2-3 entries (e.g., `charset=utf-8`, `boundary=xxx`). - **`ResponseConverter.kt:53–55`**: `acceptItems.flatMap { registrations.filter { ... } }` is O(A×R). Both A (Accept header items) and R (content negotiation registrations) are tiny in all real deployments. The subsequent `.distinct()` uses Kotlin's `LinkedHashSet`-backed dedup. ## Result **CLEAN** — no CWE-407 defects confirmed in Ktor. The codebase uses hash-backed sets (`Set<>`, `HashSet<>`, `CaseInsensitiveSet`) for all membership checks in hot per-request paths. The few cases of linear list scan (`isUnsafe`, `parameter()`, `withReplacedParameter`) operate on collections bounded by a small protocol-defined constant. - CORSConfig.kt: CaseInsensitiveSet (Set impl) — O(1) - CallId.kt: dictionarySet: Set — O(1) Scan date: 2026-03-29