#21245: fix(plugins): harden symlinked extension discovery
size: S
Cluster:
Plugin Management Enhancements
## Summary
- discover bundled plugin directories that are symlink entries
- support symlinked extension file entries safely
- ignore broken symlink entries without crashing discovery
- add focused discovery tests for symlink file and broken-link edge cases
## Test Plan
- pnpm -C /Users/farmer/.openclaw/workspace/repos-main/openclaw exec vitest run --config vitest.unit.config.ts src/plugins/discovery.test.ts
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR enables plugin discovery to handle symlinked directories and files safely by resolving symlink targets and ignoring broken symlinks.
**Changes:**
- Introduced `resolveDirentKind` helper that uses `fs.statSync` to resolve symlink targets when `entry.isFile()` and `entry.isDirectory()` return false
- Modified file discovery to set `rootDir` to the symlink path itself for symlinked files, rather than the parent directory
- Broken symlinks are caught and silently skipped during discovery
- Added three focused tests: symlinked bundled plugin directories, symlinked extension files, and broken symlink handling
**Security considerations:**
For symlinked extension files, the `rootDir` is set to the file path itself (line `src/plugins/discovery.ts:375`), which causes the "source escapes root" check in `checkSourceEscapesRoot` to always pass (both paths resolve to the same realpath). This allows symlinked extensions to reference files anywhere on the filesystem, though they still must satisfy permission checks (not world-writable, correct ownership). This appears intentional based on the PR's goal to "support symlinked extension file entries safely," where safety is enforced through permission validation rather than path containment.
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge with minor caveats around the security model change for symlinked files
- The implementation is solid with good test coverage for the symlink edge cases. The security posture shift (allowing symlinks to escape the extensions directory) is intentional and mitigated by permission checks. The code correctly handles broken symlinks and maintains existing security checks for package-based extensions. Score is 4 instead of 5 due to the subtle security model change that may warrant additional documentation or review.
- No files require special attention - the implementation is straightforward and well-tested
<sub>Last reviewed commit: 4712754</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#3564: fix(plugins): follow symlinks in plugin and hook discovery
by mollywires · 2026-01-28
84.2%
#20424: Fix plugin extension path traversal in discovery/install
by markmusson · 2026-02-18
83.3%
#23473: Hooks/Plugins: enforce discovery root containment
by bmendonca3 · 2026-02-22
78.9%
#21660: fix(plugins): require explicit allowlist for non-bundled plugins
by AI-Reviewer-QS · 2026-02-20
75.0%
#22910: fix(browser): resolve symlinks in upload path validation
by erdinccurebal · 2026-02-21
74.5%
#20499: test(plugins): add bundled+config duplicate discovery regression
by dcol91863 · 2026-02-19
74.5%
#7007: Fix security audit false-positive for symlinked state dir
by MohammadErfan-Jabbari · 2026-02-02
73.7%
#9154: fix(doctor): resolve symlinks before comparing state directories
by gavinbmoore · 2026-02-04
73.2%
#12849: fix(plugins): fallback bundled channel specs when npm install retur...
by vincentkoc · 2026-02-09
73.1%
#11439: fix(security): warn on relative OPENCLAW_CONFIG_PATH and disable co...
by coygeek · 2026-02-07
72.4%