Skip to content

server: memorySkillFiles.list() and read() do not normalize or validate non-canonical relative paths #3102

Open

Description

@shafiktanbir

Quick question on memorySkillFiles in packages/server/src/ext/skills/files.ts (lines 53-67 on main commit 46d9c99). indexRecord normalizes keys via splitRelativePath(key, 'file path').join('/') when building the dirs and files maps, but memorySkillFiles.list(dir) and memorySkillFiles.read(path) look up dir and path directly without normalizing them through splitRelativePath first (unlike nodeSkillFiles in node.ts).

Because of that, calling list('sub/') or read('./SKILL.md') throws SkillFileNotFoundError, and in read(path) the Object.hasOwn(files, path) check runs before index ?? reindex(), so calling read() before list() bypasses splitRelativePath validation when path matches a raw key on files. Should list() and read() normalize dir and path via splitRelativePath? Happy to open a quick PR with unit tests if you want!

Activity

  1. xubenqiang commented on Oct 26, 2026

    @xubenqiang

    I went ahead with this: #3104 normalises dir/path through splitRelativePath in both list() and read() (with '' still meaning the record root), so sub/, ./SKILL.md and sub//other.txt resolve, and a .. segment is rejected before the hasOwn fast path. Six tests cover those shapes plus the unchanged missing-path errors, and there is a patch changeset.

  2. added
    v2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes
    on Oct 27, 2026
  3. Ben-Home commented on Oct 28, 2026

    @Ben-Home

    Yes to normalising in list() and read(), and the detail in your second paragraph is worth more than the normalisation request itself.

    Object.hasOwn(files, path) running before index ?? reindex() means a read() that happens before any list() can match a raw key on files that splitRelativePath would have rejected or canonicalised. That is a validation bypass, not a convenience gap, so it wants its own test rather than being folded into the normalisation change.

    Three behaviours I would pin in units:

    1. list('sub/'), list('./sub') and list('sub//') return the same set as list('sub').
    2. read('./SKILL.md') and read('SKILL.md') return the same record, and a path that escapes the skill root throws instead of resolving.
    3. read(<a raw key that is not canonical>) on a fresh instance throws the same error that list() then read() would, which is the assertion that closes the bypass.

    Point 3 is the one most likely to fail against main today, and it is also the regression test that stops the ordering from being reintroduced later. nodeSkillFiles already routing through splitRelativePath is a good precedent, and mirroring it in files.ts rather than adding a second normalisation path keeps the two implementations comparable for whoever touches the third one.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    v2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions