[CmdPal] Harden JS numeric color/int parsing against out-of-range input

ReadByte/ReadInt guarded only on ValueKind == Number, then called
JsonElement.GetByte()/GetInt32(), which throw FormatException for
out-of-range or fractional values coming from untrusted extension JSON.
A single bad tag color would escape the WinRT-visible Tags getter and
degrade the entire list item to Error, losing title, subtitle, icon and
every valid tag. Switch to TryGetByte/TryGetInt32 with the existing
defaults so malformed components fall back instead of throwing.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d243b1e9-40fb-4aed-aa60-beb5e80f7d91
This commit is contained in:
Michael Jolley
2026-07-28 15:03:24 -05:00
committed by Michael Jolley
parent d04f2ae54d
commit 72807b5916
2 changed files with 53 additions and 4 deletions

View File

@@ -634,9 +634,10 @@ internal static class JSModelMapper
private static byte ReadByte(JsonElement element, string camel, string pascal, byte defaultValue = 0)
{
if (TryGetAnyCase(element, camel, pascal, out var prop) && prop.ValueKind == JsonValueKind.Number)
if (TryGetAnyCase(element, camel, pascal, out var prop) && prop.ValueKind == JsonValueKind.Number &&
prop.TryGetByte(out var value))
{
return prop.GetByte();
return value;
}
return defaultValue;
@@ -645,9 +646,10 @@ internal static class JSModelMapper
private static int ReadInt(JsonElement element, string name, int defaultValue)
{
if (element.ValueKind == JsonValueKind.Object &&
element.TryGetProperty(name, out var prop) && prop.ValueKind == JsonValueKind.Number)
element.TryGetProperty(name, out var prop) && prop.ValueKind == JsonValueKind.Number &&
prop.TryGetInt32(out var value))
{
return prop.GetInt32();
return value;
}
return defaultValue;

View File

@@ -391,6 +391,53 @@ public partial class JSAdapterRemediationTests
Assert.IsFalse(withoutAccent.AccentColor.HasValue);
}
// p3-12: a tag whose color components are out of byte range or fractional must
// not throw out of the WinRT-visible Tags getter. Every numeric component that
// does not fit is dropped to its default, and the surrounding item metadata
// (title, subtitle, other tags) is preserved rather than collapsing to Error.
[TestMethod]
public void Tags_OutOfRangeOrFractionalColorComponentsDefaultInsteadOfThrowing()
{
using var fake = new JSFakeExtension();
var element = ParseElement(new JsonObject
{
["title"] = "Item With Bad Tag Color",
["subtitle"] = "Still Here",
["tags"] = new JsonArray
{
new JsonObject
{
["text"] = "over",
["foreground"] = new JsonObject
{
// 256 overflows a byte and 1.5 is fractional; both would throw
// from JsonElement.GetByte, so they must fall back to defaults.
["r"] = 256,
["g"] = 1.5,
["b"] = 12,
},
},
new JsonObject { ["text"] = "clean" },
},
});
var adapter = new JSListItemAdapter(element, fake.Connection);
var tags = adapter.Tags;
Assert.AreEqual(2, tags.Length);
Assert.AreEqual("over", tags[0].Text);
Assert.AreEqual("clean", tags[1].Text);
var foreground = tags[0].Foreground;
Assert.IsTrue(foreground.HasValue);
Assert.AreEqual(0, foreground.Color.R);
Assert.AreEqual(0, foreground.Color.G);
Assert.AreEqual(12, foreground.Color.B);
Assert.AreEqual("Item With Bad Tag Color", adapter.Title);
Assert.AreEqual("Still Here", adapter.Subtitle);
}
private static async Task<string> SubmitAndReadFormId(JSFormContentProxy form, TaskCompletionSource<string> captured)
{
await Task.Run(() => form.SubmitForm("{}", "{}"));