Description
Any openclaw plugins install command for a ClawHub-hosted plugin fails inside a NemoClaw sandbox — on any policy tier — at the post-extract validation step with:
Invalid extensions directory: base directory must be a real directory
Steps to reproduce
- Onboard any sandbox with nemoclaw onboard.
- Open the sandbox and run:
openclaw plugins install @openclaw/diagnostics-otel
- Install proceeds through ClawHub resolution → download → extract.
- Fails at the final install-path validation. Actual output
Resolving clawhub:@openclaw/diagnostics-otel…
ClawHub code-plugin @openclaw/diagnostics-otel@2026.3.22 channel=official verification=source-linked
Compatibility: pluginApi=>=2026.3.22
Downloading plugin @openclaw/diagnostics-otel@2026.3.22 from ClawHub…
Extracting /tmp/openclaw-clawhub-package-yCnWVv/diagnostics-otel.zip…
Invalid extensions directory: base directory must be a real directory
Expected
Plugin installs into ~/.openclaw/extensions// and appears in openclaw plugins list.
Root cause
NemoClaw's sandbox image symlinks every persistent state path under ~/.openclaw/ into /sandbox/.openclaw-data/ so state survives sandbox recreation. In a fresh sandbox, ls -la ~/.openclaw shows:
lrwxrwxrwx 1 root root 30 Apr 14 01:21 agents -> /sandbox/.openclaw-data/agents
lrwxrwxrwx 1 root root 30 Apr 14 01:21 canvas -> /sandbox/.openclaw-data/canvas
lrwxrwxrwx 1 root root 35 Apr 14 01:21 credentials -> /sandbox/.openclaw-data/credentials
lrwxrwxrwx 1 root root 28 Apr 14 01:21 cron -> /sandbox/.openclaw-data/cron
...
lrwxrwxrwx 1 root root 34 Apr 14 01:21 extensions -> /sandbox/.openclaw-data/extensions
...
Every stateful subdirectory is a symlink.
OpenClaw's plugin-install validator at openclaw/src/infra/install-safe-path.ts:93-96 explicitly rejects symlinks as install bases:
const baseLstat = await fs.lstat(baseDir);
if (!baseLstat.isDirectory() || baseLstat.isSymbolicLink()) {
throw new Error(Invalid ${params.boundaryLabel}: base directory must be a real directory);
}
This is TOCTOU hardening in OpenClaw — a reasonable security default in isolation, but it's structurally incompatible with NemoClaw's symlink-based persistence layout.
Reproduction Steps
- Onboard any sandbox with nemoclaw onboard.
- Open the sandbox and run:
openclaw plugins install @openclaw/diagnostics-otel
- Install proceeds through ClawHub resolution → download → extract.
- Fails at the final install-path validation.
Environment
- NemoClaw: v0.0.21 (reproduced on current main)
- OpenClaw: 2026.4.2 (d74a122)
- Sandbox: NemoClaw-default sandbox image (openclaw-sandbox.yaml base policy)
- Policy tier: reproduced on both Balanced (with npm preset) and Restricted (no presets)
- Plugin source: ClawHub (reproduces with any ClawHub-hosted plugin)
Debug Output
Logs
Checklist
Description
Any openclaw plugins install command for a ClawHub-hosted plugin fails inside a NemoClaw sandbox — on any policy tier — at the post-extract validation step with:
Invalid extensions directory: base directory must be a real directorySteps to reproduce
openclaw plugins install @openclaw/diagnostics-otel
Expected
Plugin installs into ~/.openclaw/extensions// and appears in openclaw plugins list.
Root cause
NemoClaw's sandbox image symlinks every persistent state path under ~/.openclaw/ into /sandbox/.openclaw-data/ so state survives sandbox recreation. In a fresh sandbox, ls -la ~/.openclaw shows:
Every stateful subdirectory is a symlink.
OpenClaw's plugin-install validator at openclaw/src/infra/install-safe-path.ts:93-96 explicitly rejects symlinks as install bases:
const baseLstat = await fs.lstat(baseDir);
if (!baseLstat.isDirectory() || baseLstat.isSymbolicLink()) {
throw new Error(
Invalid ${params.boundaryLabel}: base directory must be a real directory);}
This is TOCTOU hardening in OpenClaw — a reasonable security default in isolation, but it's structurally incompatible with NemoClaw's symlink-based persistence layout.
Reproduction Steps
openclaw plugins install @openclaw/diagnostics-otel
Environment
Debug Output
Logs
Checklist