dotnet/orleans · error · ArgumentException

Journal metadata property names must not contain null charac

Error message

Journal metadata property names must not contain null characters.

What it means

Thrown by AzureBlobJournalStorage when validating a caller-supplied metadata property name passed to Append/Replace/SetMetadata. The journal reserves certain metadata keys for its own bookkeeping (format, checkpoint, checkpoint_offset, wal_generation, and anything starting with '$'), and embedded NUL characters corrupt the Azure Blob metadata layer, so both are rejected up front before any write reaches the service.

Source

Thrown at src/Azure/Orleans.Journaling.AzureStorage/AzureBlobJournalStorage.cs:1019

                changed = true;
            }
        }

        return changed;
    }

    private static void ValidateCallerMetadataProperty(string key, string value)
    {
        ValidateCallerMetadataPropertyName(key);
        ArgumentNullException.ThrowIfNull(value);
    }

    private static void ValidateCallerMetadataPropertyName(string key)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(key);
        if (key.IndexOf('\0') >= 0)
        {
            throw new ArgumentException("Journal metadata property names must not contain null characters.", nameof(key));
        }

        if (IsProviderMetadataKey(key))
        {
            throw new ArgumentException($"Journal metadata property '{key}' is provider-owned.", nameof(key));
        }
    }

    private static bool IsProviderMetadataKey(string key)
        => string.Equals(key, FormatMetadataKey, StringComparison.OrdinalIgnoreCase)
            || string.Equals(key, CheckpointMetadataKey, StringComparison.OrdinalIgnoreCase)
            || string.Equals(key, CheckpointOffsetMetadataKey, StringComparison.OrdinalIgnoreCase)
            || string.Equals(key, WalGenerationMetadataKey, StringComparison.OrdinalIgnoreCase)
            || key.StartsWith("$", StringComparison.Ordinal);

    private static ETag ToAzureETag(string eTag)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(eTag);

View on GitHub (pinned to fca799fa70)

Solutions

  1. Sanitize all caller metadata keys before passing them: strip NUL characters or reject them, e.g. key.Replace("\0", string.Empty).
  2. Restrict metadata keys to printable ASCII or UTF-8 identifiers without control characters.
  3. If you need binary metadata, put it in the metadata VALUE, not the key, or store it in the journal payload itself.

Example fix

// before
var meta = new Dictionary<string, string> { ["user\0id"] = "abc" };
await storage.AppendAsync(data, meta, ct);

// after
var meta = new Dictionary<string, string> { ["user_id"] = "abc" };
await storage.AppendAsync(data, meta, ct);
Defensive patterns

Strategy: validation

Validate before calling

static bool IsValidMetadataKey(string key)
    => !string.IsNullOrWhiteSpace(key)
        && key.IndexOf('\0') < 0
        && !key.StartsWith("$", StringComparison.Ordinal)
        && !IsReservedKey(key);

static bool IsReservedKey(string key)
    => key.Equals("format", StringComparison.OrdinalIgnoreCase)
        || key.Equals("checkpoint", StringComparison.OrdinalIgnoreCase)
        || key.Equals("checkpoint_offset", StringComparison.OrdinalIgnoreCase)
        || key.Equals("wal_generation", StringComparison.OrdinalIgnoreCase);

Prevention

When it happens

Trigger: Calling an IJournalStorage method that accepts IReadOnlyDictionary<string,string> metadata (e.g. AppendAsync, ReplaceAsync, CreateIfNotExistsAsync with metadata) and passing a dictionary whose key contains a '\0' character. Validation runs synchronously in ValidateCallerMetadataPropertyName before the first network call.

Common situations: Serializing a binary key or a struct value into a metadata key name; copying keys from an untrusted source without sanitization; using a key derived from a GUID or byte array that was not first encoded as a clean string.

Related errors


AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13). Data as JSON: /api/errors/b80072df61b48096. Report an issue: GitHub.