All releases

v0.11.1

security, performance & stability hardening

Released on .

Security, performance, and stability hardening from a full codebase audit. No changes to the 3 tools' input/output contracts (auto_optimize, smart_file_read, code_execute). Every change below is backward-compatible.

Install or upgrade:

npx distill-mcp@0.11.1 setup    # or: bunx distill-mcp@latest setup

🔒 Security

  • ReDoS guard on ctx.search.grep. The grep pattern is LLM-authored and runs host-side (outside the QuickJS execution timeout) over up to 500 files. Catastrophic-backtracking or overlong patterns are now rejected via safe-regex2 before the regex is compiled.
  • WORKING_DIR is no longer exposed to guest code. The host bridge resolves paths host-side, so the absolute host path was only something guest code could exfiltrate past sanitizeError.
  • smart_file_read closes a validate-vs-read TOCTOU. The separate fs.access pre-check is gone; a single guarded read follows the symlink-resolved path that validation approved, applies the blocked-pattern policy to that effective path, and never leaks the host path on error.
  • More keyword-reconstruction vectors blocked in the static analyzer: atob, btoa, and String.fromCodePoint join String.fromCharCode on the layer-1 blocklist (QuickJS WASM stays the final containment).
  • sanitizeError widened to scrub /Users (macOS home), /tmp, /var, /opt, /private, and /srv, not just /home.
  • Language strings validated at the QuickJS guest→host boundary instead of being blind-cast to SupportedLanguage.

🐛 Fixed

  • Tree-sitter parsers recover from a failed init. Rust, Go, Python, PHP, and Swift parsers reset their init promise when WASM loading fails, so a transient failure (OOM, missing file) no longer poisons every later parse for the lifetime of the process. The warm-up failure is logged once per process.
  • Cache memory counter can no longer go negative. Concurrent writes to the same key could double-subtract memorySizeBytes into the negative, silently disabling memory-based eviction (unbounded cache growth). It is now clamped at 0.
  • Hardened CLI argument handling. main().catch handles non-Error throws and preserves the stack on startup failures; setup warns on unknown options instead of ignoring typos; analyze rejects a non-numeric or negative --threshold (which would otherwise silently match nothing).

⚡ Performance

  • QuickJS WASM loader is memoized and shared across execution paths. The production code_execute path was re-instantiating the entire WASM module on every call (~50-200ms + GC pressure); a single cached loader now serves both the production and isolation-test paths.
  • Log scorer memoizes scoreAll(), which was recomputed 3+ times per summary.
  • Log clustering falls back to Jaccard above 500 entries, bounding the O(n²) Levenshtein cost on large corpora. Small-corpus behavior is unchanged.
  • Cache size estimation uses a bounded structural probe instead of JSON.stringify-ing whole cached values just to measure them.

♻️ Changed

  • Renamed the sandbox SDK type CtxOptSDK to DistillSDK and removed residual CtxOpt branding.

🗑️ Removed

  • Dead shared/ module (utils.ts, constants.ts, types.ts, and its barrel) with no consumers. shared/path-security.ts, the only used member, stays.

Published to npm via Trusted Publishing (OIDC) with build provenance. Full changelog: CHANGELOG.md. Compare: https://github.com/ArthurDEV44/distill/compare/v0.11.0...v0.11.1