Skip to content

Fix CWE-94 code injection in MDA and canvas interactWithControl - #688

Merged
Brad Flood (brflood) merged 2 commits into
mainfrom
users/brflood/CWE94
Sep 8, 2026
Merged

Fix CWE-94 code injection in MDA and canvas interactWithControl#688
Brad Flood (brflood) merged 2 commits into
mainfrom
users/brflood/CWE94

Conversation

@brflood

@brflood Brad Flood (brflood) commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

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:

var valueJson = `{"${itemPath.propertyName}":${value}}`;

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 as a":0});payload();({"b closes the object literal and the argument list and appends arbitrary statements to the evaluated script.

File Sink Reachable today
testengine.provider.mda/PowerAppsTestEngineMDACustom.js eval() in executePublishedAppScript() No, see note below
testengine.provider.canvas/JS/CanvasAppSdk.js invokeScriptAsync() into the published app Yes, via PowerAppsTestEngine.setPropertyValue

Fix

Escape the key with JSON.stringify() in both the array and the scalar branch of each file:

var valueJson = `{${JSON.stringify(itemPath.propertyName)}:${value}}`;

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.

PowerAppsTestEngineMDACustomInjectionTests and CanvasAppSdkInjectionTests each cover:

Test Covers
InteractWithControlEscapesPropertyNameInGeneratedScript (x2) The breakout quote is escaped and the payload stays inert when the generated script is evaluated
InteractWithControlDeliversPropertyNameAsSingleKey (x2) The payload arrives as exactly one inert object key rather than as extra statements
InteractWithControlIsUnchangedForOrdinaryPropertyNames (x2) No regression: identical script output for normal property names
SetPropertyValueDoesNotExecuteInjectedPropertyName End to end guard on the PowerAppsTestEngine.setPropertyValue path
SourceDoesNotInterpolatePropertyNameUnescaped Static guard against reintroducing the hand written quoting

Both branches of the builder are covered by parameterizing isArray, since Object.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.js is linked into the canvas test project as an embedded resource for the same reason.

Note on reachability in the MDA provider

PowerAppsModelDrivenCanvas declares interactWithControl twice (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

  • All 16 new tests pass.
  • Full suites green: MDA 128 passed / 3 pre-existing skips, canvas 136 passed / 0 skipped.
  • Mutation checked. Reverting the one line change makes 5 of 8 MDA tests and 6 of 8 canvas tests fail, and restoring it returns them to green. The payload was chosen so the pre-fix script is syntactically valid and genuinely executes the injected statement, proving real code execution rather than a parse error.

Note

testengine.provider.canvas.tests.csproj gains a Jint reference, pinned to 4.1.0 to match the version already flowing in from Microsoft.PowerApps.TestEngine.Tests and avoid an NU1605 downgrade.

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>
@brflood
Brad Flood (brflood) requested a review from a team as a code owner September 8, 2026 22:13
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>
@brflood Brad Flood (brflood) changed the title Fix CWE-94 code injection in MDA interactWithControl Fix CWE-94 code injection in MDA and canvas interactWithControl Sep 8, 2026
@brflood
Brad Flood (brflood) merged commit 2c0342d into main Sep 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants