Skip to content

fix: block behavior gadgets in loose XAML#3048

Open
lhx077 wants to merge 1 commit into
devfrom
codex/assess-wpf-behaviors-vulnerability
Open

fix: block behavior gadgets in loose XAML#3048
lhx077 wants to merge 1 commit into
devfrom
codex/assess-wpf-behaviors-vulnerability

Conversation

@lhx077

@lhx077 lhx077 commented Jun 6, 2026

Copy link
Copy Markdown
Member

Motivation

  • Adding Microsoft.Xaml.Behaviors.Wpf and a TriggerAction-based animation exposed behavior action types to loose XAML, creating a gadget that can invoke arbitrary methods (e.g. Process.Start) when attacker-controlled XAML is parsed.
  • Loose-XAML loading paths (XamlReader.Parse / XamlReader.Load) had no explicit checks to deny behavior assemblies, process-related types, or dangerous XAML members, so a pre-parse sanitizer is required to prevent remote XAML RCE.

Description

  • Add a pre-parse validator ValidateSafeLooseXaml to PCL.Core/UI/IconManager.cs and call it before invoking the WPF XAML parser, rejecting disallowed types, members, and assembly references.
  • Denylist in IconManager now includes Process and ProcessStartInfo, existing dangerous XAML types (WebBrowser, Frame, ObjectDataProvider, etc.), and assembly references starting with Microsoft.Xaml.Behaviors, and it blocks members like Code, FactoryMethod, and Static.
  • Use a WpfXamlReader alias for clarity when invoking the WPF parser and centralize loose-XAML checks into re-usable validation helpers.
  • Extend the existing launcher sanitizer in Plain Craft Launcher 2/Modules/Base/ModBase.cs to also reject Process/ProcessStartInfo and any references to the Microsoft.Xaml.Behaviors assembly during the XamlXmlReader scan.

Testing

  • Ran git diff --check which produced no whitespace or diff-check errors.
  • Attempted dotnet build PCL.Core/PCL.Core.csproj -c Debug but the dotnet SDK was not available in the execution environment so a full build could not be performed.

Codex Task

Summary by Sourcery

在图标和启动器的 XML 处理流水线中加强对松散 XAML 的加载校验,以阻止基于行为(behavior)的 gadgets 以及其他危险的类型、成员和程序集。

Bug 修复:

  • 在解析由攻击者控制的用于图标的松散 XAML 时,阻止执行不安全的行为 gadgets 和与进程相关的操作。
  • 扩展启动器的 XML/XAML 清洗器,以拒绝与进程相关的类型、危险成员以及对 Microsoft.Xaml.Behaviors 的引用。

增强内容:

  • 引入可复用的松散 XAML 验证辅助方法,并在图标加载路径中调用 WPF XAML 解析器之前应用这些验证。
Original summary in English

Summary by Sourcery

Harden loose XAML loading in the icon and launcher XML pipelines to block behavior-based gadgets and other dangerous types, members, and assemblies.

Bug Fixes:

  • Prevent execution of unsafe behavior gadgets and process-related actions when parsing attacker-controlled loose XAML for icons.
  • Extend the launcher XML/XAML sanitizer to reject process-related types, dangerous members, and references to Microsoft.Xaml.Behaviors.

Enhancements:

  • Introduce reusable loose-XAML validation helpers and apply them before invoking the WPF XAML parser in the icon loading path.

@pcl-ce-automation pcl-ce-automation Bot added 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 size: L PR 大小评估:大型 and removed codex labels Jun 6, 2026
@sourcery-ai

sourcery-ai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Reviewer's Guide

为图标加载添加集中式的“松散 XAML”安全验证,并加强现有的 XAML 清洗器,以阻止行为 gadget 以及可启动进程的类型/程序集。

IconManager 中新的“松散 XAML”验证时序图

sequenceDiagram
    participant Caller
    participant IconManager
    participant ValidateSafeLooseXaml
    participant ValidateLooseXamlType
    participant ValidateLooseXamlValue
    participant WpfXamlReader

    Caller->>IconManager: TryLoadIconFromXaml(xamlString)
    IconManager->>ValidateSafeLooseXaml: ValidateSafeLooseXaml(xamlString)
    loop XamlXmlReader.Read
        ValidateSafeLooseXaml-->>ValidateSafeLooseXaml: reader.Read()
        alt reader.Type.UnderlyingType exists
            ValidateSafeLooseXaml->>ValidateLooseXamlType: ValidateLooseXamlType(type)
        end
        alt reader.Member exists
            alt member.Name in DisallowedLooseXamlMembers
                ValidateSafeLooseXaml-->>Caller: throw UnauthorizedAccessException
                Note over Caller: return
            end
            alt member.DeclaringType.UnderlyingType exists
                ValidateSafeLooseXaml->>ValidateLooseXamlType: ValidateLooseXamlType(declaringType)
            end
        end
        alt reader.Value is string
            ValidateSafeLooseXaml->>ValidateLooseXamlValue: ValidateLooseXamlValue(value)
            alt value or type matches disallowed
                ValidateLooseXamlType-->>Caller: throw UnauthorizedAccessException
                Note over Caller: return
            end
        end
    end
    IconManager->>WpfXamlReader: Parse(xamlString)
    WpfXamlReader-->>IconManager: UIElement
    IconManager-->>Caller: success
Loading

文件级变更

Change Details Files
在 IconManager 中引入可复用的“松散 XAML”验证,并在解析图标 XAML 之前强制执行。
  • 添加 WpfXamlReader 别名以及用于 XAML 检查和禁止控件类型的新 using 指令
  • 在 TryLoadIconFromXaml 和 LoadIconFromXaml 中所有“松散 XAML”解析之前插入 ValidateSafeLooseXaml 调用
  • 定义“松散 XAML”的拒绝列表类型,包括 WebBrowser/Frame/MediaElement/ObjectDataProvider/XmlDataProvider/WpfXamlReader/Window/Process/ProcessStartInfo
  • 为“松散 XAML”定义拒绝的成员(Code、FactoryMethod、Static)和程序集(Microsoft.Xaml.Behaviors)
  • 实现 ValidateSafeLooseXaml,以使用 XamlXmlReader 以流式方式解析 XAML,并委托给类型/成员/值验证器
  • 实现 ValidateLooseXamlType 和 ValidateLooseXamlValue 辅助函数,以强制执行类型和程序集拒绝列表检查
PCL.Core/UI/IconManager.cs
强化现有的 XAML 启动器清洗器,以同时阻止与 Process 相关的类型以及 Microsoft.Xaml.Behaviors 程序集的使用。
  • 在 GetObjectFromXML 中扩展黑名单,在扫描 XAML 节点时将 Process 和 ProcessStartInfo 包含进来
  • 添加检查,以拒绝任何与名称以 Microsoft.Xaml.Behaviors 开头的程序集相关联的 XAML 类型、成员或字符串值
  • 在新的程序集检查之后,复用基于黑名单的成员检查以处理 Code/FactoryMethod/Static
Plain Craft Launcher 2/Modules/Base/ModBase.cs

Tips and commands

Interacting with Sourcery

  • 触发新评审: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的评审评论。
  • 从评审评论生成 GitHub issue: 在评审评论下回复,请 Sourcery 从该评论创建一个 issue。你也可以回复 @sourcery-ai issue 来从该评论创建 issue。
  • 生成 pull request 标题: 在 pull request 标题中任意位置写上 @sourcery-ai,即可随时生成标题。也可以在 pull request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 正文的任意位置写上 @sourcery-ai summary,即可在指定位置生成 PR 摘要。也可以在 pull request 中评论 @sourcery-ai summary 来(重新)生成摘要。
  • 生成评审者指南: 在 pull request 中评论 @sourcery-ai guide,即可随时(重新)生成评审者指南。
  • 解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,以解决所有 Sourcery 评论。如果你已经处理了所有评论且不希望再看到它们,这会很有用。
  • 忽略所有 Sourcery 评审: 在 pull request 中评论 @sourcery-ai dismiss,以忽略所有现有的 Sourcery 评审。如果你希望从一次全新的评审开始,这尤其有用——别忘了再评论 @sourcery-ai review 来触发新评审!

Customizing Your Experience

访问你的 dashboard 来:

  • 启用或禁用评审功能,例如 Sourcery 生成的 pull request 摘要、评审者指南等。
  • 更改评审语言。
  • 添加、移除或编辑自定义评审说明。
  • 调整其它评审设置。

Getting Help

Original review guide in English

Reviewer's Guide

Adds centralized loose-XAML safety validation for icon loading and tightens the existing XAML sanitizer to block behavior gadgets and process-launching types/assemblies.

Sequence diagram for new loose-XAML validation in IconManager

sequenceDiagram
    participant Caller
    participant IconManager
    participant ValidateSafeLooseXaml
    participant ValidateLooseXamlType
    participant ValidateLooseXamlValue
    participant WpfXamlReader

    Caller->>IconManager: TryLoadIconFromXaml(xamlString)
    IconManager->>ValidateSafeLooseXaml: ValidateSafeLooseXaml(xamlString)
    loop XamlXmlReader.Read
        ValidateSafeLooseXaml-->>ValidateSafeLooseXaml: reader.Read()
        alt reader.Type.UnderlyingType exists
            ValidateSafeLooseXaml->>ValidateLooseXamlType: ValidateLooseXamlType(type)
        end
        alt reader.Member exists
            alt member.Name in DisallowedLooseXamlMembers
                ValidateSafeLooseXaml-->>Caller: throw UnauthorizedAccessException
                Note over Caller: return
            end
            alt member.DeclaringType.UnderlyingType exists
                ValidateSafeLooseXaml->>ValidateLooseXamlType: ValidateLooseXamlType(declaringType)
            end
        end
        alt reader.Value is string
            ValidateSafeLooseXaml->>ValidateLooseXamlValue: ValidateLooseXamlValue(value)
            alt value or type matches disallowed
                ValidateLooseXamlType-->>Caller: throw UnauthorizedAccessException
                Note over Caller: return
            end
        end
    end
    IconManager->>WpfXamlReader: Parse(xamlString)
    WpfXamlReader-->>IconManager: UIElement
    IconManager-->>Caller: success
Loading

File-Level Changes

Change Details Files
Introduce reusable loose-XAML validation in IconManager and enforce it before parsing XAML for icons.
  • Add WpfXamlReader alias and new using directives for XAML inspection and disallowed control types
  • Insert ValidateSafeLooseXaml calls before all loose XAML parses in TryLoadIconFromXaml and LoadIconFromXaml
  • Define denylisted loose-XAML types including WebBrowser/Frame/MediaElement/ObjectDataProvider/XmlDataProvider/WpfXamlReader/Window/Process/ProcessStartInfo
  • Define denylisted members (Code, FactoryMethod, Static) and assemblies (Microsoft.Xaml.Behaviors) for loose XAML
  • Implement ValidateSafeLooseXaml to stream-parse XAML with XamlXmlReader and delegate to type/member/value validators
  • Implement ValidateLooseXamlType and ValidateLooseXamlValue helpers to enforce type and assembly denylist checks
PCL.Core/UI/IconManager.cs
Harden the existing XAML launcher sanitizer to also block Process-related types and Microsoft.Xaml.Behaviors assembly usage.
  • Extend the blacklist in GetObjectFromXML to include Process and ProcessStartInfo when scanning XAML nodes
  • Add checks to reject any XAML types, members, or string values associated with assemblies whose names start with Microsoft.Xaml.Behaviors
  • Reuse the existing blacklist-based member checks for Code/FactoryMethod/Static after the new assembly checks
Plain Craft Launcher 2/Modules/Base/ModBase.cs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并给出了一些高层次的反馈:

  • 当前松散 XAML 的 denylist 逻辑(类型、成员、程序集)在 IconManagerModBase 中都有一份,现在是重复的;可以考虑把它们集中到共享的 helper/常量中,以避免逻辑分叉,并让后续更新更安全。
  • ValidateSafeLooseXamlGetObjectFromXML 中,一些字符串比较/Contains 检查依赖默认比较器,或者每次都重新分配 new[] { ... };建议统一使用 StringComparison.Ordinal,并把这些数组提升为静态 readonly 字段,这样既能让检查行为更可预测,又能降低开销。
给 AI Agent 的提示词
Please address the comments from this code review:

## Overall Comments
- The loose XAML denylist logic (types, members, assemblies) is now duplicated between `IconManager` and `ModBase`; consider centralizing this into shared helpers/constants to avoid divergence and make future updates safer.
- In both `ValidateSafeLooseXaml` and `GetObjectFromXML`, several string comparisons/Contains checks rely on default comparers or repeated allocation of `new[] { ... }`; using `StringComparison.Ordinal` consistently and hoisting these arrays to static readonly fields would make the checks more predictable and reduce overhead.

## Individual Comments

### Comment 1
<location path="PCL.Core/UI/IconManager.cs" line_range="152-157" />
<code_context>
+    }
+
+    private static void ValidateLooseXamlValue(string value) {
+        if (DisallowedLooseXamlTypes.Any(disallowedType => string.Equals(value, disallowedType.Name, StringComparison.Ordinal))) {
+            throw new UnauthorizedAccessException($"不允许使用 {value} 值。");
+        }
+
+        if (DisallowedLooseXamlAssemblies.Any(disallowedAssembly => value.Contains(disallowedAssembly, StringComparison.Ordinal))) {
+            throw new UnauthorizedAccessException($"不允许引用 {value}。");
+        }
+    }
</code_context>
<issue_to_address>
**🚨 issue (security):** The value-based type check may miss dangerous types referenced via fully qualified or markup-extension syntax.

`ValidateLooseXamlValue` compares `value` only to `disallowedType.Name`, so inputs like `System.Diagnostics.Process`, `{x:Type Process}`, or other namespace/markup forms will not be caught. If this check is intended as a safety net for string-only values, consider at least using `EndsWith(disallowedType.Name, StringComparison.Ordinal)` or normalizing common patterns before comparison so dangerous types cannot bypass the filter via qualified or markup syntax.
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得这次 review 有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈来改进后续的 review。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • The loose XAML denylist logic (types, members, assemblies) is now duplicated between IconManager and ModBase; consider centralizing this into shared helpers/constants to avoid divergence and make future updates safer.
  • In both ValidateSafeLooseXaml and GetObjectFromXML, several string comparisons/Contains checks rely on default comparers or repeated allocation of new[] { ... }; using StringComparison.Ordinal consistently and hoisting these arrays to static readonly fields would make the checks more predictable and reduce overhead.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The loose XAML denylist logic (types, members, assemblies) is now duplicated between `IconManager` and `ModBase`; consider centralizing this into shared helpers/constants to avoid divergence and make future updates safer.
- In both `ValidateSafeLooseXaml` and `GetObjectFromXML`, several string comparisons/Contains checks rely on default comparers or repeated allocation of `new[] { ... }`; using `StringComparison.Ordinal` consistently and hoisting these arrays to static readonly fields would make the checks more predictable and reduce overhead.

## Individual Comments

### Comment 1
<location path="PCL.Core/UI/IconManager.cs" line_range="152-157" />
<code_context>
+    }
+
+    private static void ValidateLooseXamlValue(string value) {
+        if (DisallowedLooseXamlTypes.Any(disallowedType => string.Equals(value, disallowedType.Name, StringComparison.Ordinal))) {
+            throw new UnauthorizedAccessException($"不允许使用 {value} 值。");
+        }
+
+        if (DisallowedLooseXamlAssemblies.Any(disallowedAssembly => value.Contains(disallowedAssembly, StringComparison.Ordinal))) {
+            throw new UnauthorizedAccessException($"不允许引用 {value}。");
+        }
+    }
</code_context>
<issue_to_address>
**🚨 issue (security):** The value-based type check may miss dangerous types referenced via fully qualified or markup-extension syntax.

`ValidateLooseXamlValue` compares `value` only to `disallowedType.Name`, so inputs like `System.Diagnostics.Process`, `{x:Type Process}`, or other namespace/markup forms will not be caught. If this check is intended as a safety net for string-only values, consider at least using `EndsWith(disallowedType.Name, StringComparison.Ordinal)` or normalizing common patterns before comparison so dangerous types cannot bypass the filter via qualified or markup syntax.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +152 to +157
if (DisallowedLooseXamlTypes.Any(disallowedType => string.Equals(value, disallowedType.Name, StringComparison.Ordinal))) {
throw new UnauthorizedAccessException($"不允许使用 {value} 值。");
}

if (DisallowedLooseXamlAssemblies.Any(disallowedAssembly => value.Contains(disallowedAssembly, StringComparison.Ordinal))) {
throw new UnauthorizedAccessException($"不允许引用 {value}。");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): 基于值的类型检查可能会漏掉通过全限定名或标记扩展语法引用的危险类型。

ValidateLooseXamlValue 只把 valuedisallowedType.Name 做比较,因此像 System.Diagnostics.Process{x:Type Process} 或其他带命名空间/标记形式的输入不会被拦截。如果这个检查是作为字符串值的安全兜底机制,建议至少使用 EndsWith(disallowedType.Name, StringComparison.Ordinal),或者在比较前对常见模式做归一化处理,避免危险类型通过限定名或标记语法绕过过滤。

Original comment in English

🚨 issue (security): The value-based type check may miss dangerous types referenced via fully qualified or markup-extension syntax.

ValidateLooseXamlValue compares value only to disallowedType.Name, so inputs like System.Diagnostics.Process, {x:Type Process}, or other namespace/markup forms will not be caught. If this check is intended as a safety net for string-only values, consider at least using EndsWith(disallowedType.Name, StringComparison.Ordinal) or normalizing common patterns before comparison so dangerous types cannot bypass the filter via qualified or markup syntax.

@whitecat346 whitecat346 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

按照sourcery所示更改

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: L PR 大小评估:大型 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants