Skip to content

.NET, Python: Reject file skills with resource or script names that differ only by case - #10158

Merged
SergeyMenshykh merged 7 commits into
microsoft:mainfrom
SergeyMenshykh:reject-case-variant-skill-file-names
Nov 10, 2026
Merged

SergeyMenshykh merged 7 commits into
microsoft:mainfrom
SergeyMenshykh:reject-case-variant-skill-file-names

Conversation

@SergeyMenshykh

@SergeyMenshykh SergeyMenshykh commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

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 uses StringComparer.OrdinalIgnoreCase. Unicode equivalence can differ between SDKs and .NET runtimes.

  • Python: validate file skill names and reject archive manifest paths that differ only by case, such as SKILL.md and skill.md.
  • .NET: validate file skill names and reject case-variant archive paths before extraction can discard them on Windows. Exact duplicate archive paths still keep the first file with a warning.
  • Add regression tests for archive order, name collisions, Unicode comparison rules, and unaffected skills; update the Python consumer documentation.

Related Issue

N/A - file skill name consistency changes.

AI Assistance

  • No material AI assistance was used.
  • This is an AI-assisted contribution. I reviewed, understood, and verified all submitted content and accept responsibility for it.

AI assistance details: implementation, tests, documentation, and this PR description.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR links to an agreed issue with no competing open PR, or the Related Issue section documents a trivial-change or repository-automation exception.
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

SergeyMenshykh and others added 2 commits November 6, 2026 18:52
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]>
Copilot AI balanced review requested due to automatic review settings November 6, 2026 19:47
@agent-framework-automation agent-framework-automation Bot added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python .NET Usage: [Issues, PRs], Target: .Net labels Nov 6, 2026
@github-actions github-actions Bot changed the title Python, .NET: Reject file skills with resource or script names that differ only by case Python: Python, .NET: Reject file skills with resource or script names that differ only by case Nov 6, 2026
@github-actions github-actions Bot changed the title Python: Python, .NET: Reject file skills with resource or script names that differ only by case .NET: Python, .NET: Reject file skills with resource or script names that differ only by case Nov 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 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

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

@SergeyMenshykh SergeyMenshykh changed the title .NET: Python, .NET: Reject file skills with resource or script names that differ only by case .NET, Python: Reject file skills with resource or script names that differ only by case Nov 6, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 BlueRaddish left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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") // True

Python'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.

@SergeyMenshykh

Copy link
Copy Markdown
Collaborator Author

BlueRaddish Thanks for the detailed examples! Updated the docs and added Unicode regression tests. Each SDK keeps its existing comparison rule—str.lower() in Python and OrdinalIgnoreCase in .NET—and the PR description now clarifies that Unicode behavior can differ.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 10, 2026
@SergeyMenshykh
SergeyMenshykh added this pull request to the merge queue Nov 10, 2026
Merged via the queue into microsoft:main with commit cb9dc05 Nov 10, 2026
55 checks passed
@SergeyMenshykh
SergeyMenshykh deleted the reject-case-variant-skill-file-names branch November 10, 2026 10:38
@github-project-automation github-project-automation Bot moved this from In Review to Done in Agent Framework Nov 10, 2026

This branch was previously deployed

1 inactive deployment
github-app-auth — 18cc83de Deployed Nov 9, 2026 by SergeyMenshykh via add_label #27434
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs .NET Usage: [Issues, PRs], Target: .Net python Usage: [Issues, PRs], Target: Python

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants