kaml has potential denial of service while parsing input with anchors and aliases
Description
kaml provides YAML support for kotlinx.serialization. Prior to version 0.53.0, applications that use kaml to parse untrusted input containing anchors and aliases may consume excessive memory and crash. Version 0.53.0 and later default to refusing to parse YAML documents containing anchors and aliases. There are no known workarounds.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
CVE-2023-28118 in kaml before 0.53.0 allows denial of service via YAML anchors/aliases (billion laughs attack); fixed by disabling them by default.
Vulnerability
Overview amaFlaws in kaml's YAML parsing, before version 0.53.0, allow a denial-of-service condition when processing crafted input containing YAML anchors and aliases. This attack is a variant of the "billion laughs" attack, where deeply nested or self-referential structures can cause exponential memory consumption, leading to application crashes [1][4].
Attack
Vector An attacker can supply a malicious YAML document that uses anchors and aliases to create exponential expansion during parsing. No authentication or special privileges are needed; the vulnerability is trivially exploitable by sending crafted data to any application that uses kaml to parse untrusted YAML content [1].
Impact
Successful exploitation results in excessive memory consumption, potentially causing the application to crash (denial of service). This can disrupt service availability without requiring any other compromise [1].
Mitigation
The fix was released in kaml version 0.53.0, which changes the default behavior to refuse parsing documents containing anchors and aliases [4]. Applications that require this feature can explicitly enable it via the YamlConfiguration.allowAnchorsAndAliases setting. Note that the kaml project is now archived and no longer maintained, so no further patches are expected [3].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
com.charleskorn.kaml:kamlMaven | < 0.53.0 | 0.53.0 |
Affected products
3- Range: <0.53.0
- charleskorn/kamlv5Range: < 0.53.0
Patches
15f82a2d7e00bDefault to not parsing anchors and aliases to prevent "billion laughs"-style attacks.
6 files changed · +57 −14
src/commonMain/kotlin/com/charleskorn/kaml/YamlConfiguration.kt+2 −0 modified@@ -33,6 +33,7 @@ package com.charleskorn.kaml * * [sequenceStyle]: how sequences (aka lists and arrays) should be formatted. See [SequenceStyle] for an example of each * * [ambiguousQuoteStyle]: how strings should be escaped when [singleLineStringStyle] is [SingleLineStringStyle.PlainExceptAmbiguous] and the value is ambiguous * * [sequenceBlockIndent]: number of spaces to use as indentation for sequences, if [sequenceStyle] set to [SequenceStyle.Block] + * * [allowAnchorsAndAliases]: set to true to allow anchors and aliases when decoding YAML (defaults to `false`) */ public data class YamlConfiguration constructor( internal val encodeDefaults: Boolean = true, @@ -47,6 +48,7 @@ public data class YamlConfiguration constructor( internal val multiLineStringStyle: MultiLineStringStyle = singleLineStringStyle.multiLineStringStyle, internal val ambiguousQuoteStyle: AmbiguousQuoteStyle = AmbiguousQuoteStyle.DoubleQuoted, internal val sequenceBlockIndent: Int = 0, + internal val allowAnchorsAndAliases: Boolean = false, ) public enum class PolymorphismStyle {
src/commonMain/kotlin/com/charleskorn/kaml/YamlException.kt+2 −0 modified@@ -98,3 +98,5 @@ public class NoAnchorForExtensionException( path: YamlPath, ) : YamlException("The key '$key' starts with the extension definition prefix '$extensionDefinitionPrefix' but does not define an anchor.", path) + +public class ForbiddenAnchorOrAliasException(message: String, path: YamlPath) : YamlException(message, path)
src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt+17 −2 modified@@ -1170,8 +1170,23 @@ class YamlReadingTest : DescribeSpec({ name: *name """.trimIndent() - context("parsing that input") { - val configuration = YamlConfiguration(extensionDefinitionPrefix = ".") + context("parsing anchors and aliases is disabled") { + val configuration = YamlConfiguration(extensionDefinitionPrefix = ".", allowAnchorsAndAliases = false) + val yaml = Yaml(configuration = configuration) + + it("throws an appropriate exception") { + val exception = shouldThrow<ForbiddenAnchorOrAliasException> { yaml.decodeFromString(SimpleStructure.serializer(), input) } + + exception.asClue { + it.message shouldBe "Parsing anchors and aliases is disabled." + it.line shouldBe 1 + it.column shouldBe 18 + } + } + } + + context("parsing anchors and aliases is enabled") { + val configuration = YamlConfiguration(extensionDefinitionPrefix = ".", allowAnchorsAndAliases = true) val yaml = Yaml(configuration = configuration) val result = yaml.decodeFromString(SimpleStructure.serializer(), input)
src/jvmMain/kotlin/com/charleskorn/kaml/Yaml.kt+1 −1 modified@@ -60,7 +60,7 @@ public actual class Yaml( private fun parseToYamlNodeFromReader(source: Reader): YamlNode { val parser = YamlParser(source) - val reader = YamlNodeReader(parser, configuration.extensionDefinitionPrefix) + val reader = YamlNodeReader(parser, configuration.extensionDefinitionPrefix, configuration.allowAnchorsAndAliases) val node = reader.read() parser.ensureEndOfStreamReached() return node
src/jvmMain/kotlin/com/charleskorn/kaml/YamlNodeReader.kt+9 −0 modified@@ -30,6 +30,7 @@ import java.util.Optional internal actual class YamlNodeReader( private val parser: YamlParser, private val extensionDefinitionPrefix: String? = null, + private val allowAnchorsAndAliases: Boolean = false, ) { private val aliases = mutableMapOf<Anchor, YamlNode>() @@ -43,6 +44,10 @@ internal actual class YamlNodeReader( if (event is NodeEvent) { event.anchor.ifPresent { + if (!allowAnchorsAndAliases) { + throw ForbiddenAnchorOrAliasException("Parsing anchors and aliases is disabled.", path) + } + aliases.put(it, node.withPath(YamlPath.forAliasDefinition(it.value, event.location))) } @@ -186,6 +191,10 @@ internal actual class YamlNodeReader( } private fun readAlias(event: AliasEvent, path: YamlPath): YamlNode { + if (!allowAnchorsAndAliases) { + throw ForbiddenAnchorOrAliasException("Parsing anchors and aliases is disabled.", path) + } + val anchor = event.anchor.get() val resolvedNode = aliases.getOrElse(anchor) {
src/jvmTest/kotlin/com/charleskorn/kaml/YamlNodeReaderTest.kt+26 −11 modified@@ -312,7 +312,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input") { val parser = YamlParser(input) - val result = YamlNodeReader(parser).read() + val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read() it("returns the expected list") { result shouldBe @@ -339,7 +339,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input") { val parser = YamlParser(input) - val result = YamlNodeReader(parser).read() + val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read() it("returns the expected list, using the most-recently defined value each time the alias is referenced") { result shouldBe @@ -363,11 +363,11 @@ class YamlNodeReaderTest : DescribeSpec({ - *thing """.trimIndent() - describe("parsing that input") { + describe("parsing that input with anchor and alias parsing enabled") { it("throws an appropriate exception") { val exception = shouldThrow<UnknownAnchorException> { val parser = YamlParser(input) - YamlNodeReader(parser).read() + YamlNodeReader(parser, allowAnchorsAndAliases = true).read() } exception.asClue { @@ -378,6 +378,21 @@ class YamlNodeReaderTest : DescribeSpec({ } } } + + describe("parsing that input with anchor and alias parsing disabled") { + it("throws an appropriate exception") { + val exception = shouldThrow<ForbiddenAnchorOrAliasException> { + val parser = YamlParser(input) + YamlNodeReader(parser, allowAnchorsAndAliases = false).read() + } + + exception.asClue { + it.message shouldBe "Parsing anchors and aliases is disabled." + it.line shouldBe 2 + it.column shouldBe 3 + } + } + } } context("given some input representing a list of strings in flow style") { @@ -652,7 +667,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input") { val parser = YamlParser(input) - val result = YamlNodeReader(parser).read() + val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read() val key1Path = YamlPath.root.withMapElementKey("key1", Location(1, 1)) val value1Path = key1Path.withMapElementValue(Location(1, 7)) val key2Path = YamlPath.root.withMapElementKey("key2", Location(2, 1)) @@ -1156,7 +1171,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input") { val parser = YamlParser(input) - val result = YamlNodeReader(parser).read() + val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read() val firstItemPath = YamlPath.root.withListEntry(0, Location(1, 3)) val firstXPath = firstItemPath.withMapElementKey("x", Location(1, 13)) @@ -1206,7 +1221,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input") { val parser = YamlParser(input) - val result = YamlNodeReader(parser).read() + val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read() val firstItemPath = YamlPath.root.withListEntry(0, Location(1, 3)) val firstXPath = firstItemPath.withMapElementKey("x", Location(1, 13)) @@ -1302,7 +1317,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input") { val parser = YamlParser(input) - val result = YamlNodeReader(parser).read() + val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read() val firstItemPath = YamlPath.root.withListEntry(0, Location(1, 3)) val firstXPath = firstItemPath.withMapElementKey("x", Location(1, 13)) @@ -1365,7 +1380,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input") { val parser = YamlParser(input) - val result = YamlNodeReader(parser).read() + val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read() val firstItemPath = YamlPath.root.withListEntry(0, Location(1, 3)) val firstXPath = firstItemPath.withMapElementKey("x", Location(1, 11)) @@ -1531,7 +1546,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input with an extension definition prefix defined") { val parser = YamlParser(input) - val result = YamlNodeReader(parser, extensionDefinitionPrefix = ".").read() + val result = YamlNodeReader(parser, extensionDefinitionPrefix = ".", allowAnchorsAndAliases = true).read() val fooKeyPath = YamlPath.root.withMapElementKey("foo", Location(3, 1)) val fooValuePath = fooKeyPath.withMapElementValue(Location(4, 5)) @@ -1598,7 +1613,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input with an extension definition prefix defined") { val parser = YamlParser(input) - val result = YamlNodeReader(parser, extensionDefinitionPrefix = ".").read() + val result = YamlNodeReader(parser, extensionDefinitionPrefix = ".", allowAnchorsAndAliases = true).read() val keyPath = YamlPath.root .withMerge(Location(4, 6))
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-c24f-2j3g-rg48ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-28118ghsaADVISORY
- github.com/charleskorn/kaml/commit/5f82a2d7e00bfc307afca05d1dc4d7c50593531aghsax_refsource_MISCWEB
- github.com/charleskorn/kaml/releases/tag/0.53.0ghsax_refsource_MISCWEB
- github.com/charleskorn/kaml/security/advisories/GHSA-c24f-2j3g-rg48ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.