This is an automated email from the ASF dual-hosted git repository. luigidemasi pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-kit.git
commit 909ac08593cc95400d5416ce72857a61d4c4834d Author: Andrea Cosentino <[email protected]> AuthorDate: Mon Nov 2 09:29:05 2026 +0100 Fix #139: correct SafeConstructor rationale and add global-tag regression tests Address review feedback: - Correct the rationale. SnakeYAML 2.3 (pinned) already rejects custom global tags by default via UnTrustedTagInspector, so the previous commit overstated the risk: new Yaml() does not permit arbitrary type instantiation here. SafeConstructor is defense in depth -- it also removes arbitrary object construction and makes the parsers' data-only intent explicit -- not a fix for an open vulnerability. - Add regression tests (VersionResolverTest, CveParserTest) asserting a globally tagged type is never constructed: release parsing rejects the YAML, CVE parsing falls back to line-based extraction, and a construction canary stays untouched. Co-authored-by: Claude Opus 4.8 <[email protected]> --- .../kit/knowledge/indexer/VersionResolver.java | 3 +- .../kit/knowledge/indexer/parser/CveParser.java | 3 +- .../kit/knowledge/indexer/VersionResolverTest.java | 25 +++++++++++++++++ .../knowledge/indexer/parser/CveParserTest.java | 32 ++++++++++++++++++++++ 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/knowledge/indexer/src/main/java/org/apache/camel/kit/knowledge/indexer/VersionResolver.java b/knowledge/indexer/src/main/java/org/apache/camel/kit/knowledge/indexer/VersionResolver.java index 2444eeb7..8522e82f 100644 --- a/knowledge/indexer/src/main/java/org/apache/camel/kit/knowledge/indexer/VersionResolver.java +++ b/knowledge/indexer/src/main/java/org/apache/camel/kit/knowledge/indexer/VersionResolver.java @@ -94,7 +94,8 @@ public class VersionResolver { } List<CamelRelease> releases = new ArrayList<>(); - // Frontmatter is plain data (maps/lists/scalars); SafeConstructor refuses arbitrary type tags. + // Frontmatter is plain data. SnakeYAML 2.x already rejects custom global tags by default; + // SafeConstructor also strips arbitrary-object construction from this parser (defense in depth). Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions())); // Only top-level release files (not subdirectories like q/, sb/, etc.) diff --git a/knowledge/indexer/src/main/java/org/apache/camel/kit/knowledge/indexer/parser/CveParser.java b/knowledge/indexer/src/main/java/org/apache/camel/kit/knowledge/indexer/parser/CveParser.java index fd7e493f..c52adda2 100644 --- a/knowledge/indexer/src/main/java/org/apache/camel/kit/knowledge/indexer/parser/CveParser.java +++ b/knowledge/indexer/src/main/java/org/apache/camel/kit/knowledge/indexer/parser/CveParser.java @@ -328,7 +328,8 @@ public class CveParser { @SuppressWarnings("unchecked") private static Map<String, Object> parseYamlFrontmatter(String frontmatter) { try { - // Frontmatter is plain data; SafeConstructor refuses arbitrary type tags (!!type). + // Frontmatter is plain data. SnakeYAML 2.x already rejects custom global tags by default; + // SafeConstructor also strips arbitrary-object construction here (defense in depth). Object parsed = new Yaml(new SafeConstructor(new LoaderOptions())).load(frontmatter); return parsed instanceof Map ? (Map<String, Object>) parsed : null; } catch (Exception e) { diff --git a/knowledge/indexer/src/test/java/org/apache/camel/kit/knowledge/indexer/VersionResolverTest.java b/knowledge/indexer/src/test/java/org/apache/camel/kit/knowledge/indexer/VersionResolverTest.java index 1e699ba3..90af7136 100644 --- a/knowledge/indexer/src/test/java/org/apache/camel/kit/knowledge/indexer/VersionResolverTest.java +++ b/knowledge/indexer/src/test/java/org/apache/camel/kit/knowledge/indexer/VersionResolverTest.java @@ -375,4 +375,29 @@ class VersionResolverTest { assertEquals("4.14", mapping.camelMinor()); assertEquals("4.14.2", mapping.camelVersion()); } + + // ── 10. SafeConstructor rejects globally tagged frontmatter ───────── + + /** Flips a flag if SnakeYAML ever constructs it; a SafeConstructor-backed parser must not. */ + public static final class Canary { + static volatile boolean constructed = false; + + public Canary() { + constructed = true; + } + } + + @Test + void rejectsGloballyTaggedReleaseFrontmatterWithoutConstructing() throws Exception { + Canary.constructed = false; + Path releasesDir = Files.createDirectories(tempDir.resolve("content/releases")); + Files.writeString(releasesDir.resolve("release-4.14.0.md"), + "---\ncategory: camel\ndate: 2026-01-15\npwn: !!" + Canary.class.getName() + " {}\n---\nBody.\n"); + + // SafeConstructor refuses the global tag during composition, before any construction. + assertThrows(org.yaml.snakeyaml.error.YAMLException.class, + () -> VersionResolver.parseReleases(tempDir)); + assertFalse(Canary.constructed, + "SafeConstructor must not instantiate a globally tagged type in release frontmatter"); + } } diff --git a/knowledge/indexer/src/test/java/org/apache/camel/kit/knowledge/indexer/parser/CveParserTest.java b/knowledge/indexer/src/test/java/org/apache/camel/kit/knowledge/indexer/parser/CveParserTest.java index 99e50723..2d7c4b29 100644 --- a/knowledge/indexer/src/test/java/org/apache/camel/kit/knowledge/indexer/parser/CveParserTest.java +++ b/knowledge/indexer/src/test/java/org/apache/camel/kit/knowledge/indexer/parser/CveParserTest.java @@ -51,4 +51,36 @@ class CveParserTest { assertTrue(ranges.contains("3.0.0-3.21.4")); assertTrue(ranges.contains("4.0.0-4.0.4")); } + + /** Flips a flag if SnakeYAML ever constructs it; a SafeConstructor-backed parser must not. */ + public static final class Canary { + static volatile boolean constructed = false; + + public Canary() { + constructed = true; + } + } + + @Test + void cveGlobalTagFallsBackToLineExtractionWithoutConstructing() { + Canary.constructed = false; + String content = "---\n" + + "cve: CVE-2099-0001\n" + + "severity: HIGH\n" + + "summary: \"Canary advisory\"\n" + + "pwn: !!" + Canary.class.getName() + " {}\n" + + "fixed: \"4.0.4, 3.21.4\"\n" + + "---\n" + + "Body referencing CAMEL-12345.\n"; + + // The global tag makes SafeConstructor throw; parseYamlFrontmatter catches it and falls + // back to line-based regex extraction, so the advisory still parses and nothing is constructed. + CveParser.CveAdvisory cve = CveParser.parse(content); + + assertFalse(Canary.constructed, + "SafeConstructor must not instantiate a globally tagged type in CVE frontmatter"); + assertEquals("CVE-2099-0001", cve.cveId()); + assertEquals("HIGH", cve.severity()); + assertTrue(cve.fixedVersions().contains("4.0.4")); + } }
