Skip to content

fix(skills): make the wire probe's leak check find a path under its temp dir - #575

Merged
ThomasK33 merged 7 commits into
mainfrom
skills-wire-probe-leak-run-dir
Nov 12, 2026
Merged

ThomasK33 merged 7 commits into
mainfrom
skills-wire-probe-leak-run-dir

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Why

The operator's goal is to land the code-shape refactor of the plugin on main in small PRs, fixes first. The tools/call wire probe (#517) checks that no frame that the plugin server sends leaks a path. The launcher gives nvim its own HOME, XDG folders, TMPDIR and CLAUDE_CONFIG_DIR inside its temp dir (/tmp/wire-probe.XXXXXX), and the lock file is there too. The checker looked only for the home folder that the password database gives. So a frame that holds vim.fn.expand("~"), vim.env.HOME, a stdpath() path or the lock file path passed with cases=44 failed=0 leaks=0 on both Neovim versions. The page said that the probe checks for "the user's home folder" and "a path under the real home folder", and for "a Lua source path" where the rule looks for a Lua position (.lua: and a digit).

What changed

  • The checker (proof-tools-call-wire.mjs) rewrites each of four paths to a placeholder by plain text, as main does for the tree and the home folder: the work folder to $CWD, the temp dir to $RUN, the tree to $TREE and the real home folder to $HOME, in that order. $RUN joins PLACEHOLDERS, PATHS and LEAK, so a frame that holds a path under the temp dir is a leak. The launcher passes the temp dir as a new fourth argument, after the work folder.
  • The launcher makes the work folder in a second temp dir, /tmp/wire-probe-cwd.XXXXXX (the folder is work in it, as the case table names it), beside the temp dir and not in it. The two names differ after wire-probe (. against -cwd.), and mktemp fills XXXXXX with letters and digits, so neither path is a prefix of the other. The launcher also asserts it. So no path under the temp dir starts with the text of the work folder, and the checker needs no rule for where a path ends inside raw JSON text. Earlier versions of this PR had such a rule (a text prefix, a whole-path boundary on every path, then on the work folder only), and each drew one more case in review.
  • The cleanup of the launcher removes both temp dirs, in the same function and the same way. The cleanup rule of AGENTS.md ("Wire probe cleanup": nvim is a direct child of the script, checks its parent every half second and stops its server, so the lock file is gone within 5 seconds after the script ends) is unchanged and still true. The second dir adds no second cleanup mechanism.
  • The checker's refusal of a path with a double quote, a backslash, a control character or DEL covers the temp dir path by itself now, and the message names "the temp dir". With a backslash in the temp dir path, nvim fails first (nvim wrote no lock file in 10 s). Both end with exit 2 before a case is sent.
  • The page (features/tools-call-wire.md), lines 3, 12, 23, 31, 37, 39, 40 and 41, says that the check finds four things: a Lua position, a path under the temp dir, the tree and the home folder. It says what lives in the temp dir (HOME, the XDG folders, TMPDIR, CLAUDE_CONFIG_DIR and the lock file), that each rewrite is plain text, where the work folder lives and why no path under the temp dir can read as the work folder, that the script removes both temp dirs, and that a home folder of / switches the home check off, because the checker keeps a path only when it is longer than one character. Only line 3 said "a Lua source path"; it now says "a Lua position (.lua: and a digit)". The stdpath() paths are under the temp dir, except config_dirs and data_dirs, which are system folders.
  • AGENTS.md, the line "Wire probe cleanup": "Do not add a second cleanup mechanism to make that time shorter." becomes "Do not add a second cleanup mechanism." The old words limited the ban to one purpose, and the rule bans a second cleanup mechanism for any purpose. Nothing else in that file changes.
  • The checker and the launcher together change 48 lines against the base (32 added, 16 deleted). The four files of the PR change 66 lines (41 added, 25 deleted).
  • The first automated code review raised a P2 on features/tools-call-wire.md line 39: the sentence "the temp dirs can stay, and neither holds a token" is false when a SIGKILL of the launcher lands after its cleanup has killed a blocked nvim and before the run dir is removed. A verifier had found the earlier form of the sentence false in another case. The last commit (c33e1d894d12e8a61463481d8c6550b8ca82919f) cuts the token claim instead of refining it: line 39 says only that the temp dirs can stay, and the next sentences of the same line say when the lock file keeps its token (the 5-second bound needs nvim's main loop to run, and a SIGKILL of the whole process group leaves the lock file with its token in the 0700 temp dir). Its parent commit (cecf52dbae3e59435e2e1f33da10d232f4c6e5fe) tried a longer sentence with a list of cases, and the last commit replaces it: the line now equals the line of 601c4064 without , and neither holds a token.

How I proved it

Every run uses PATH with only the node dir, /usr/bin and /bin, an absolute Neovim path (VCN_NVIM), a scratch HOME, and an empty environment otherwise (env -i). The launcher gives nvim its own HOME, XDG folders and CLAUDE_CONFIG_DIR. No claude runs. The runs use the verifier's probe.sh and mutant list, with these changes: probe.sh reads the temp dir from CLAUDE_CONFIG_DIR (it read it from WIRE_CWD) and reports the work folder's temp dir too, and the builder takes the commit and the checkout of this head. The ten r_* mutants below are mine. The matrix has 68 runs across the two Neovim versions, and the host load was 23 to 28 (evidence/option-b-r4/matrix.txt).

  1. The unchanged plugin passes on Neovim 0.12.3 and v0.8.0: cases=44 failed=0 leaks=0, exit 0.
  2. Ten mutants put the temp dir into the -32602 data of the entry check, in the spellings that earlier rounds used for the work folder: the temp dir itself, a path below it, plus X/file.txt, plus -backup/file.txt, plus : now, plus now, plus one backslash, plus two backslashes, plus \/sub, and plus "X. All fail on both versions: cases=44 failed=31 leaks=31, with 31 lines that name $RUN.
  3. The same spellings on the work folder pass on both versions: the work folder itself, a path below it, and the work folder plus X/file.txt, -backup/file.txt, : now, one backslash, two backslashes, \/sub and "X (cases=44 failed=0 leaks=0). The work folder lies beside the temp dir, so text that starts with its path is no path under the temp dir, and a frame may hold the work folder. The parent of the work folder plus " now" passes for the same reason. With the previous commit of this branch (8716d76a), the work folder plus one backslash and plus two backslashes passed although the work folder was under the temp dir there (the finding of the second verify round), and the temp dir plus one backslash failed.
  4. The four mutants of the first version (vim.fn.expand("~"), vim.env.HOME, vim.fn.stdpath("data") and the lock directory) fail on both versions: 31 of 44 cases, 31 lines name $RUN. A Lua position, the tree path plus ": x", the tree path, the real home folder (also plus ": now") and the token (<holds the auth token> in 31 lines) fail 31 of 44 cases on both versions.
  5. After each of the 68 runs, neither temp dir was left, and /tmp held no wire-probe* entry after the matrix.
  6. A SIGKILL of the launcher alone, right after the lock file appeared, on both versions (4 runs, nvim was alive and stopped its server): the lock file and nvim were gone after 222 to 432 ms. Both temp dirs stayed, the first with 3 files (2 on v0.8.0) and the second with none, and no run of 8 characters of the token was in a file of either dir or in the output (evidence/option-b-r4/kill-results.txt).
  7. Run through the launcher (a verifier's run), a temp dir path with a double quote, a newline or DEL ends with exit 2 and the message "the path of the temp dir holds ...", before a case is sent. A backslash in the temp dir path ends with exit 2 and nvim wrote no lock file in 10 s, because nvim fails first. My own run (evidence/option-b-r4/refusal.txt) started the checker alone, and there a backslash gives the temp dir message too.
  8. The token: in the 51 of 68 runs where the probe read the throwaway server's token, no output line holds a run of 8 or more of its characters. In the other 17 runs it did not read the token in time, so those runs carry no token count.
  9. Gates on the last commit c33e1d894d12e8a61463481d8c6550b8ca82919f (each file ends with its exit code): mise run format-check, mise run check (CASES 5 for proof-static.sh, 0 warnings / 0 errors in 185 files) and check.sh --hash-only (covered.lock matches the code) all pass. mise run test is not run: no line of lua/ or plugin/ changes.
  10. Independent checks: the first version (d649d11b) had one finding in the first verify round. The head 8716d76a had one finding in the second round, which this commit removes by construction. b64a3414 had no verify round. In the third round, one fresh verifier passed 387078bd with one wording finding: the page said that both temp dirs stay after a SIGKILL of the launcher, but a kill during the cleanup leaves only the work folder's temp dir. The commit 601c4064 fixes that one page line. The first automated code review then raised one P2 on it (the last bullet of "What changed" says what it found). The two last commits change only that same page line, so no run above depends on them.

What this PR does not cover

  • Another spelling of a path stays outside the check, as before: the other name of a symlink, a relative path, ~ as text. The page says so. This PR adds no detector for one.
  • A path that is neither under the temp dir, the tree nor the home folder is not found (/usr/share/... for example). The work folder's own temp dir (/tmp/wire-probe-cwd.XXXXXX, the parent of the work folder) is not looked for either.
  • The work folder is not looked for: a frame may hold it, with any text after it.
  • The launcher is the only caller of the checker, and it passes the work folder and the temp dir as its third and fourth arguments.
  • After a SIGKILL of the launcher, the temp dirs can stay, as the one temp dir did before. Whether the lock file keeps its token depends on nvim: the page says when, and this PR makes no general claim about it.
  • The base is an older main (523ee200). The files of this PR are the same on main now, and a text merge into main is clean. This PR and part 1 of the wire probe's second follow-up (skills-wire-probe-table-1) change adjacent lines of the page (the call-arguments to call-no-leak lines): one text conflict, which the PR that lands second resolves by taking part 1's three lines and this PR's call-no-leak line.

Generated with xum • Model: anthropic:claude-sonnet-5-5 • Thinking: xhigh

…emp dir

The launcher gives nvim its own HOME, XDG folders, TMPDIR and CLAUDE_CONFIG_DIR inside
its temp dir (/tmp/wire-probe.XXXXXX). The checker looked only for the home folder of the
password database, so a frame that held vim.fn.expand("~"), vim.env.HOME, a stdpath() path or
the lock file path passed with leaks=0.

The checker now rewrites the rest of the temp dir to $RUN, after the work folder ($CWD),
and counts $RUN as a leak. The temp dir is the parent of the work folder that the launcher
already passes, so the launcher gives no new argument. The refusal of a path with a double
quote, a backslash or a control character covers the temp dir path too, because it loops over
the same list.

The page says the check finds four things, says what lives in the temp dir, and says that a
home folder of "/" switches the home check off. AGENTS.md drops "to make that time shorter"
from the wire probe cleanup rule.
…whole path

The checker rewrote each path as a text prefix. The work folder is the one path under the
temp dir that a frame may hold, so a path that only starts with its text (the work folder
plus "X" or "-backup") read as $CWD plus a suffix and passed.

A placeholder now stands for a whole path. The rewrite replaces a path only where "/", the
end of a JSON string (a quote, with the backslashes of a nested string before it) or the end
of the text follows it. The work folder still goes first, so the work folder and the paths
below it pass, and a sibling of it reads as a $RUN path and fails.

The page says this where the rewrite is described, and fixes three sentences that were not
true: the work folder is under the temp dir and may appear in a frame; a bad character in the
temp dir path is refused through the work folder path, and a backslash makes nvim fail first;
two of the stdpath() paths (config_dirs and data_dirs) are system folders.
…r only

b64a341 rewrote every placeholder only as a whole path. Then a leak path followed by a
space or a colon (the run dir plus " now", the home folder plus ": now") was not rewritten,
and the check missed it. The text-prefix rewrite found it.

Only the work folder, the one path that a frame may hold, is rewritten as a whole path: where
"/", the end of a JSON string or the end of the text follows it. The run dir, the tree and the
home path are rewritten wherever their text appears, as before: an over-match of a leak path
is a leak. The work folder followed by other text reads as a run dir leak.

The page and the comments say this.
…run dir

The checker found the work folder as a text prefix of the run dir, and a text rule that tells where a path ends inside
raw JSON drew one more encoding case per review round (the work folder followed by an escaped backslash at the end of
a JSON string passed). The boundary rule is removed by construction.

The launcher makes the work folder in a second temp dir, /tmp/wire-probe-cwd.XXXXXX, beside the run dir
/tmp/wire-probe.XXXXXX and not in it. The two names differ after "wire-probe", and mktemp fills XXXXXX with letters and
digits, so neither path is a prefix of the other. The launcher asserts it, and passes the run dir to the checker as a
fourth argument. The checker rewrites every placeholder by plain text, as main does, with the work folder first. The
cleanup removes both temp dirs in the same place. Its refusal of a path with a double quote, a backslash or a control
character now covers the run dir path by itself.

The page says where the work folder lives and that each rewrite is plain text, and drops the sentences about a
whole-path boundary.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

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

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-12T20:01:44.918811Z c33e1d8 Manual request
🔒 Security Review ✅ Completed 2026-11-12T20:05:27.785554Z c33e1d8 Manual request
ℹ️ 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.

@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: 601c406499

ℹ️ 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".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: 601c406499

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: c33e1d894d

ℹ️ 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".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: c33e1d894d

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ThomasK33
ThomasK33 added this pull request to the merge queue Nov 12, 2026
Merged via the queue into main with commit 2e01c6b Nov 12, 2026
8 checks passed
@ThomasK33
ThomasK33 deleted the skills-wire-probe-leak-run-dir branch November 12, 2026 20:46
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.

1 participant