Initial project documentation and status

This commit is contained in:
Clawdia
2026-08-30 14:46:23 -04:00
commit 765f372c72
50 changed files with 12916 additions and 0 deletions
+491
View File
@@ -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*