{"record":{"id":"b80072df61b48096","repo":"dotnet/orleans","slug":"journal-metadata-property-names-must-not-contain-n","errorCode":null,"errorMessage":"Journal metadata property names must not contain null characters.","messagePattern":"Journal metadata property names must not contain null characters\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Azure/Orleans.Journaling.AzureStorage/AzureBlobJournalStorage.cs","lineNumber":1019,"sourceCode":"                changed = true;\n            }\n        }\n\n        return changed;\n    }\n\n    private static void ValidateCallerMetadataProperty(string key, string value)\n    {\n        ValidateCallerMetadataPropertyName(key);\n        ArgumentNullException.ThrowIfNull(value);\n    }\n\n    private static void ValidateCallerMetadataPropertyName(string key)\n    {\n        ArgumentException.ThrowIfNullOrWhiteSpace(key);\n        if (key.IndexOf('\\0') >= 0)\n        {\n            throw new ArgumentException(\"Journal metadata property names must not contain null characters.\", nameof(key));\n        }\n\n        if (IsProviderMetadataKey(key))\n        {\n            throw new ArgumentException($\"Journal metadata property '{key}' is provider-owned.\", nameof(key));\n        }\n    }\n\n    private static bool IsProviderMetadataKey(string key)\n        => string.Equals(key, FormatMetadataKey, StringComparison.OrdinalIgnoreCase)\n            || string.Equals(key, CheckpointMetadataKey, StringComparison.OrdinalIgnoreCase)\n            || string.Equals(key, CheckpointOffsetMetadataKey, StringComparison.OrdinalIgnoreCase)\n            || string.Equals(key, WalGenerationMetadataKey, StringComparison.OrdinalIgnoreCase)\n            || key.StartsWith(\"$\", StringComparison.Ordinal);\n\n    private static ETag ToAzureETag(string eTag)\n    {\n        ArgumentException.ThrowIfNullOrWhiteSpace(eTag);","sourceCodeStart":1001,"sourceCodeEnd":1037,"githubUrl":"https://github.com/dotnet/orleans/blob/fca799fa70ecb6ad975224271703ca43221f58de/src/Azure/Orleans.Journaling.AzureStorage/AzureBlobJournalStorage.cs#L1001-L1037","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Sanitize all caller metadata keys before passing them: strip NUL characters or reject them, e.g. key.Replace(\"\\0\", string.Empty).","Restrict metadata keys to printable ASCII or UTF-8 identifiers without control characters.","If you need binary metadata, put it in the metadata VALUE, not the key, or store it in the journal payload itself."],"exampleFix":"// before\nvar meta = new Dictionary<string, string> { [\"user\\0id\"] = \"abc\" };\nawait storage.AppendAsync(data, meta, ct);\n\n// after\nvar meta = new Dictionary<string, string> { [\"user_id\"] = \"abc\" };\nawait storage.AppendAsync(data, meta, ct);","handlingStrategy":"validation","validationCode":"static bool IsValidMetadataKey(string key)\n    => !string.IsNullOrWhiteSpace(key)\n        && key.IndexOf('\\0') < 0\n        && !key.StartsWith(\"$\", StringComparison.Ordinal)\n        && !IsReservedKey(key);\n\nstatic bool IsReservedKey(string key)\n    => key.Equals(\"format\", StringComparison.OrdinalIgnoreCase)\n        || key.Equals(\"checkpoint\", StringComparison.OrdinalIgnoreCase)\n        || key.Equals(\"checkpoint_offset\", StringComparison.OrdinalIgnoreCase)\n        || key.Equals(\"wal_generation\", StringComparison.OrdinalIgnoreCase);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Sanitize metadata keys from untrusted sources before passing them to journal storage APIs.","Namespace application metadata keys (e.g. 'app.foo') to avoid collisions with reserved keys.","Add a unit test that asserts reserved keys are rejected."],"tags":["azure-blob-storage","metadata","validation","argument"],"backgroundTag":null,"analyzedSha":"fca799fa70ecb6ad975224271703ca43221f58de","analyzedAt":"2026-08-13T19:55:57.938Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}