TINKERPOP-3279 Restrict GraphSON 1.0 embedded-type deserialization - #3586
TINKERPOP-3279 Restrict GraphSON 1.0 embedded-type deserialization#3586GumpacG wants to merge 4 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## 3.7-dev #3586 +/- ##
=============================================
+ Coverage 75.49% 75.63% +0.13%
- Complexity 13161 13239 +78
=============================================
Files 1092 1095 +3
Lines 67208 67461 +253
Branches 7391 7431 +40
=============================================
+ Hits 50742 51023 +281
+ Misses 13837 13812 -25
+ Partials 2629 2626 -3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| * application type read from trusted input; a graph document from an untrusted source should not be granted | ||
| * additional prefixes. Has no effect on GraphSON 2.0 or 3.0, which resolve types through a fixed registry. | ||
| */ | ||
| public Builder addAllowedTypeIdPrefix(final String... prefixes) { |
There was a problem hiding this comment.
Should this have a matching denyTypeIdPrefix() in case the current allowed list is too permissive?
|
Some potential missing test cases:
|
| /** | ||
| * Canary used to verify a disallowed class named as a generic type argument is not loaded when refused. | ||
| */ | ||
| public static class StaticInitCanaryArg { |
There was a problem hiding this comment.
Nit: maybe a short comment about why this StaticInitCanaryArg and StaticInitCanaryValue both need to exist. From a first glance it looks like the same class, but I'm guessing this has to do with static initialization?
| protection against nefarious scripts. *(documented — `gremlin-applications.asciidoc` "Protecting Script | ||
| Execution", the two sample configs)* | ||
| - **Enabled serializers** — wire set is GraphSON 3.0 + GraphBinary. Gryo is IO-format-only, not on the wire, | ||
| - **Enabled serializers** — the default wire set is GraphSON 3.0 + GraphBinary. The typed GraphSON 1.0 serializer (`vnd.gremlin-v1.0+json`) is also shippable and screens its `@class` default typing against an allow-list of exact class names before loading the class (§8). Gryo is IO-format-only, not on the wire, |
There was a problem hiding this comment.
I think we can probably drop any threat model changes from this PR, such updates are probably best handled separately.
Summary
GraphSON 1.0 with embedded types (
TypeInfo.PARTIAL_TYPES) configured Jackson default typing withJsonTypeInfo.Id.CLASSand noPolymorphicTypeValidator. Reading a document reconstructed whatever class was named in its@classproperty, so a crafted document could name and construct (or merely load, running its staticinitializer) any class on the classpath. It is reachable pre-auth via the typed GraphSON 1.0 wire serializer
GraphSONMessageSerializerV1(application/vnd.gremlin-v1.0+json), and throughio()reads and GraphSON 1.0persistence. GraphSON 2.0/3.0 are unaffected: they resolve types through a fixed registry, not by arbitrary class name.
This change constrains that default typing while leaving legitimate value types working.
What changed
New API
GraphSONMapper.Builder.addAllowedTypeIdPrefix(String...)trusts additional class-name prefixes for GraphSON 1.0 embedded-type deserialization, in addition to the safe defaults. Intended for provider/application types read from trusted input.Hardened (GraphSON 1.0
PARTIAL_TYPESonly)PolymorphicTypeValidatordecides a simple type id from its name, so a disallowed class is refused invalidateSubClassNamebefore it is loaded (its static initializer never runs). Array descriptors are unwrapped and allowed only when the component type is allowed; primitive arrays are allowed.java.lang,java.util,java.math,java.time,java.sql,org.apache.tinkerpop, minus the network packagesjava.net/java.nio.java.net.InetAddressandjava.net.URI(string-backed, no DNS, unlikejava.net.URL).java.lang.Classis exact-denied even thoughjava.langis allowed, so ajava.lang.Classvalue cannot name and load an arbitrary class (Jackson would otherwise resolve it withinitialize=true).GraphSON1dScreeningIdResolverrefuses a parameterized type id (one containing<) before Jackson resolves it. This closes a bypass where a disallowed class hidden as a generic type argument was loaded before validation, and where enum type arguments were not validated at all (Jackson skips them). GraphSON 1.0 never emits a parameterized@class, so nothing legitimate is affected.Behavior change
A GraphSON 1.0 typed document whose
@classnames a type outside the allowed set now fails on read:Breaking changes and capability impact
Breaking: reading a GraphSON 1.0 typed document now fails, instead of deserializing, when its
@class:IoRegistry, an application POJO, mostjava.net.*/java.nio.*types),java.lang.Classused as a value, orA document written by an older version that embeds such a type may no longer be readable.
Restore a specific type for trusted input by trusting its package:
or migrate to GraphSON 3.0 / GraphBinary. Standard scalar, collection and array values,
java.math/java.time/java.sqlvalues,java.net.InetAddress/java.net.URI, and TinkerPop graph types are unaffected. The restrictiongates reads; the write path still emits
@classfor any type, so a writer and reader should be configured consistently. (java.nio.ByteBufferis written by V1 as a concrete subtype Jackson cannot reconstruct, so it was never round-trippable in V1 and remains refused on read.)Testing
GraphSONMapperPartialEmbeddedTypeTestcovers, on the default V1 mapper:@classis refused, and is not class-loaded (a static-init canary never fires);argument is refused (regression for the parameterized-id / enum bypass);
java.lang.Classvalue is refused and not loaded;java.net.URL) is refused, whilejava.net.InetAddressandjava.net.URIround-trip;java.sql/java.utilvalue types round-trip;addAllowedTypeIdPrefix(...)re-enables an otherwise-denied package.GraphSON 2.0/3.0 embedded-type and wire-serializer suites continue to pass unchanged.
Assisted-by: Kiro:claude-opus-4.8