Skip to content

feat(vscode): add filesystem validation protocol for file links - #11218

Merged
markijbema merged 5 commits into
Kilo-Org:mainfrom
sylwester-liljegren:feat/file-links-2-fs-validation
Jun 29, 2026
Merged

feat(vscode): add filesystem validation protocol for file links#11218
markijbema merged 5 commits into
Kilo-Org:mainfrom
sylwester-liljegren:feat/file-links-2-fs-validation

Conversation

@sylwester-liljegren

@sylwester-liljegren sylwester-liljegren commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Issue

Re-split of #10340 per maintainer feedback (no separate tracking issue exists). PR 1 of 2.

Stack (review/merge in order): #11218 (this PR) → #11219

The maintainer suggested three pieces (path parsing, filesystem validation, UI rendering). Filesystem validation is genuinely self-contained and is this PR. Path-parsing and UI rendering had to be combined in #11219: the path-parsing API change and its only consumers (marked.tsx, message-part.tsx) can't compile apart, and the UI also depends on the data-context hook added here — so a fully separate path-parsing PR can't pass CI. This 2-PR stack keeps both PRs green while still isolating the validation layer for separate review.

Context

The extension-side plumbing that confirms which inline-code candidates are real files, plus a fallback for opening links whose exact path is missing. No parsing or rendering changes here — this layer is independent and compiles on its own.

Implementation

  • Adds a validateFiles request/response round-trip (webview ↔ extension) so the webview can ask "which of these candidate paths exist?" and only promote real ones.
  • New kilo-provider/file-links.ts stat-checks candidate paths (files only, not directories) relative to the session/workspace root.
  • Routing is integrated into kilo-provider/editor-actions.ts (where main now keeps the editor open/openFile logic after its refactor), so KiloProvider only needs a one-line post callback rather than the larger change from the original PR.
  • openFile gains a dead-link fallback: workspace filename search (opens on a single match, quick-pick on multiple, "File not found" warning on none).
  • packages/ui/src/context/data.tsx exposes the validateFiles function on the data context (and App.tsx wires it to the extension).

Screenshots / Video

N/A for the core plumbing. The visible surfaces (the "File not found" warning / multi-match quick-pick) are exercised end-to-end in #11219.

How to Test

Manual/local verification

  • Agent-executed: bun run typecheck in both packages/ui/ and packages/kilo-vscode/ (extension + webview) — green. This branch compiles standalone (contrast with the original 3-way split, where the intermediate PRs could not).
  • Agent-executed: bun test tests/unit/kilo-ui-contract.test.ts in packages/kilo-vscode/ — 28 pass.

Reviewer test steps

The behavior you can exercise directly here is the openFile dead-link fallback. File references in chat are already clickable on main (the existing renderer), and every click routes through the openFile this PR enhances. Launch the dev extension from this branch (bun run extension from the repo root), open a real project, and in a chat:

  1. Real file (no regression): ask the agent to reply with exactly `package.json` and click it → the file opens.
  2. Dead link (new): reply with exactly `src/totally-missing.ts` and click it → a "File not found: src/totally-missing.ts" warning. (On main this silently did nothing.)
  3. Ambiguous basename (new): reply with exactly `index.ts` and click it → a quick-pick of matching files; choosing one opens it.

The same fallback also covers file mentions and tool-output paths (same openFile entry point) — e.g. a tool-output path that has since been moved/deleted.

Note: the validateFiles request/response added here has no UI consumer in this PR — its only consumer is the post-render pass in #11219, so it is dormant on this branch. Verify it by reviewing file-links.ts (a ~20-line stat-check) plus the round-trip wiring in editor-actions.ts / App.tsx / data.tsx, or exercise it end-to-end on the stacked #11219.

Blocked checks and substitute verification

  • The full-monorepo pre-push typecheck fails locally only on @kilocode/kilo-jetbrains (needs JDK 21, not installed on this machine) — unrelated to this change. Substitute: ran the ui + kilo-vscode (extension + webview) typechecks directly (agent-executed, green); CI runs the full typecheck.

Checklist

  • Issue linked above, or exception explained
  • Tests/verification described
  • Screenshots/video included for visual changes, or marked N/A
  • Changeset considered for user-facing changes (carried in feat(ui): parse and render clickable file links #11219)
  • I personally reviewed the diff and can explain the changes, including any AI-assisted work.

Add a validateFiles request/response round-trip between the webview and
the extension so the webview can confirm which inline code-span
candidates are real files before promoting them to clickable links.

The extension stat-checks candidate paths (new file-links.ts) and
replies with the subset that exist. Routing lives in editor-actions
alongside the other editor open actions, and openFile now falls back to
a workspace filename search (single match opens, multiple prompts) with
a "File not found" warning when a clicked path cannot be resolved.
@sylwester-liljegren
sylwester-liljegren force-pushed the feat/file-links-2-fs-validation branch from dc5ccb4 to ed5fc5a Compare June 14, 2026 17:37
@sylwester-liljegren
sylwester-liljegren marked this pull request as ready for review June 14, 2026 21:37
*/
function findFallback(filePath: string, line?: number, column?: number): void {
const name = filePath.split(/[\\/]/).pop() || filePath
Promise.resolve(vscode.workspace.findFiles(`**/${name}`, "**/node_modules/**", 5)).then(

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.

WARNING: Glob-special characters in filenames are not escaped before building the pattern.

Filenames common in Next.js / Remix projects — [id].tsx, [...slug].tsx — contain [, ] which are glob metacharacters. Passing them unescaped in **/${name} will produce an invalid glob and findFiles will return zero matches, falling through to the "File not found" warning even when the file exists.

Consider escaping metacharacters before constructing the pattern:

const escaped = name.replace(/[\[\]{}?*!()]/g, "\\$&")
vscode.workspace.findFiles(`**/${escaped}`, "**/node_modules/**", 5)

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread packages/kilo-vscode/src/kilo-provider/editor-actions.ts Outdated
Comment thread packages/kilo-vscode/src/kilo-provider/editor-actions.ts Outdated
Comment thread packages/kilo-vscode/src/kilo-provider/editor-actions.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (1 file)
  • packages/kilo-vscode/src/kilo-provider/file-links.ts
Previous Review Summaries (2 snapshots, latest commit d497be6)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit d497be6)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0

Fix these issues in Kilo Cloud

Resolved in d497be6311:

  • packages/kilo-vscode/src/kilo-provider/file-links.ts: plain absolute paths, UNC paths, and ../ traversal are now rejected before fs.stat
  • packages/kilo-vscode/src/kilo-provider/editor-actions.ts: fallback search is now scoped to the active session directory instead of the opened workspace
  • packages/kilo-vscode/src/path-utils.ts: filename escaping now uses VS Code-compatible bracket escaping for routes like [id].tsx
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-vscode/src/path-utils.ts 43 contains() only checks lexical containment, so a symlink inside the session root can still make validateFiles() probe files outside the trusted directory.
Other Observations (not in diff)

None.

Files Reviewed (4 files)
  • packages/kilo-vscode/src/kilo-provider/editor-actions.ts - 0 issues
  • packages/kilo-vscode/src/kilo-provider/file-links.ts - 0 issues
  • packages/kilo-vscode/src/path-utils.ts - 1 issue
  • packages/kilo-vscode/tests/unit/path-utils.test.ts - 0 issues

Previous review (commit 81aad79)

Status: No Issues Found | Recommendation: Merge

All four issues from the previous review have been resolved in commit 81aad79f98:

  • Glob metacharacter escaping (was WARNING): filenames like [id].tsx are now escaped before constructing the findFiles pattern
  • showQuickPick rejection handler (was SUGGESTION): error handler added
  • showTextDocument result dropped (was SUGGESTION): rejection handler added
  • Inline type-cast workaround (was SUGGESTION): id and paths are now properly typed on the message parameter
Files Reviewed (1 file)
  • packages/kilo-vscode/src/kilo-provider/editor-actions.ts — 0 issues

Reviewed by gpt-5.4-2026-03-05 · 170,066 tokens

Review guidance: REVIEW.md from base branch main

Address kilo-code-bot review on Kilo-Org#11218:
- escape glob metacharacters in the workspace filename search so names
  like [id].tsx / [...slug].tsx resolve instead of falling through to
  the "File not found" warning
- add rejection handlers to showTextDocument (in show()) and showQuickPick
  so VS Code API errors surface instead of being silently swallowed
- type the validateFiles message fields on the handleEditorAction
  parameter instead of inline casts
sylwester-liljegren pushed a commit to sylwester-liljegren/kilocode that referenced this pull request Jun 14, 2026
Address kilo-code-bot review on Kilo-Org#11218:
- escape glob metacharacters in the workspace filename search so names
  like [id].tsx / [...slug].tsx resolve instead of falling through to
  the "File not found" warning
- add rejection handlers to showTextDocument (in show()) and showQuickPick
  so VS Code API errors surface instead of being silently swallowed
- type the validateFiles message fields on the handleEditorAction
  parameter instead of inline casts
@sylwester-liljegren

Copy link
Copy Markdown
Contributor Author

Thanks for the review — addressed all four in 81aad79:

  • WARNING (glob escaping, findFallback): the workspace filename search now escapes glob metacharacters before building the findFiles pattern, so names like [id].tsx / [...slug].tsx resolve instead of falling through to the "File not found" warning.
  • SUGGESTION (showQuickPick): added a rejection handler so VS Code API errors surface.
  • SUGGESTION (showTextDocument in show()): added a rejection handler for the show path.
  • SUGGESTION (validateFiles type-cast): typed the id/paths fields on the handleEditorAction message parameter and dropped the inline casts (also lets TypeScript catch mismatches).

The same fix is applied to the stacked #11219, which carries its own copy of editor-actions.ts.

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

Thanks for splitting it up, i find a few issues still, could you look into that?

Comment thread packages/kilo-vscode/src/kilo-provider/file-links.ts Outdated
Comment thread packages/kilo-vscode/src/kilo-provider/editor-actions.ts Outdated
Comment thread packages/kilo-vscode/src/kilo-provider/editor-actions.ts Outdated
@sylwester-liljegren

Copy link
Copy Markdown
Contributor Author

Thanks for splitting it up, i find a few issues still, could you look into that?

Hi @markijbema ! Thanks for the feedback, I'll look into it later this day :)

Address markijbema review on Kilo-Org#11218:
- validateFiles now rejects candidates that resolve outside the session
  root (absolute paths elsewhere, UNC paths, ../ traversal) before any
  fs.stat, so auto-validated model output can't probe arbitrary host paths
- the openFile dead-link fallback searches the session dir via a
  RelativePattern instead of the whole opened workspace, so it can't cross
  into another worktree/branch
- use VS Code-compatible bracket glob escaping (`[id].tsx` -> `[[]id[]].tsx`)
  so dynamic-route filenames resolve instead of falling through

Adds focused unit tests for the new vscode-free `contains` and `escapeGlob`
helpers in path-utils.
sylwester-liljegren pushed a commit to sylwester-liljegren/kilocode that referenced this pull request Jun 18, 2026
Address markijbema review on Kilo-Org#11218:
- validateFiles now rejects candidates that resolve outside the session
  root (absolute paths elsewhere, UNC paths, ../ traversal) before any
  fs.stat, so auto-validated model output can't probe arbitrary host paths
- the openFile dead-link fallback searches the session dir via a
  RelativePattern instead of the whole opened workspace, so it can't cross
  into another worktree/branch
- use VS Code-compatible bracket glob escaping (`[id].tsx` -> `[[]id[]].tsx`)
  so dynamic-route filenames resolve instead of falling through

Adds focused unit tests for the new vscode-free `contains` and `escapeGlob`
helpers in path-utils.
@sylwester-liljegren

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — addressed all three in d497be6:

F1 (path containment): validateFiles now rejects any candidate that resolves outside the session root — absolute paths elsewhere, UNC paths (\\server\… / //server/…), and ../ traversal — via a new contains(root, candidate) helper, before any fs.stat. So once the stacked UI auto-validates code spans, model output can't probe arbitrary host paths.

F2 (worktree scoping): the openFile dead-link fallback now searches the session dir via new vscode.RelativePattern(vscode.Uri.file(dir), …) instead of the opened workspace, so it can't cross into another worktree/branch and open the wrong same-named file. (openFile already receives the session dir.)

F3 (glob escaping + test): replaced the backslash escaping with VS Code-compatible bracket escaping (escapeGlob), so [id].tsx[[]id[]].tsx and [...slug].tsx resolve instead of falling through. Both contains and escapeGlob are pure, vscode-free helpers in path-utils.ts with focused unit tests covering the [id].tsx / [...slug].tsx cases (and the traversal/UNC/absolute rejections for contains).

The same fix is applied to the stacked #11219.

// UNC candidates can trigger outbound filesystem requests on Windows — never allow them.
if (candidate.startsWith("\\\\") || candidate.startsWith("//")) return false
const base = path.resolve(root)
const rel = path.relative(base, path.resolve(base, candidate))

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.

WARNING: contains() only enforces lexical containment, so symlink escapes can still probe files outside the session root.

path.resolve() / path.relative() treat root/link/passwd as in-tree even when link is a symlink to /etc, so validateFiles() will still call workspace.fs.stat() on an external target through any checked-in symlink. Once the stacked UI auto-validates code spans, model output can still confirm arbitrary host files this way. Consider resolving the real path before the containment check, or rejecting symlinked path segments entirely.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Address kilo-code-bot review on Kilo-Org#11218: contains() is purely lexical, so a
checked-in symlink (e.g. root/link -> /etc) would pass the check and let
validateFiles stat an external target. validateFiles now resolves each
candidate's real path (symlinks included) and requires it to stay inside the
real session root before any stat, on top of the existing lexical pre-check
that still rejects UNC / absolute-outside / ../ without touching the disk.
sylwester-liljegren pushed a commit to sylwester-liljegren/kilocode that referenced this pull request Jun 18, 2026
Address kilo-code-bot review on Kilo-Org#11218: contains() is purely lexical, so a
checked-in symlink (e.g. root/link -> /etc) would pass the check and let
validateFiles stat an external target. validateFiles now resolves each
candidate's real path (symlinks included) and requires it to stay inside the
real session root before any stat, on top of the existing lexical pre-check
that still rejects UNC / absolute-outside / ../ without touching the disk.
@sylwester-liljegren

Copy link
Copy Markdown
Contributor Author

Good catch — addressed in c2076c8.

contains() is intentionally lexical, so per your first suggestion validateFiles now resolves the real path before trusting it:

  • it realpath()s the session root once and each candidate (resolving any symlinked segments), then requires the real target to still be inside the real root via contains(realRoot, real) before any stat;
  • the cheap lexical contains(root, p) pre-check still runs first, so UNC / absolute-outside / ../ candidates are rejected without touching the disk at all.

So a checked-in symlink like root/link -> /etc now resolves to /etc/…, fails the real-path containment check, and returns null — the external target is never confirmed back to the webview. Same fix applied to the stacked #11219.

@johnnyeric
johnnyeric requested a review from markijbema June 19, 2026 12:53
@sylwester-liljegren

Copy link
Copy Markdown
Contributor Author

Thanks for your approval, @markijbema ! I'll update the latter PR with images later today so that you may start reviewing it. Btw, should I do something about the failing check "Check forbidden strings / Check forbidden strings (pull_request)"?

@markijbema
markijbema merged commit ac22b40 into Kilo-Org:main Jun 29, 2026
21 of 22 checks passed
@markijbema

Copy link
Copy Markdown
Contributor

Nopes, that check was already fixed on master, and i can override it. Thanks!

TahsinArafat pushed a commit to TahsinArafat/sleepy-vscode that referenced this pull request Jul 5, 2026
Address kilo-code-bot review on Kilo-Org#11218:
- escape glob metacharacters in the workspace filename search so names
  like [id].tsx / [...slug].tsx resolve instead of falling through to
  the "File not found" warning
- add rejection handlers to showTextDocument (in show()) and showQuickPick
  so VS Code API errors surface instead of being silently swallowed
- type the validateFiles message fields on the handleEditorAction
  parameter instead of inline casts
TahsinArafat pushed a commit to TahsinArafat/sleepy-vscode that referenced this pull request Jul 5, 2026
Address markijbema review on Kilo-Org#11218:
- validateFiles now rejects candidates that resolve outside the session
  root (absolute paths elsewhere, UNC paths, ../ traversal) before any
  fs.stat, so auto-validated model output can't probe arbitrary host paths
- the openFile dead-link fallback searches the session dir via a
  RelativePattern instead of the whole opened workspace, so it can't cross
  into another worktree/branch
- use VS Code-compatible bracket glob escaping (`[id].tsx` -> `[[]id[]].tsx`)
  so dynamic-route filenames resolve instead of falling through

Adds focused unit tests for the new vscode-free `contains` and `escapeGlob`
helpers in path-utils.
TahsinArafat pushed a commit to TahsinArafat/sleepy-vscode that referenced this pull request Jul 5, 2026
Address kilo-code-bot review on Kilo-Org#11218: contains() is purely lexical, so a
checked-in symlink (e.g. root/link -> /etc) would pass the check and let
validateFiles stat an external target. validateFiles now resolves each
candidate's real path (symlinks included) and requires it to stay inside the
real session root before any stat, on top of the existing lexical pre-check
that still rejects UNC / absolute-outside / ../ without touching the disk.
t7tran pushed a commit to t7tran/kilocode that referenced this pull request Aug 14, 2026
Address kilo-code-bot review on Kilo-Org#11218:
- escape glob metacharacters in the workspace filename search so names
  like [id].tsx / [...slug].tsx resolve instead of falling through to
  the "File not found" warning
- add rejection handlers to showTextDocument (in show()) and showQuickPick
  so VS Code API errors surface instead of being silently swallowed
- type the validateFiles message fields on the handleEditorAction
  parameter instead of inline casts
t7tran pushed a commit to t7tran/kilocode that referenced this pull request Aug 14, 2026
Address markijbema review on Kilo-Org#11218:
- validateFiles now rejects candidates that resolve outside the session
  root (absolute paths elsewhere, UNC paths, ../ traversal) before any
  fs.stat, so auto-validated model output can't probe arbitrary host paths
- the openFile dead-link fallback searches the session dir via a
  RelativePattern instead of the whole opened workspace, so it can't cross
  into another worktree/branch
- use VS Code-compatible bracket glob escaping (`[id].tsx` -> `[[]id[]].tsx`)
  so dynamic-route filenames resolve instead of falling through

Adds focused unit tests for the new vscode-free `contains` and `escapeGlob`
helpers in path-utils.
t7tran pushed a commit to t7tran/kilocode that referenced this pull request Aug 14, 2026
Address kilo-code-bot review on Kilo-Org#11218: contains() is purely lexical, so a
checked-in symlink (e.g. root/link -> /etc) would pass the check and let
validateFiles stat an external target. validateFiles now resolves each
candidate's real path (symlinks included) and requires it to stay inside the
real session root before any stat, on top of the existing lexical pre-check
that still rejects UNC / absolute-outside / ../ without touching the disk.
t7tran pushed a commit to t7tran/kilocode that referenced this pull request Aug 14, 2026
…links-2-fs-validation

feat(vscode): add filesystem validation protocol for file links
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants