Fix CWE-94 code injection in MDA and canvas interactWithControl - #688
Merged
Conversation
interactWithControl() built the value object literal by wrapping
itemPath.propertyName in hand written quotes:
var valueJson = `{"${itemPath.propertyName}":${value}}`;
That string is concatenated into a script which executePublishedAppScript()
runs through eval(). The property name originates from the test plan
(.fx.yaml), so a crafted name such as `a":0});payload();({"b` closes the
object literal and the argument list and appends arbitrary statements to the
evaluated script.
Escape the key with JSON.stringify() in both the array and the scalar branch.
Ordinary property names serialize identically, so the generated script is
unchanged for existing test plans.
Adds PowerAppsTestEngineMDACustomInjectionTests covering the escaping, the
inertness of the payload once the generated script is evaluated, and the
unchanged output for ordinary property names. The class declares
interactWithControl twice and the later definition wins at runtime, so the
tests extract the script building overload from the embedded resource and
evaluate it in isolation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Deja (Quesadeha)
approved these changes
Sep 8, 2026
interactWithControl() built the value object literal by wrapping itemPath.propertyName in hand written quotes, and executePublishedAppScript() marshals the resulting string into the published app where it is evaluated. A crafted property name from the test plan closes the object literal and the argument list and appends arbitrary statements to the script that crosses into the app. Unlike the model driven provider, these are plain top level functions with a single definition, so the path is reachable through PowerAppsTestEngine.setPropertyValue. Escape the key with JSON.stringify() in both the array and the scalar branch. Ordinary property names serialize identically, so the generated script is unchanged for existing test plans. Adds CanvasAppSdkInjectionTests, which loads the shipped script, stubs executePublishedAppScript to capture what would be sent to the app, and asserts the payload arrives as one inert key. The script is linked into the test project as an embedded resource so the tests run against the shipped file rather than a copy. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Deja (Quesadeha)
approved these changes
Sep 8, 2026
Prabhat Pandey (prabhatp75)
approved these changes
Sep 8, 2026
Prabhat Pandey (prabhatp75)
approved these changes
Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a CWE-94 code injection reported by Glasswing Mythos against the model driven provider, and the identical defect in the canvas provider.
Both SDKs built the value object literal by wrapping the control property name in hand written quotes:
That string is concatenated into a script which is then evaluated. The property name comes from the test plan (
.fx.yaml), so a crafted name such asa":0});payload();({"bcloses the object literal and the argument list and appends arbitrary statements to the evaluated script.testengine.provider.mda/PowerAppsTestEngineMDACustom.jseval()inexecutePublishedAppScript()testengine.provider.canvas/JS/CanvasAppSdk.jsinvokeScriptAsync()into the published appPowerAppsTestEngine.setPropertyValueFix
Escape the key with
JSON.stringify()in both the array and the scalar branch of each file:Ordinary property names serialize identically (
{"Text":1}), so the generated script is byte for byte unchanged for existing test plans. Value serialization and all surrounding logic are untouched.Tests
Two new suites, 16 tests total, xUnit + Jint, following the existing pattern in these projects.
PowerAppsTestEngineMDACustomInjectionTestsandCanvasAppSdkInjectionTestseach cover:InteractWithControlEscapesPropertyNameInGeneratedScript(x2)InteractWithControlDeliversPropertyNameAsSingleKey(x2)InteractWithControlIsUnchangedForOrdinaryPropertyNames(x2)SetPropertyValueDoesNotExecuteInjectedPropertyNamePowerAppsTestEngine.setPropertyValuepathSourceDoesNotInterpolatePropertyNameUnescapedBoth branches of the builder are covered by parameterizing
isArray, sinceObject.values()always returns an array and the scalar branch is otherwise unreachable.Each suite reads the real shipped script rather than a copy. The MDA test pulls it from the existing embedded resource;
CanvasAppSdk.jsis linked into the canvas test project as an embedded resource for the same reason.Note on reachability in the MDA provider
PowerAppsModelDrivenCanvasdeclaresinteractWithControltwice (lines 51 and 345). Per JS class semantics the later in-app definition wins, so the script building overload there is currently shadowed and not reachable through the public API. The fix is still worth making, and the test extracts that overload from the embedded resource and evaluates it in isolation so it exercises the shipped source. The canvas provider has no such shadowing, so its path is live.Validation
Note
testengine.provider.canvas.tests.csprojgains aJintreference, pinned to 4.1.0 to match the version already flowing in fromMicrosoft.PowerApps.TestEngine.Testsand avoid an NU1605 downgrade.