From 6fc377bf04f0cc1bc3eca2af59a8335260bdbb37 Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Mon, 4 May 2026 10:05:23 -0400 Subject: [PATCH 1/2] fix: validate non-empty flag key and non-null defaultValue in Variable Match the existing guards in the Cloud client and the Java/Python SDKs. Empty/null keys reaching the WASM bucketing engine trigger an internal abort() at eventQueue.ts:122 ('Event missing target to save aggregate event') which compiles to a wasmtime trap. After enough traps the AS heap state becomes corrupted, and on the .NET host wasmtime panics internally. --- .../DevCycleTest.cs | 53 ++++++++++++++++++- .../Api/DevCycleLocalClient.cs | 20 +++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs b/DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs index 7d4ba83..6c17c26 100644 --- a/DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs +++ b/DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; using DevCycle.SDK.Server.Local.Api; using DevCycle.SDK.Server.Common.Model; @@ -233,6 +233,57 @@ public void Variable_NullUser_ThrowsException() }); } + [TestMethod] + public void Variable_NullKey_ThrowsArgumentException() + { + // Reaching the WASM bucketing engine with a null/empty flag key + // triggers an internal abort() and corrupts the WASM heap. Match + // the Java/Python SDKs (and Cloud client) by failing fast with a + // clear ArgumentException before we ever enter WASM. + using DevCycleLocalClient api = DevCycleTestClient.getTestClient(); + var user = new DevCycleUser("test_user"); + + Assert.Throws(() => api.Variable(user, null, true).Result); + } + + [TestMethod] + public void Variable_EmptyKey_ThrowsArgumentException() + { + using DevCycleLocalClient api = DevCycleTestClient.getTestClient(); + var user = new DevCycleUser("test_user"); + + Assert.Throws(() => api.Variable(user, "", true).Result); + } + + [TestMethod] + public async Task VariableAsync_NullKey_ThrowsArgumentException() + { + using DevCycleLocalClient api = DevCycleTestClient.getTestClient(); + var user = new DevCycleUser("test_user"); + + await Assert.ThrowsExactlyAsync(async () => + await api.VariableAsync(user, null, true)); + } + + [TestMethod] + public async Task VariableAsync_EmptyKey_ThrowsArgumentException() + { + using DevCycleLocalClient api = DevCycleTestClient.getTestClient(); + var user = new DevCycleUser("test_user"); + + await Assert.ThrowsExactlyAsync(async () => + await api.VariableAsync(user, "", true)); + } + + [TestMethod] + public void Variable_NullDefaultValue_ThrowsArgumentNullException() + { + using DevCycleLocalClient api = DevCycleTestClient.getTestClient(); + var user = new DevCycleUser("test_user"); + + Assert.Throws(() => api.Variable(user, "some_key", null).Result); + } + [TestMethod] public void User_NullUserId_ThrowsException() { diff --git a/DevCycle.SDK.Server.Local/Api/DevCycleLocalClient.cs b/DevCycle.SDK.Server.Local/Api/DevCycleLocalClient.cs index 01cf24c..8d608c1 100644 --- a/DevCycle.SDK.Server.Local/Api/DevCycleLocalClient.cs +++ b/DevCycle.SDK.Server.Local/Api/DevCycleLocalClient.cs @@ -307,6 +307,16 @@ public override Task> Variable(DevCycleUser user, string key, T d { var requestUser = new DevCyclePopulatedUser(user); + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentException("key cannot be null or empty"); + } + + if (defaultValue == null) + { + throw new ArgumentNullException(nameof(defaultValue)); + } + if (!configManager.Initialized) { logger.LogWarning("Variable called before DevCycleClient has initialized, returning default value"); @@ -366,6 +376,16 @@ public async Task> VariableAsync(DevCycleUser user, string key, T { var requestUser = new DevCyclePopulatedUser(user); + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentException("key cannot be null or empty"); + } + + if (defaultValue == null) + { + throw new ArgumentNullException(nameof(defaultValue)); + } + if (!configManager.Initialized) { logger.LogWarning("Variable called before DevCycleClient has initialized, returning default value"); From 7356a2c9fd479bd910eb5ffbf92529026412e03f Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Mon, 4 May 2026 10:16:45 -0400 Subject: [PATCH 2/2] test: add VariableAsync null defaultValue test --- DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs b/DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs index 6c17c26..0ee39f0 100644 --- a/DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs +++ b/DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs @@ -284,6 +284,16 @@ public void Variable_NullDefaultValue_ThrowsArgumentNullException() Assert.Throws(() => api.Variable(user, "some_key", null).Result); } + [TestMethod] + public async Task VariableAsync_NullDefaultValue_ThrowsArgumentNullException() + { + using DevCycleLocalClient api = DevCycleTestClient.getTestClient(); + var user = new DevCycleUser("test_user"); + + await Assert.ThrowsExactlyAsync(async () => + await api.VariableAsync(user, "some_key", null)); + } + [TestMethod] public void User_NullUserId_ThrowsException() {