Skip to content

ptc-fs-mcp 0.4.0

Latest

Choose a tag to compare

@andreasronge andreasronge released this 07 Sep 14:56
v0.4.0
447292e

Makes a real repository usable as a root, and closes two confinement bugs
present in 0.3.0. Upgrading is recommended for anyone serving a root that
contains symlinks or relies on --exclude.

Security

  • Ancestor symlinks were followed. O_NOFOLLOW guards only the name it
    opens, so an intermediate link was resolved by the kernel before the flag
    applied: read_text_file {"path":"link/creds.txt"} returned bytes from
    outside the root. Every path component is now checked before the open, and
    list_directory validates its prefix the same way.
  • An exclude did not cover what was under it. --exclude secret pruned the
    walk at that directory but never matched secret/creds.txt, so naming the
    path directly still served it. Excludes now cover the whole ancestor chain,
    and the refusal names the exclude rule that refused.
  • Host paths could leak in errors. A path component long enough to fail
    lstat produced a Node message carrying the root's absolute location. All
    such failures now read as "not served".
  • Built-in excludes are matched case-insensitively, so NODE_MODULES/pkg.js
    cannot alias past the excluded spelling on a case-insensitive filesystem. An
    explicit --exclude stays case-sensitive.

Added

  • Built-in excludes. Dependency and tool output (node_modules, _build,
    .git, .venv, .next, __pycache__, and similar) plus credential
    filenames (.env, .env.*, *.pem, *.p12, *.pfx, SSH private keys) are
    excluded by default. --no-default-exclude drops the list. build, dist,
    target, coverage, and *.key are deliberately not excluded — each is
    an ordinary word or the Keynote extension, and hiding real data silently is
    the worse failure.
  • Multi-term search. search_files and search_text accept any_of (up to
    16 literal substrings) instead of query, and case_insensitive to fold
    ASCII letters. A cursor is bound to the terms and folding it was issued for.
  • start_line on read_text_file. Page from a 1-based line, so reaching
    row 4,000 of a CSV costs one page instead of pushing 3,999 rows through the
    result budget first. byte_offset stays absolute, so the read is still
    citable. Only the bytes actually returned have to decode.
  • Traversal ceilings on the CLI: --max-files, --max-directories,
    --max-depth, --max-entries, and --max-scan-bytes (minimum 16384).

Changed

  • list_directory no longer costs an inventory. It probes each child and
    stops at the first served file, preserving the rule that a directory is
    listed exactly when it holds something served.
  • Walk ceilings raised to admit a real repository (50000 files, 50000
    directories, depth 64, 1000000 entries).
  • search_text skips binary files. A file is skipped whole when one line in
    its opening 8 KiB both contains a NUL and fails to decode as UTF-8 — both
    signals, on the same line. On one 374 MB checkout this skipped 146 MB of
    compiled artifacts whose every line would have been dropped anyway. Listings
    stay content-blind, and read_text_file still refuses the file by name.
    Caveat: a file that begins with a binary header but holds real text further
    in is skipped whole.
  • A bare . names the root, as the empty string does. ./lib and lib/.
    still carry a dot segment and are still rejected.
  • inventory and directoryListing are no longer exported; a test pins the
    export list.
  • Development moved to pnpm. This does not affect consumers.

Upgrading

The built-in exclude list can hide paths a working --include used to serve.
When a literal --include is covered by a built-in exclude, startup says so on
stderr and names the flag that turns the list off. Run with
--no-default-exclude to restore 0.3.x behavior exactly.