Commit 8b39ed1
fix(providers,cli,mcp): never descend into a symlink-to-directory
GAP-A02 is the standing rule: a symlink is unlinked, never followed.
`rmdir_recursive`, the used-bytes scan, the rm/purge plan, the MCP delete
preview and MCP storage_quota honoured it. Fifteen other recursive walks
did not. They gated the descent on `is_dir` alone, and `list` resolves a
symlink-to-directory to `is_dir = true` because callers need that to
render the entry, so every one of them walked straight into the link.
On a tree holding `sub/loop -> ..`, `lsjson -R` emitted 123 entries for
3 real ones, stopping only at the MAX_SCAN_DEPTH cap of 100, and `tree
-d 6` reported 6 directories and 3 files where 2 and 1 exist. The
`visited` sets do not catch the cycle: each pass yields a longer,
distinct path string. `build_tree` even carried a comment claiming
"Symlink loop detection" over one of them.
Put the invariant on the type that carries the data, as
`RemoteEntry::is_walkable_dir()`, and gate every descent on it. The
entry keeps `is_dir = true`: only the traversal is refused. Swapping the
guard naively would drop a symlink-to-directory into each walker's
`else` branch, where `get -r` would try to download it and `dedupe`
would hash it. `rmdirs` skips it entirely, out of both the descent stack
and the empty-directory candidate list, because SSH_FXP_RMDIR does not
follow links and removing the link because its target looks empty is not
this tool's call.
Two walks needed more than the guard. `tree`'s text renderer decided to
descend by sniffing a trailing `/` off the rendered name, which a
symlink-to-directory also carries; it now carries `walkable` explicitly
on its queue item. The local_tree agent tool typed entries with
`metadata()`, which follows links; it now uses `file_type()`, which does
not.
Providers other than SFTP always report `is_symlink = false`, so
`is_walkable_dir()` collapses to `is_dir` and nothing changes for S3,
WebDAV or FTP.
Live on a plain OpenSSH remote: the cycle tree drops from 123 entries at
depth 81 to 3 at depth 1, and `tree -d 6` from 6 dirs / 3 files to the
true 2 / 1. On the symlink matrix `link-to-dir` is still listed with
`IsDir: true` while `link-to-dir/inner.txt` is gone, and `ls -l`,
`rm --dry-run -r` and `size` are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent 3eebe9e commit 8b39ed1
3 files changed
Lines changed: 115 additions & 23 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1543 | 1543 | | |
1544 | 1544 | | |
1545 | 1545 | | |
1546 | | - | |
| 1546 | + | |
| 1547 | + | |
| 1548 | + | |
1547 | 1549 | | |
1548 | 1550 | | |
1549 | 1551 | | |
| |||
2197 | 2199 | | |
2198 | 2200 | | |
2199 | 2201 | | |
2200 | | - | |
| 2202 | + | |
| 2203 | + | |
| 2204 | + | |
2201 | 2205 | | |
2202 | 2206 | | |
2203 | 2207 | | |
| |||
2418 | 2422 | | |
2419 | 2423 | | |
2420 | 2424 | | |
2421 | | - | |
| 2425 | + | |
| 2426 | + | |
| 2427 | + | |
2422 | 2428 | | |
2423 | 2429 | | |
2424 | 2430 | | |
| |||
2531 | 2537 | | |
2532 | 2538 | | |
2533 | 2539 | | |
2534 | | - | |
| 2540 | + | |
| 2541 | + | |
| 2542 | + | |
2535 | 2543 | | |
2536 | 2544 | | |
2537 | 2545 | | |
| |||
2763 | 2771 | | |
2764 | 2772 | | |
2765 | 2773 | | |
2766 | | - | |
| 2774 | + | |
| 2775 | + | |
| 2776 | + | |
2767 | 2777 | | |
2768 | 2778 | | |
2769 | 2779 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7452 | 7452 | | |
7453 | 7453 | | |
7454 | 7454 | | |
7455 | | - | |
| 7455 | + | |
| 7456 | + | |
| 7457 | + | |
| 7458 | + | |
| 7459 | + | |
| 7460 | + | |
7456 | 7461 | | |
7457 | 7462 | | |
7458 | 7463 | | |
| |||
13787 | 13792 | | |
13788 | 13793 | | |
13789 | 13794 | | |
13790 | | - | |
| 13795 | + | |
| 13796 | + | |
| 13797 | + | |
13791 | 13798 | | |
13792 | 13799 | | |
13793 | 13800 | | |
| |||
28102 | 28109 | | |
28103 | 28110 | | |
28104 | 28111 | | |
28105 | | - | |
28106 | | - | |
| 28112 | + | |
| 28113 | + | |
| 28114 | + | |
| 28115 | + | |
| 28116 | + | |
| 28117 | + | |
28107 | 28118 | | |
28108 | 28119 | | |
28109 | 28120 | | |
| |||
35095 | 35106 | | |
35096 | 35107 | | |
35097 | 35108 | | |
35098 | | - | |
| 35109 | + | |
35099 | 35110 | | |
35100 | 35111 | | |
35101 | 35112 | | |
| |||
35742 | 35753 | | |
35743 | 35754 | | |
35744 | 35755 | | |
35745 | | - | |
| 35756 | + | |
| 35757 | + | |
| 35758 | + | |
| 35759 | + | |
| 35760 | + | |
35746 | 35761 | | |
35747 | 35762 | | |
35748 | 35763 | | |
| |||
36090 | 36105 | | |
36091 | 36106 | | |
36092 | 36107 | | |
36093 | | - | |
| 36108 | + | |
| 36109 | + | |
| 36110 | + | |
36094 | 36111 | | |
36095 | 36112 | | |
36096 | 36113 | | |
| |||
39484 | 39501 | | |
39485 | 39502 | | |
39486 | 39503 | | |
39487 | | - | |
| 39504 | + | |
| 39505 | + | |
| 39506 | + | |
39488 | 39507 | | |
39489 | 39508 | | |
39490 | 39509 | | |
| |||
39730 | 39749 | | |
39731 | 39750 | | |
39732 | 39751 | | |
39733 | | - | |
| 39752 | + | |
| 39753 | + | |
| 39754 | + | |
39734 | 39755 | | |
39735 | 39756 | | |
39736 | 39757 | | |
| |||
41117 | 41138 | | |
41118 | 41139 | | |
41119 | 41140 | | |
41120 | | - | |
| 41141 | + | |
| 41142 | + | |
| 41143 | + | |
41121 | 41144 | | |
41122 | 41145 | | |
41123 | 41146 | | |
| |||
42468 | 42491 | | |
42469 | 42492 | | |
42470 | 42493 | | |
| 42494 | + | |
| 42495 | + | |
| 42496 | + | |
| 42497 | + | |
42471 | 42498 | | |
42472 | 42499 | | |
42473 | 42500 | | |
| |||
42485 | 42512 | | |
42486 | 42513 | | |
42487 | 42514 | | |
42488 | | - | |
| 42515 | + | |
| 42516 | + | |
| 42517 | + | |
| 42518 | + | |
42489 | 42519 | | |
42490 | 42520 | | |
42491 | 42521 | | |
| |||
42499 | 42529 | | |
42500 | 42530 | | |
42501 | 42531 | | |
42502 | | - | |
| 42532 | + | |
42503 | 42533 | | |
42504 | 42534 | | |
42505 | 42535 | | |
| |||
42649 | 42679 | | |
42650 | 42680 | | |
42651 | 42681 | | |
| 42682 | + | |
42652 | 42683 | | |
42653 | 42684 | | |
42654 | 42685 | | |
| |||
42670 | 42701 | | |
42671 | 42702 | | |
42672 | 42703 | | |
42673 | | - | |
42674 | | - | |
| 42704 | + | |
| 42705 | + | |
| 42706 | + | |
| 42707 | + | |
42675 | 42708 | | |
42676 | 42709 | | |
42677 | 42710 | | |
| |||
42699 | 42732 | | |
42700 | 42733 | | |
42701 | 42734 | | |
| 42735 | + | |
42702 | 42736 | | |
42703 | 42737 | | |
42704 | 42738 | | |
| |||
45780 | 45814 | | |
45781 | 45815 | | |
45782 | 45816 | | |
45783 | | - | |
| 45817 | + | |
| 45818 | + | |
| 45819 | + | |
| 45820 | + | |
45784 | 45821 | | |
45785 | 45822 | | |
45786 | 45823 | | |
| |||
46350 | 46387 | | |
46351 | 46388 | | |
46352 | 46389 | | |
46353 | | - | |
| 46390 | + | |
| 46391 | + | |
| 46392 | + | |
| 46393 | + | |
| 46394 | + | |
46354 | 46395 | | |
46355 | 46396 | | |
46356 | 46397 | | |
| |||
47576 | 47617 | | |
47577 | 47618 | | |
47578 | 47619 | | |
47579 | | - | |
| 47620 | + | |
| 47621 | + | |
| 47622 | + | |
47580 | 47623 | | |
47581 | 47624 | | |
47582 | 47625 | | |
| |||
52895 | 52938 | | |
52896 | 52939 | | |
52897 | 52940 | | |
| 52941 | + | |
| 52942 | + | |
| 52943 | + | |
| 52944 | + | |
| 52945 | + | |
52898 | 52946 | | |
52899 | 52947 | | |
52900 | 52948 | | |
| |||
52908 | 52956 | | |
52909 | 52957 | | |
52910 | 52958 | | |
52911 | | - | |
| 52959 | + | |
52912 | 52960 | | |
52913 | 52961 | | |
52914 | 52962 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1573 | 1573 | | |
1574 | 1574 | | |
1575 | 1575 | | |
| 1576 | + | |
| 1577 | + | |
| 1578 | + | |
| 1579 | + | |
| 1580 | + | |
| 1581 | + | |
| 1582 | + | |
| 1583 | + | |
| 1584 | + | |
| 1585 | + | |
| 1586 | + | |
| 1587 | + | |
| 1588 | + | |
| 1589 | + | |
| 1590 | + | |
1576 | 1591 | | |
1577 | 1592 | | |
1578 | 1593 | | |
| |||
2193 | 2208 | | |
2194 | 2209 | | |
2195 | 2210 | | |
| 2211 | + | |
| 2212 | + | |
| 2213 | + | |
| 2214 | + | |
| 2215 | + | |
| 2216 | + | |
| 2217 | + | |
| 2218 | + | |
| 2219 | + | |
| 2220 | + | |
| 2221 | + | |
| 2222 | + | |
| 2223 | + | |
| 2224 | + | |
| 2225 | + | |
| 2226 | + | |
| 2227 | + | |
| 2228 | + | |
| 2229 | + | |
2196 | 2230 | | |
0 commit comments