Skip to content

Commit dff555b

Browse files
committed
Sanitize only subset of bindings.
1 parent 25e9506 commit dff555b

File tree

7 files changed

+195
-10
lines changed

7 files changed

+195
-10
lines changed

src/WebJobs.Script.WebHost/Extensions/FunctionMetadataExtensions.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,21 @@ private static async Task<JObject> GetFunctionConfig(FunctionMetadata metadata,
176176

177177
private static async Task<JObject> GetFunctionConfigFromFile(string path)
178178
{
179-
return JObject.Parse(Sanitizer.Sanitize(await FileUtility.ReadAsync(path)));
179+
var fileContent = await FileUtility.ReadAsync(path);
180+
var jObject = JObject.Parse(fileContent);
181+
182+
if (jObject.TryGetValue("bindings", StringComparison.OrdinalIgnoreCase, out JToken bindingsToken) && bindingsToken is JArray bindings)
183+
{
184+
var bindingObjects = bindings.OfType<JObject>().ToList();
185+
186+
foreach (var binding in bindingObjects)
187+
{
188+
var sanitizedBinding = MetadataJsonHelper.CreateJObjectWithSanitizedPropertyValue(binding, ScriptConstants.SensitiveMetadataBindingPropertyNames);
189+
binding.Replace(sanitizedBinding);
190+
}
191+
}
192+
193+
return jObject;
180194
}
181195

182196
private static JObject GetFunctionConfigFromMetadata(FunctionMetadata metadata)

src/WebJobs.Script/Host/HostFunctionMetadataProvider.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using System.IO.Abstractions;
1010
using System.Linq;
1111
using System.Threading.Tasks;
12-
using Microsoft.Azure.WebJobs.Logging;
1312
using Microsoft.Azure.WebJobs.Script.Configuration;
1413
using Microsoft.Azure.WebJobs.Script.Description;
1514
using Microsoft.Azure.WebJobs.Script.Diagnostics;
@@ -136,9 +135,7 @@ internal static FunctionMetadata ParseFunctionMetadata(string functionName, JObj
136135
{
137136
foreach (JObject binding in bindingArray)
138137
{
139-
// Sanitize the binding JSON to remove any sensitive information before creating BindingMetadata
140-
var sanitizedBindingJson = Sanitizer.Sanitize(binding.ToString(Newtonsoft.Json.Formatting.None));
141-
var sanitizedJObject = JObject.Parse(sanitizedBindingJson);
138+
var sanitizedJObject = MetadataJsonHelper.CreateJObjectWithSanitizedPropertyValue(binding, ScriptConstants.SensitiveMetadataBindingPropertyNames);
142139

143140
BindingMetadata bindingMetadata = BindingMetadata.Create(sanitizedJObject);
144141
functionMetadata.Bindings.Add(bindingMetadata);
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using Microsoft.Azure.WebJobs.Logging;
8+
using Newtonsoft.Json.Linq;
9+
10+
namespace Microsoft.Azure.WebJobs.Script
11+
{
12+
internal static class MetadataJsonHelper
13+
{
14+
/// <summary>
15+
/// Sanitizes the values of top-level properties in the specified <see cref="JObject"/>
16+
/// whose names match any in the provided collection, using case-insensitive comparison.
17+
/// The original property casing is preserved.
18+
/// </summary>
19+
/// <param name="jsonObject">The <see cref="JObject"/> to sanitize.</param>
20+
/// <param name="propertyNames">A collection of top-level property names to sanitize.</param>
21+
/// <returns>
22+
/// A <see cref="JObject"/> with the specified properties' values sanitized if found.
23+
/// </returns>
24+
/// <exception cref="ArgumentNullException">
25+
/// Thrown if <paramref name="jsonObject"/> or <paramref name="propertyNames"/> is <c>null</c>.
26+
/// </exception>
27+
public static JObject CreateJObjectWithSanitizedPropertyValue(JObject jsonObject, IEnumerable<string> propertyNames)
28+
{
29+
ArgumentNullException.ThrowIfNull(jsonObject, nameof(jsonObject));
30+
ArgumentNullException.ThrowIfNull(propertyNames, nameof(propertyNames));
31+
32+
foreach (var prop in jsonObject.Properties())
33+
{
34+
if (propertyNames.Contains(prop.Name, StringComparer.OrdinalIgnoreCase))
35+
{
36+
jsonObject[prop.Name] = Sanitizer.Sanitize(prop.Value.ToString());
37+
}
38+
}
39+
40+
return jsonObject;
41+
}
42+
43+
/// <summary>
44+
/// Parses the input JSON string into a <see cref="JObject"/> and sanitizes the values of top-level properties
45+
/// whose names match any in the provided collection, using case-insensitive comparison.
46+
/// The original property casing is preserved.
47+
/// </summary>
48+
/// <param name="json">The JSON string to parse and sanitize.</param>
49+
/// <param name="propertyNames">A collection of top-level property names to sanitize.</param>
50+
/// <returns>
51+
/// A <see cref="JObject"/> representing the parsed JSON, with the specified properties' values sanitized if found.
52+
/// </returns>
53+
/// <exception cref="ArgumentException">
54+
/// Thrown if <paramref name="json"/> is <c>null</c> or empty.
55+
/// </exception>
56+
/// <exception cref="ArgumentNullException">
57+
/// Thrown if <paramref name="propertyNames"/> is <c>null</c>.
58+
/// </exception>
59+
/// <exception cref="JsonReaderException">
60+
/// Thrown if <paramref name="json"/> is not a valid JSON string.
61+
/// </exception>
62+
public static JObject CreateJObjectWithSanitizedPropertyValue(string json, IEnumerable<string> propertyNames)
63+
{
64+
if (string.IsNullOrWhiteSpace(json))
65+
{
66+
throw new ArgumentException("Input JSON cannot be null or empty.", nameof(json));
67+
}
68+
69+
ArgumentNullException.ThrowIfNull(propertyNames, nameof(propertyNames));
70+
71+
var jsonObject = JObject.Parse(json);
72+
73+
return CreateJObjectWithSanitizedPropertyValue(jsonObject, propertyNames);
74+
}
75+
}
76+
}

src/WebJobs.Script/Host/WorkerFunctionMetadataProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,8 @@ internal FunctionMetadata ValidateBindings(IEnumerable<string> rawBindings, Func
258258

259259
foreach (string binding in rawBindings)
260260
{
261-
var deserializedObj = JsonConvert.DeserializeObject<JObject>(Sanitizer.Sanitize(binding), _dateTimeSerializerSettings);
262-
var functionBinding = BindingMetadata.Create(deserializedObj);
261+
var sanitizedBinding = MetadataJsonHelper.CreateJObjectWithSanitizedPropertyValue(binding, ScriptConstants.SensitiveMetadataBindingPropertyNames);
262+
var functionBinding = BindingMetadata.Create(sanitizedBinding);
263263

264264
Utility.ValidateBinding(functionBinding);
265265

src/WebJobs.Script/ScriptConstants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,5 +269,7 @@ public static class ScriptConstants
269269
public static readonly string CancellationTokenRegistration = "CancellationTokenRegistration";
270270

271271
internal const string MasterKeyName = "_master";
272+
273+
public static readonly ImmutableArray<string> SensitiveMetadataBindingPropertyNames = ["connection"];
272274
}
273275
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using Newtonsoft.Json.Linq;
7+
using Xunit;
8+
9+
namespace Microsoft.Azure.WebJobs.Script.Tests
10+
{
11+
public sealed class MetadataJsonHelperTests
12+
{
13+
[Fact]
14+
public void CreateJObjectWithSanitizedPropertyValue_NullJsonObject_ThrowsArgumentNullException()
15+
{
16+
JObject jsonObject = null;
17+
var propertyNames = new[] { "sensitiveProperty" };
18+
19+
Assert.Throws<ArgumentNullException>(() => MetadataJsonHelper.CreateJObjectWithSanitizedPropertyValue(jsonObject, propertyNames));
20+
}
21+
22+
[Fact]
23+
public void CreateJObjectWithSanitizedPropertyValue_NullPropertyNames_ThrowsArgumentNullException()
24+
{
25+
var jsonObject = new JObject();
26+
IEnumerable<string> propertyNames = null;
27+
28+
Assert.Throws<ArgumentNullException>(() => MetadataJsonHelper.CreateJObjectWithSanitizedPropertyValue(jsonObject, propertyNames));
29+
}
30+
31+
[Fact]
32+
public void CreateJObjectWithSanitizedPropertyValue_ValidInput_SanitizesMatchingProperties()
33+
{
34+
var jsonObject = new JObject
35+
{
36+
{ "sensitiveProperty1", "AccountKey=foo" },
37+
{ "sensitiveProperty2", "MyConnection" },
38+
{ "SENSITIVEPROPERTY3", "AccountKey=bar" },
39+
{ "otherProperty", "value2" }
40+
};
41+
var sensitiveBindingPropertyNames = new[] { "sensitiveProperty1", "sensitiveproperty2", "sensitiveproperty3" };
42+
43+
var result = MetadataJsonHelper.CreateJObjectWithSanitizedPropertyValue(jsonObject, sensitiveBindingPropertyNames);
44+
45+
Assert.Equal("[Hidden Credential]", result["sensitiveProperty1"].ToString());
46+
Assert.Equal("MyConnection", result["sensitiveProperty2"].ToString());
47+
Assert.Equal("[Hidden Credential]", result["SENSITIVEPROPERTY3"].ToString());
48+
Assert.Equal("value2", result["otherProperty"].ToString());
49+
}
50+
51+
[Fact]
52+
public void CreateJObjectWithSanitizedPropertyValue_NoMatchingProperties_DoesNotSanitize()
53+
{
54+
var jsonObject = new JObject
55+
{
56+
{ "otherProperty1", "value1" },
57+
{ "otherProperty2", "value2" },
58+
{ "otherProperty3", "AccountKey=foo" }
59+
};
60+
var sensitiveBindingPropertyNames = new[] { "sensitiveProperty" };
61+
62+
var result = MetadataJsonHelper.CreateJObjectWithSanitizedPropertyValue(jsonObject, sensitiveBindingPropertyNames);
63+
64+
Assert.Equal("value1", result["otherProperty1"].ToString());
65+
Assert.Equal("value2", result["otherProperty2"].ToString());
66+
Assert.Equal("AccountKey=foo", result["otherProperty3"].ToString());
67+
}
68+
69+
[Fact]
70+
public void CreateJObjectWithSanitizedPropertyValue_StringInput_NullOrEmptyJson_ThrowsArgumentException()
71+
{
72+
string json = null;
73+
var propertyNames = new[] { "sensitiveProperty" };
74+
75+
Assert.Throws<ArgumentException>(() => MetadataJsonHelper.CreateJObjectWithSanitizedPropertyValue(json, propertyNames));
76+
}
77+
78+
[Fact]
79+
public void CreateJObjectWithSanitizedPropertyValue_StringInput_InvalidJson_ThrowsJsonReaderException()
80+
{
81+
var json = "invalid json";
82+
var propertyNames = new[] { "sensitiveProperty" };
83+
84+
Assert.Throws<Newtonsoft.Json.JsonReaderException>(() => MetadataJsonHelper.CreateJObjectWithSanitizedPropertyValue(json, propertyNames));
85+
}
86+
87+
[Fact]
88+
public void CreateJObjectWithSanitizedPropertyValue_StringInput_ValidJson_SanitizesMatchingProperties()
89+
{
90+
var json = "{ \"SensitiveProperty\": \"pwd=12345\", \"otherProperty\": \"value2\" }";
91+
var propertyNames = new[] { "sensitiveproperty" };
92+
93+
var result = MetadataJsonHelper.CreateJObjectWithSanitizedPropertyValue(json, propertyNames);
94+
95+
Assert.Equal("[Hidden Credential]", result["SensitiveProperty"].ToString());
96+
Assert.Equal("value2", result["otherProperty"].ToString());
97+
}
98+
}
99+
}

test/WebJobs.Script.Tests/SanitizerTests.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,6 @@ public class SanitizerTests
4141
[InlineData("test,aaa://aaa:[email protected]:1111,test", "test,[Hidden Credential],test")]
4242
[InlineData(@"some text abc://abc:[email protected]:1111 some text abc://abc:[email protected]:1111 text", @"some text [Hidden Credential] some text [Hidden Credential] text")]
4343
[InlineData(@"some text abc://abc:[email protected]:1111 some text AccountKey=heyyyyyyy text", @"some text [Hidden Credential] some text [Hidden Credential]")]
44-
[InlineData("""{"queueName":"my-q-items","connection":"MyConnection","type":"queueTrigger","name":"qTrigger1","direction":"in"}""", "{\"queueName\":\"my-q-items\",\"connection\":\"MyConnection\",\"type\":\"queueTrigger\",\"name\":\"qTrigger1\",\"direction\":\"in\"}")]
45-
[InlineData("""{"queueName":"my-q-items","connection":"DefaultEndpointsProtocol=https;AccountName=a;AccountKey=b/c==;EndpointSuffix=core.windows.net","type":"queueTrigger","name":"queueTrigger1","direction":"in"}""", "{\"queueName\":\"my-q-items\",\"connection\":\"[Hidden Credential]\",\"type\":\"queueTrigger\",\"name\":\"queueTrigger1\",\"direction\":\"in\"}")]
46-
[InlineData("""{"name":"message","type":"queueTrigger","direction":"In","properties":{"supportsDeferredBinding":"True"},"queueName":"myqueue-items2","connection":"key:setting"}""", """{"name":"message","type":"queueTrigger","direction":"In","properties":{"supportsDeferredBinding":"True"},"queueName":"myqueue-items2","connection":"key:setting"}""")]
4744
public void SanitizeString(string input, string expectedOutput)
4845
{
4946
var sanitized = Sanitizer.Sanitize(input);

0 commit comments

Comments
 (0)