Repository navigation
.NET, Python: Reject file skills with resource or script names that differ only by case - #10158
Conversation
FileSkill lookup is case-insensitive, so names such as readme.md and README.md were ambiguous and could resolve to the wrong file. The FileSkill constructor now rejects such resource or script names, and file and MCP archive skill sources skip the affected skill with a warning. ZIP extraction rejects members that normalize to the same path instead of keeping the first. Co-authored-by: Copilot App <[email protected]>
…by case Co-authored-by: Copilot App <[email protected]>
There was a problem hiding this comment.
🟡 Changes recommended
Windows case-variant archive entries are silently deduplicated before validation, allowing colliding resources to load instead of being rejected.
1 open issue
What changed in this PR
This PR adds validation to Python and .NET file-skill loading to reject ambiguous resource or script names that differ only by case, while allowing other skills to load.
Changes:
- Adds separate case-collision checks for resources and scripts, with logging when a skill is skipped.
- Changes Python ZIP handling to reject duplicate normalized paths and preserve case variants for skill-level validation.
- Adds regression tests and updates Python consumer guidance.
| File | Description |
|---|---|
| skills/agent-framework-builder-python/references/harness-agent-builder.md | Documents collision rejection and warnings. |
| python/packages/core/tests/core/test_skills.py | Tests constructor validation, lookup, and affected-skill isolation. |
| python/packages/core/tests/core/test_mcp_skills.py | Updates archive collision tests to expect rejection. |
| python/packages/core/agent_framework/_skills.py | Adds name validation and revises archive collision handling. |
| dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs | Tests filesystem collision rejection without skipping other skills. |
| dotnet/src/Microsoft.Agents.AI/Skills/File/AgentFileSkillsSource.cs | Adds resource and script collision checks with error logging. |
Review effort: Balanced
There was a problem hiding this comment.
MAF Automated Review — Iteration 1
Result: Findings reported
Scope: full PR (2 commit(s)): 13ca088fc4da, 6424dc69593d
Model: gpt-5.6-sol
Overview
The PR adds local case-insensitive collision checks and preserves unrelated skills when one skill is rejected, with focused filesystem and archive tests around the intended behavior. The guards correctly separate resource and script namespaces and retain existing archive size and traversal protections. Two archive paths remain inconsistent: Python does not reject case-variant manifests, and .NET cannot observe case-colliding archive resources after Windows extraction has already deduplicated them.
Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
2 verified findings remained after source verification (2 medium) across 2 files. Details are attached to the affected lines below.
Affected areas: dotnet/src/Microsoft.Agents.AI/Skills/File/AgentFileSkillsSource.cs, python/packages/core/agent_framework/_skills.py
Co-authored-by: Copilot App <[email protected]>
Co-authored-by: Copilot App <[email protected]>
Co-authored-by: Copilot App <[email protected]>
There was a problem hiding this comment.
MAF Automated Review — Iteration 2
Result: No findings
Scope: 3 net-new commit(s): b2642200eb4e, c9c47e5e4813, 7865be586a6a
Model: gpt-5.6-sol
Overview
The incremental changes reject case-variant archive destinations in .NET on every operating system and reject case-variant Python manifest paths before selecting a manifest. The strongest guardrails are order-independent collision tests, per-skill failure isolation, .NET partial-extraction cleanup, and preservation of first-entry behavior for exact normalized .NET aliases. No Critical, High, or Medium defect introduced by the authoritative incremental range remained after reconciliation.
Reviewed the supplied incremental change set across correctness, security/reliability, architecture, and failure behavior.
No publishable findings remained after source verification for this scope.
BlueRaddish
left a comment
There was a problem hiding this comment.
The new Python and .NET checks still disagree for valid non-ASCII resource names. At head 7865be586a6a9d0bb732f3bcb8c87eb3e75a83f5, Python's FileSkill._reject_case_variant_names keys names with str.lower(), while .NET's HasCaseVariantNames uses StringComparer.OrdinalIgnoreCase.
For example, resources named Σ.md (U+03A3) and ς.md (U+03C2) are both accepted by the current Python FileSkill constructor. Their lowercase keys are σ.md and ς.md. On installed .NET 10.0.10, the actual comparer used by the C# check considers these names equal:
StringComparer.OrdinalIgnoreCase.Equals("\u03A3.md", "\u03C2.md") // TruePython's resource lookup has the corresponding difference: a skill containing only ς.md returns None for await skill.get_resource("Σ.MD"), whereas that name pair compares equal with .NET 10's comparer.
I reproduced the Python behavior using the unchanged complete FileSkill, Skill, SkillFrontmatter, SkillResource, and InlineSkillResource definitions and their validation helpers from this head, exercising the public constructor, get_resource, and read. The .NET comparison ran separately under System.Private.CoreLib 10.0.0.0 / runtime 10.0.10. Controls: readme.md/README.md was rejected by Python and compared equal in .NET; one.md/two.md was accepted and compared unequal; straße.md/strasse.md was accepted and compared unequal. Thus replacing lower() with unrestricted casefold() is not an exact match for the .NET semantics either.
Could the intended comparison contract be made explicit and covered with a non-ASCII regression case? If the goal is the same accepted names across the two SDKs, both implementations need a shared comparison rule. If only ASCII case variants are in scope, documenting that boundary would avoid promising broader consistency.
Scope: this is a constructor/comparer consistency finding, not an end-to-end archive, filesystem-discovery, MCP, or security finding. I did not run the full Python or .NET owner suites. The installed .NET Framework 4.8 comparer returned unequal for the sigma pair, so the result above specifically concerns the tested .NET 10 target; it is not a claim about every supported runtime.
AI assistance: Codex assisted with the analysis, isolated reproduction, and this review.
Co-authored-by: Copilot App <[email protected]>
|
BlueRaddish Thanks for the detailed examples! Updated the docs and added Unicode regression tests. Each SDK keeps its existing comparison rule— |
Co-authored-by: Copilot App <[email protected]>

Motivation & Context
Reject case-variant file skill names in Python and .NET, including MCP archives.
Description
Skills with resource or script names that differ only by case are skipped with a logged message. Resources and scripts are checked separately, and other skills continue loading.
Python compares names using
str.lower(); .NET usesStringComparer.OrdinalIgnoreCase. Unicode equivalence can differ between SDKs and .NET runtimes.SKILL.mdandskill.md.Related Issue
N/A - file skill name consistency changes.
AI Assistance
AI assistance details: implementation, tests, documentation, and this PR description.
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.