Skip to content

fix(skills): enforce manifest boundaries and capability negotiation - #1412

Merged
DaleSeo merged 3 commits into
modelcontextprotocol:mainfrom
tsarlandie-oai:codex/fix-skills-validation-and-negotiation
Nov 3, 2026
Merged

DaleSeo merged 3 commits into
modelcontextprotocol:mainfrom
tsarlandie-oai:codex/fix-skills-validation-and-negotiation

Conversation

@tsarlandie-oai

@tsarlandie-oai tsarlandie-oai commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #1392.

  • Skill::validate requires each static manifest URI to identify a file under the skill root. It rejects sibling directories, paths that escape the root, and paths ending in a literal or percent-encoded dot segment. Duplicate detection compares normalized file paths. A manifest may list refs/../invoice.md on its own, but listing it alongside invoice.md returns DuplicateResource. The comparison preserves interior empty segments, encoded separators, query strings, and fragments. Resource reads use the original advertised URI. Both skills/list and skills/get return -32603 for an invalid handler manifest. Skills manifest requirements
  • The client returns -32601 for skills/list and skills/get until the server declares the extension. One duplex transport test checks that those helpers and list_all_skills send zero bytes when peer information or the extension declaration is missing. Skills capability negotiation

Validation

  • cargo +stable test -p rmcp --features client --lib --test test_skills passed all 286 library tests and 14 Skills integration tests.
  • cargo +stable test -p rmcp --no-default-features --lib passed all 188 tests.
  • cargo +nightly fmt --all -- --check passed.
  • Scoped Clippy passed with -D warnings -A clippy::result_large_err. The allowance covers 13 diagnostics also present on the base commit with Rust 1.99.1.

@tsarlandie-oai
tsarlandie-oai requested a review from a team as a code owner October 30, 2026 00:24
@github-actions github-actions Bot added T-test Testing related changes T-core Core library changes T-model Model/data structure changes T-service Service layer changes labels Oct 30, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-03T00:20:09.211218Z 36203c3 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions
github-actions Bot requested a review from gocamille October 30, 2026 00:24
@github-actions

Copy link
Copy Markdown
Contributor

@gocamille is on triage rotation this week and is the reviewer for this one.

Labels come from the PR labeler. Please review the change.

Automated triage. Labels are a first pass and the assigned maintainer has the final say.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 757e5b30c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/rmcp/src/model/skill.rs Outdated
Comment on lines +330 to +331
let path = relative.split(['?', '#']).next().unwrap_or_default();
if path.is_empty() || path.ends_with('/') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject paths that normalize to directories

When a manifest contains skill://acme/billing/references/. (or references/sub/.., including percent-encoded forms), this check does not see a trailing slash and the loop finishes with positive depth, so Skill::validate() accepts it. URI dot-segment normalization turns each into .../references/, which is a directory rather than the file a SkillResource promises; this leaves malformed handler manifests passing the new validation. Reject terminal normalized ./.. segments or otherwise require the normalized path to end in a file segment.

Useful? React with 👍 / 👎.

DaleSeo
DaleSeo previously approved these changes Oct 30, 2026

@DaleSeo DaleSeo 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.

Thanks for the fix, @tsarlandie-oai ! Approving with a non-blocking suggestion.

Comment thread crates/rmcp/src/model/skill.rs Resolved
@DaleSeo
DaleSeo merged commit 3fc54d0 into modelcontextprotocol:main Nov 3, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-core Core library changes T-model Model/data structure changes T-service Service layer changes T-test Testing related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants