fix(serialization)!: enforce type allow-list in default JSON storage serializer - #10268
Merged
ReubenBond merged 1 commit intoJul 31, 2026
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
ReubenBond
force-pushed
the
reubenbond-secure-newtonsoft-json-defaults
branch
from
July 31, 2026 16:35
44143cf to
8d48046
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens Orleans’ default Newtonsoft.Json-based serializer path (grain storage/JSON streaming/transactional state) by enforcing the existing Orleans type allow-list when resolving $type values emitted under TypeNameHandling.All, mitigating gadget-chain deserialization risks while keeping the wire format compatible.
Changes:
- Update
OrleansJsonSerializationBinderto resolve types throughTypeConverter(allow-list enforcement), with an opt-out flag for legacy permissive behavior. - Introduce
OrleansJsonSerializerOptions.AllowAllTypesand plumb it through default JSON serializer settings configuration. - Add targeted tests validating allow-listed vs disallowed type resolution and round-trip behavior, plus update API surface.
Show a summary per file
| File | Description |
|---|---|
| test/Orleans.Core.Tests/Serialization/OrleansJsonSerializationBinderTests.cs | Adds coverage for strict vs permissive binder behavior during type binding and deserialization. |
| src/Orleans.Core/Serialization/OrleansJsonSerializerSettings.cs | Threads AllowAllTypes from options into binder creation for default JSON settings. |
| src/Orleans.Core/Serialization/OrleansJsonSerializerOptions.cs | Adds AllowAllTypes option and ensures settings are configured accordingly. |
| src/Orleans.Core/Serialization/OrleansJsonSerializationBinder.cs | Enforces Orleans type allow-list via TypeConverter, with legacy permissive compatibility path. |
| src/api/Orleans.Core/Orleans.Core.cs | Updates public API surface for new constructor and options property. |
Copilot's findings
Suppressed comments (1)
test/Orleans.Core.Tests/Serialization/OrleansJsonSerializationBinderTests.cs:126
- This assertion uses Assert.ThrowsAny, which is overly broad and could pass for unrelated failures. Since deserialization of a disallowed $type is expected to fail with JsonSerializationException (thrown by the binder), assert that exception type explicitly.
Assert.ThrowsAny<Exception>(() => JsonConvert.DeserializeObject(json, typeof(object), settings));
- Files reviewed: 5/5 changed files
- Comments generated: 2
…erializer The default grain-storage serializer (`JsonGrainStorageSerializer` -> `OrleansJsonSerializer`) is configured with `TypeNameHandling.All`, and its `OrleansJsonSerializationBinder` resolved any type named in the payload's `$type` token (via `CachedTypeResolver` and Newtonsoft's permissive `DefaultSerializationBinder`). This let an attacker who can influence persisted/streamed/transactional state cause arbitrary CLR types to be constructed during deserialization (a classic `TypeNameHandling.All` gadget-chain vulnerability). Make the binder enforce Orleans' existing type allow-list (reusing `TypeConverter` / `ITypeNameFilter` / `TypeManifestOptions.AllowedTypes`) instead of building a parallel Newtonsoft-specific mechanism. Types marked `[GenerateSerializer]` are already auto-allow-listed and keep working with no changes; BCL collections/primitives remain permitted by `DefaultTypeFilter`. Disallowed `$type` tokens now throw an actionable `JsonSerializationException`. This is secure by default with an opt-out: the new `OrleansJsonSerializerOptions.AllowAllTypes` (default false) restores the previous permissive behavior for the JSON storage/streaming/transaction path only, without weakening the native serializer's global filtering. The legacy `OrleansJsonSerializationBinder(TypeResolver)` constructor is retained (permissive) for API compatibility. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 75c9fe25-7322-4eb8-b0d4-a475959d5658
ReubenBond
force-pushed
the
reubenbond-secure-newtonsoft-json-defaults
branch
from
July 31, 2026 17:30
8d48046 to
2494046
Compare
Contributor
There was a problem hiding this comment.
Copilot's findings
Suppressed comments (1)
src/Orleans.Core/Serialization/OrleansJsonSerializationBinder.cs:33
- The legacy constructor does not assign
_typeConverter, but_typeConverteris areadonlyfield. This will not compile (readonly fields must be definitely assigned in every constructor).
public OrleansJsonSerializationBinder(TypeResolver typeResolver)
{
_typeResolver = typeResolver;
_allowAllTypes = true;
}
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
This was referenced Aug 28, 2026
Merged
This was referenced Aug 31, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Orleans'' default grain-storage serializer (
JsonGrainStorageSerializer→OrleansJsonSerializer) is configured withTypeNameHandling.All, so every serialized object embeds a$typetoken that is honored on read. TheOrleansJsonSerializationBinderresolved that$typeviaCachedTypeResolverand, on failure, fell back to Newtonsoft''s permissiveDefaultSerializationBinder— neither consults any allow-list. An attacker able to influence persisted, streamed, or transactional state could therefore cause arbitrary CLR types to be constructed during deserialization — the classicTypeNameHandling.Allgadget-chain vulnerability. The same shared settings feed grain storage, JSON streaming, and (Azure) transactional state, so all three were affected.Solution
Make the binder enforce Orleans'' existing type allow-list rather than introduce a parallel Newtonsoft-specific system.
OrleansJsonSerializationBinder.BindToTypenow resolves$typethroughTypeConverter, which only constructs types that are permitted by the configuredITypeNameFilter/ITypeFilter/TypeManifestOptions.AllowedTypes(or auto-allow-listed via[GenerateSerializer]). Disallowed types throw an actionableJsonSerializationExceptionthat lists every remediation option.This is secure by default with an opt-out:
[GenerateSerializer]state types are already auto-allow-listed, andDefaultTypeFilterstill permits BCL collections/primitives.TypeNameHandling.Allis retained so existing persisted state stays readable — security comes from the binder, not from changing the wire format.OrleansJsonSerializerOptions.AllowAllTypes(defaultfalse) restores the previous permissive behavior for the JSON storage/streaming/transaction path only, without weakening the native serializer''s global type filtering. Narrower opt-ins (AllowedTypes,ITypeNameFilter) are preferred and are surfaced in the exception message.OrleansJsonSerializationBinder(TypeResolver)constructor is kept (permissive) for API compatibility; a new constructor takes theTypeConverter+ opt-out flag.Compatibility
Breaking for apps that persist
$typeentries for types that are neither[GenerateSerializer]-registered nor covered by a filter — these now throw on read. The thrown exception explains the four ways to resolve it: mark the type[GenerateSerializer], add it toTypeManifestOptions.AllowedTypes, register anITypeNameFilter/ITypeFilter, or setOrleansJsonSerializerOptions.AllowAllTypes = trueto restore the old behavior.Microsoft Reviewers: Open in CodeFlow