Initial project documentation and status
This commit is contained in:
@@ -0,0 +1,491 @@
|
||||
# Review Report: MeshCentral ↔ OpenClaw Bridge
|
||||
|
||||
**Review Date:** 2026-08-24
|
||||
**Reviewer:** Independent subagent (clawdia review flow)
|
||||
**Scope:** Implementation under `/home/ledadmin/projects/meshcentral-openclaw-bridge/implementation/`
|
||||
**Reference Docs:** PROJECT.md, SECURITY.md, research/INTEGRATION-RESEARCH.md
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The implementation provides a well-structured scaffold for the MeshCentral ↔ OpenClaw bridge with solid contracts, typed schemas via Zod, and comprehensive mock-based testing. However, **the critical "real" adapter is completely unimplemented**, leaving only the mock path operational. The current codebase has three categories of issues:
|
||||
|
||||
1. **Critical:** Unimplemented real adapter (`MeshCentralRealAdapter`) with dangerous defaults (insecure TLS, no auth).
|
||||
2. **High:** Test failures indicate missing error message alignment; linting configuration incomplete.
|
||||
3. **Medium:** Diagnostic output sanitization and prompt-injection protection not addressed.
|
||||
|
||||
**Test Results:**
|
||||
- 50 tests pass
|
||||
- 2 tests fail (expectation mismatch on error message string, not logic)
|
||||
- `npm run build` fails with TypeScript errors in test setup
|
||||
- `npm run lint` fails due to missing ESLint configuration
|
||||
|
||||
**Recommendation:** Do **not** proceed with live MeshCentral integration until the real adapter is implemented according to security requirements and all test/lint failures are resolved.
|
||||
|
||||
---
|
||||
|
||||
## Severity-Ranked Findings
|
||||
|
||||
### 🔴 CRITICAL: Real adapter unimplemented with dangerous TLS defaults
|
||||
|
||||
**Location:** `src/adapter.ts:35-48`, `healthCheck()` method
|
||||
**Severity:** Critical
|
||||
|
||||
The `MeshCentralRealAdapter.healthCheck()` method uses raw `fetch` with no TLS validation options:
|
||||
|
||||
```typescript
|
||||
async healthCheck(): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`${this.config.meshCentralUrl}/api`, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
return response.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Issues:**
|
||||
- No certificate verification (relies on Node.js default, which may not validate against internal CA).
|
||||
- `fetch` does not support WebSocket connections; MeshCentral's control API uses `wss://.../control.ashx`, not `/api`.
|
||||
- Research found upstream `meshctrl.js` uses `rejectUnauthorized: false`—this should be explicitly avoided.
|
||||
- No support for meshctrl-style WebSocket authentication (`x-meshauth` header with base64-encoded credentials).
|
||||
|
||||
**Attack Surface:** MITM attack, credential theft via insecure WebSocket upgrade.
|
||||
|
||||
**Required Fix:**
|
||||
- Replace `fetch` with a proper WebSocket client (`ws` package, already in dependencies).
|
||||
- Enable TLS certificate validation (do **not** disable it).
|
||||
- Support custom CA bundle for internal MeshCentral deployments.
|
||||
- Implement meshctrl protocol handshake (research confirms `wss://host:port/control.ashx`).
|
||||
|
||||
---
|
||||
|
||||
### 🔴 CRITICAL: No authentication or authorization implementation
|
||||
|
||||
**Location:** `src/adapter.ts` (entire file)
|
||||
**Severity:** Critical
|
||||
|
||||
The research document explicitly identifies two authentication modes:
|
||||
1. Username/password via `x-meshauth` WebSocket header (base64 encoded).
|
||||
2. Server login key (highly sensitive—should not be used for the bridge).
|
||||
|
||||
**Current State:** The real adapter has no authentication mechanism. It does not:
|
||||
- Accept or validate credentials.
|
||||
- Support login keys (even in config).
|
||||
- Manage WebSocket sessions or reconnects.
|
||||
|
||||
**Required Fix:**
|
||||
- Add `username`, `password` (or `loginKey`) to `BridgeConfig`.
|
||||
- Implement `x-meshauth` header encoding per upstream `meshctrl.js` source.
|
||||
- Store secrets in environment or protected file with restrictive permissions (0600).
|
||||
- Fail closed if credentials are missing or invalid.
|
||||
|
||||
---
|
||||
|
||||
### 🟠 HIGH: Test expectation mismatch (non-blocking but indicates incomplete validation)
|
||||
|
||||
**Location:** `src/__tests__/mock-adapter.test.ts:56,86`
|
||||
**Severity:** High
|
||||
|
||||
Tests expect error message substring `"Resource not found"` but actual errors use more specific messages:
|
||||
|
||||
| Test | Expected | Actual |
|
||||
|------|----------|--------|
|
||||
| `getDevice` non-existent | `"Resource not found"` | `"Device not found: nonexistent"` |
|
||||
| `getGroup` non-existent | `"Resource not found"` | `"Group not found: nonexistent"` |
|
||||
|
||||
**Fix:** Update test expectations to match the current error messages, or standardize error messages across the codebase.
|
||||
|
||||
```typescript
|
||||
// Recommendation: Use consistent prefix for all resource-not-found errors
|
||||
`Resource not found: device ${deviceId}`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🟠 HIGH: TypeScript build fails in test setup
|
||||
|
||||
**Location:** `src/__tests__/setup.ts`
|
||||
**Severity:** High
|
||||
|
||||
Build errors prevent compilation and CI validation:
|
||||
|
||||
```
|
||||
src/__tests__/setup.ts(13,15): error TS2428: All declarations of 'Expect' must have identical type parameters.
|
||||
src/__tests__/setup.ts(23,13): error TS7006: Parameter 'received' implicitly has an 'any' type.
|
||||
```
|
||||
|
||||
**Root Cause:** Conflicting `jest` and `jest-extended` types with mismatched generic parameters. The custom matchers declare `Expect<T>` without the required type argument.
|
||||
|
||||
**Required Fix:**
|
||||
- Align custom matcher types with `jest` v29 expectations.
|
||||
- Remove duplicate import of `'jest-extended'`.
|
||||
- Use proper typing for `received` parameter.
|
||||
|
||||
---
|
||||
|
||||
### 🟠 HIGH: ESLint configuration missing
|
||||
|
||||
**Location:** Project root
|
||||
**Severity:** High
|
||||
|
||||
Lint command fails:
|
||||
|
||||
```
|
||||
ESLint couldn't find a configuration file.
|
||||
```
|
||||
|
||||
**Required Fix:**
|
||||
- Add `.eslintrc.yml` or `eslint.config.js`.
|
||||
- Enforce TypeScript rules (`@typescript-eslint/`).
|
||||
- Configure import ordering, unused variables, and strict null checks.
|
||||
|
||||
Example minimal config (`eslint.config.js`):
|
||||
```javascript
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/strict-boolean-expressions': 'off',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🟠 HIGH: Diagnostics produce raw output without sanitization
|
||||
|
||||
**Location:** `src/mock-adapter.ts:54-103`, `DIAGNOSTIC_OUTPUTS`
|
||||
**Severity:** High
|
||||
|
||||
Diagnostic output is hard-coded static strings. Research document warns:
|
||||
- Endpoint command output may contain secrets (passwords, keys).
|
||||
- Device names, logs, and process lists can expose personal data.
|
||||
- Prompt injection via untrusted output is a valid threat.
|
||||
|
||||
**Current State:** No sanitization or escape of special characters, secrets, or prompt-injection vectors.
|
||||
|
||||
**Required Fix:**
|
||||
- Implement output sanitization: replace known secret patterns (password=, token=, api_key=).
|
||||
- Truncate or mask sensitive fields in logs.
|
||||
- Add a `sanitizeOutput()` function before returning results.
|
||||
- Consider adding an explicit "safe output only" mode.
|
||||
|
||||
---
|
||||
|
||||
### 🟡 MEDIUM: No device/group allowlists
|
||||
|
||||
**Location:** `src/adapter.ts`, `contracts.ts`
|
||||
**Severity:** Medium
|
||||
|
||||
Research document explicitly requires:
|
||||
> Restrict targets using explicit device/group allowlists.
|
||||
|
||||
**Current State:**
|
||||
- Configuration has no `allowedDeviceIds` or `allowedGroupIds`.
|
||||
- No validation that requested devices/groups are in an allowlist.
|
||||
|
||||
**Required Fix:**
|
||||
- Add `allowedDeviceIds?: string[]` and `allowedGroupIds?: string[]` to `BridgeConfig`.
|
||||
- Add validation in adapter methods: reject if target not in allowlist (or empty allowlist = all allowed).
|
||||
- Audit log must record whether allowlist check passed.
|
||||
|
||||
---
|
||||
|
||||
### 🟡 MEDIUM: No audit logging
|
||||
|
||||
**Location:** `src/` (all files)
|
||||
**Severity:** Medium
|
||||
|
||||
Security requirements mandate:
|
||||
> Log authentication events, requests, target, operation, confirmation, result, and timestamp.
|
||||
|
||||
**Current State:** No logging implementation. Debug logs or console.log statements are absent.
|
||||
|
||||
**Required Fix:**
|
||||
- Introduce `BridgeLogger` interface with methods:
|
||||
- `logAuth(principal, success, details)`
|
||||
- `logRequest(requestId, principal, operation, target, timestamp)`
|
||||
- `logResult(requestId, success, details)`
|
||||
- Use a structured formatter (JSON) for downstream processing.
|
||||
- Never log secrets (passwords, keys, tokens).
|
||||
|
||||
---
|
||||
|
||||
### 🟡 MEDIUM: No connection resilience
|
||||
|
||||
**Location:** `src/adapter.ts`
|
||||
**Severity:** Medium
|
||||
|
||||
Research document notes:
|
||||
> Whether sessions expire, how reconnects behave, and whether a reconnect can replay an action.
|
||||
|
||||
**Current State:** No retry logic, backoff, or session management.
|
||||
|
||||
**Required Fix:**
|
||||
- Add `maxRetries`, `retryBackoffMs` to config.
|
||||
- Implement exponential backoff on WebSocket connect failures.
|
||||
- Add reconnect guard to prevent duplicate action replay (e.g., request ID deduplication).
|
||||
|
||||
---
|
||||
|
||||
### 🟡 MEDIUM: No version compatibility checking
|
||||
|
||||
**Location:** `src/adapter.ts`, ` contracts.ts`
|
||||
**Severity:** Medium
|
||||
|
||||
Research document warns:
|
||||
> The bridge should record the tested server version and client/source commit, use semver/commit compatibility checks, and fail closed or enter degraded read-only mode when an unknown version is detected.
|
||||
|
||||
**Current State:** No MeshCentral version check.
|
||||
|
||||
**Required Fix:**
|
||||
- Add `serverVersion` field to adapter metadata.
|
||||
- Fetch version on connect (e.g., via `ServerInfo` control message).
|
||||
- Define supported version range; fail closed if incompatible.
|
||||
|
||||
---
|
||||
|
||||
### 🟡 MEDIUM: Diagnostics allow all check types without policy
|
||||
|
||||
**Location:** `src/contracts.ts`, `mock-adapter.ts`
|
||||
**Severity:** Medium
|
||||
|
||||
Research document requires:
|
||||
> Define an allowlisted set of diagnostic checks... Restrict execution by device/group allowlist and command policy.
|
||||
|
||||
**Current State:**
|
||||
- All `DiagnosticCheckType` values are enabled by default.
|
||||
- No policy mapping check types to permitted device groups.
|
||||
|
||||
**Required Fix:**
|
||||
- Add `diagnosticPolicy` config with allowed check types per group/device.
|
||||
- Example:
|
||||
```typescript
|
||||
diagnosticPolicy: {
|
||||
'grp-servers': ['diskSpace', 'uptime'],
|
||||
'*': [],
|
||||
}
|
||||
```
|
||||
- Reject diagnostic requests not in policy.
|
||||
|
||||
---
|
||||
|
||||
### 🟡 MEDIUM: No timeout enforcement on real adapter
|
||||
|
||||
**Location:** `src/adapter.ts`
|
||||
**Severity:** Medium
|
||||
|
||||
The `healthCheck()` method has no timeout; `fetch` will hang indefinitely by default.
|
||||
|
||||
**Required Fix:**
|
||||
- Use an AbortController with a configurable timeout.
|
||||
- Reject with `ErrorCodes.TimeoutExceeded` if exceeded.
|
||||
|
||||
---
|
||||
|
||||
### 🟢 LOW: Diagnostic output truncation inconsistent
|
||||
|
||||
**Location:** `src/mock-adapter.ts`
|
||||
**Severity:** Low
|
||||
|
||||
Truncation is simulated but not tested for edge cases:
|
||||
- What happens if `maxOutputLength` is 0?
|
||||
- What happens if output is exactly at the limit?
|
||||
|
||||
**Test Gap:** `timeout.test.ts` has no test for zero-length max output.
|
||||
|
||||
---
|
||||
|
||||
## Verification Results
|
||||
|
||||
### Test Execution (`npm test -- --coverage`)
|
||||
|
||||
```
|
||||
Test Suites: 1 failed, 7 passed, 8 total
|
||||
Tests: 2 failed, 50 passed, 52 total
|
||||
Coverage: 92.3% statements, 91.66% branches, 72.72% functions
|
||||
```
|
||||
|
||||
**Passing suites:**
|
||||
- `contracts.test.ts` — All Zod schema validations pass.
|
||||
- `adapter-factory.test.ts` — Factory correctly returns mock by default.
|
||||
- `group-filtering.test.ts` — Device filtering by group works.
|
||||
- `timeout.test.ts` — Timeout and output limits respected (except edge cases).
|
||||
- `device-status.test.ts` — Status enum handling correct.
|
||||
- `mock-data-loader.test.ts` — JSON fixture loading works.
|
||||
- `integration.test.ts` — Full workflow mocks succeed.
|
||||
|
||||
**Failing tests (non-blocking, expectation issue):**
|
||||
- `mock-adapter.test.ts` — 2 tests fail due to error message mismatch (not logic):
|
||||
- `getDevice` non-existent expects "Resource not found" but receives "Device not found: nonexistent"
|
||||
- `getGroup` non-existent expects "Resource not found" but receives "Group not found: nonexistent"
|
||||
|
||||
---
|
||||
|
||||
### Build (`npm run build`)
|
||||
|
||||
```
|
||||
node_modules/@types/jest/index.d.ts(14,23): error TS2314
|
||||
node_modules/@types/jest/index.d.ts(683,15): error TS2428
|
||||
src/__tests__/setup.ts(13,15): error TS2428
|
||||
src/__tests__/setup.ts(23,13): error TS7006
|
||||
src/__tests__/setup.ts(30,14): error TS7006
|
||||
src/__tests__/setup.ts(37,14): error TS7006
|
||||
```
|
||||
|
||||
**Status:** Build fails—cannot produce dist artifacts. All errors are in test setup, not source code.
|
||||
|
||||
---
|
||||
|
||||
### Lint (`npm run lint`)
|
||||
|
||||
```
|
||||
ESLint couldn't find a configuration file. To set up a configuration file for this project, please run:
|
||||
npm init @eslint/config
|
||||
```
|
||||
|
||||
**Status:** Linting entirely skipped—no code style enforcement. Missing `.eslintrc.*` or `eslint.config.js`.
|
||||
|
||||
---
|
||||
|
||||
### Code Coverage
|
||||
|
||||
```
|
||||
----------------------|---------|----------|---------|---------|-------------------
|
||||
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
||||
----------------------|---------|----------|---------|---------|-------------------
|
||||
All files | 92.3 | 91.66 | 72.72 | 91.83 |
|
||||
src | 91.83 | 91.66 | 71.87 | 91.3 |
|
||||
adapter.ts | 60 | 87.5 | 44.44 | 60 | 41-65
|
||||
contracts.ts | 100 | 100 | 100 | 100 |
|
||||
index.ts | 100 | 100 | 50 | 100 |
|
||||
mock-adapter.ts | 100 | 90 | 92.3 | 100 | 197
|
||||
src/__tests__ | 100 | 100 | 100 | 100 |
|
||||
```
|
||||
|
||||
**Coverage Gaps:**
|
||||
- `adapter.ts` lines 41–65: Unimplemented real adapter methods.
|
||||
- `mock-adapter.ts` line 197: Edge case in diagnostic output handling (uncovered due to fixed mock data sizes).
|
||||
- `index.ts` line 50: Factory export (uncovered in isolation tests).
|
||||
|
||||
---
|
||||
|
||||
## Scope Creep Assessment
|
||||
|
||||
**Current scope aligns with PROJECT.md Goal 1 (read-only foundation).**
|
||||
|
||||
**Observed scope creep:**
|
||||
- None detected—adapter factory and config correctly default to `'mock'`.
|
||||
- `example-config-real.json` suggests real adapter is WIP, not production-ready.
|
||||
|
||||
**Not yet in scope (per TODO.md):**
|
||||
- Diagnostics beyond mock data.
|
||||
- State-changing actions (reboot, service restart).
|
||||
- Confirmation flows or dry-run.
|
||||
|
||||
---
|
||||
|
||||
## TLS and Auth Assumptions (Per Research)
|
||||
|
||||
| Requirement | Current Implementation | Research Finding |
|
||||
|-------------|------------------------|------------------|
|
||||
| TLS certificate validation | Unknown (no actual connection) | Must be enabled; no `rejectUnauthorized: false` |
|
||||
| WebSocket endpoint | Not implemented | `wss://host:port/control.ashx` |
|
||||
| Auth header | Not implemented | `x-meshauth: base64(username,password,2fa?)` |
|
||||
| Login key support | Not implemented | High sensitivity—avoid unless explicitly chosen |
|
||||
|
||||
**Conclusion:** The real adapter is currently a stub that will fail or expose credentials if used with live MeshCentral.
|
||||
|
||||
---
|
||||
|
||||
## Test Failure Classification
|
||||
|
||||
| Test | Failure Type | Fix Required |
|
||||
|------|--------------|--------------|
|
||||
| `mock-adapter.test.ts:56` | Expectation mismatch (error message) | Update test expectation |
|
||||
| `mock-adapter.test.ts:86` | Expectation mismatch (error message) | Update test expectation |
|
||||
| TypeScript build errors | Type definition conflict | Fix `setup.ts` types |
|
||||
| ESLint failure | Missing config | Add `.eslintrc.*` |
|
||||
|
||||
**Note:** No test failures indicate logic defects in core functionality.
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
### Immediate (Before Live Integration)
|
||||
|
||||
1. **Fix TypeScript errors in `src/__tests__/setup.ts`** to enable build.
|
||||
2. **Add ESLint configuration** and enforce linting in CI.
|
||||
3. **Fix test expectations** to align with actual error messages (or standardize messages).
|
||||
4. **Implement `MeshCentralRealAdapter`** with:
|
||||
- Secure TLS (enabled validation).
|
||||
- WebSocket connection to `/control.ashx`.
|
||||
- `x-meshauth` header authentication.
|
||||
- Configurable `username`, `password`, or explicit opt-in for login key.
|
||||
5. **Add audit logging** with structured JSON output.
|
||||
|
||||
### Short-Term (Before Production)
|
||||
|
||||
6. Implement device/group allowlists.
|
||||
7. Add output sanitization for diagnostics.
|
||||
8. Define and enforce diagnostic policies.
|
||||
9. Add version compatibility checks.
|
||||
10. Implement retry logic with backoff.
|
||||
|
||||
### Medium-Term (After Security Review)
|
||||
|
||||
11. Add confirmation flow for reversible actions.
|
||||
12. Implement dry-run support.
|
||||
13. Add idempotency and duplicate-execution protection.
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
The implementation is a **well-structured mock-based scaffold** suitable for local development and testing, but it is not production-ready. The real adapter is **unimplemented** and contains no security controls beyond basic config structure.
|
||||
|
||||
**Do not proceed with live MeshCentral integration until:**
|
||||
- The real adapter is implemented per security requirements.
|
||||
- All build/lint/test failures are resolved.
|
||||
- An independent security review confirms TLS, auth, and logging.
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: File Inventory
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| `src/index.ts` | ✅ Clean | Exports adapter factory and contracts |
|
||||
| `src/adapter.ts` | ⚠️ Stub | Real adapter unimplemented; healthCheck uses wrong endpoint |
|
||||
| `src/mock-adapter.ts` | ✅ Complete | Full mock with test fixtures |
|
||||
| `src/contracts.ts` | ✅ Complete | Zod schemas, enums, types |
|
||||
| `src/__tests__/adapter-factory.test.ts` | ✅ Pass | Factory correctly returns mock by default |
|
||||
| `src/__tests__/contracts.test.ts` | ✅ Pass | All Zod schema validations pass |
|
||||
| `src/__tests__/device-status.test.ts` | ✅ Pass | Status enum handling correct |
|
||||
| `src/__tests__/group-filtering.test.ts` | ✅ Pass | Device filtering by group works |
|
||||
| `src/__tests__/integration.test.ts` | ✅ Pass | Full workflow mocks succeed |
|
||||
| `src/__tests__/mock-adapter.test.ts` | ⚠️ 2/6 fail | Expectation mismatch on error messages |
|
||||
| `src/__tests__/mock-data-loader.test.ts` | ✅ Pass | JSON fixture loading works |
|
||||
| `src/__tests__/setup.ts` | ❌ Broken | TypeScript errors block build and coverage |
|
||||
| `src/__tests__/timeout.test.ts` | ✅ Pass | Timeout and output limits respected |
|
||||
|
||||
**Test Summary:**
|
||||
- 50 tests pass (96.2%)
|
||||
- 2 tests fail due to error message expectation mismatch
|
||||
- Build fails due to TypeScript type conflicts in test setup
|
||||
- Linting skipped due to missing ESLint configuration
|
||||
|
||||
---
|
||||
|
||||
*End of Review Report*
|
||||
Reference in New Issue
Block a user