feat: Phase 5 -- Web Management UI with RBAC Auth#13
Conversation
…client Add TCP listen mode (MCP2CLI_LISTEN_HOST/PORT) so the daemon can serve multiple machines over HTTP. Bearer token auth with timing-safe comparison protects authenticated endpoints while /health and /metrics remain open. Prometheus metrics at GET /metrics expose request counts, latency histograms, connection pool state, memory usage, and auth failures per service/tool. Remote client mode (MCP2CLI_REMOTE_URL) lets the CLI connect to a remote daemon instead of starting a local one. Includes a bash wrapper (scripts/mcp2cli-remote) for curl-only machines. LXC deployment files in deploy/ with systemd unit, env template, services config, and setup script. Ansible playbook lives in the infrastructure repo. Fixes idle timer firing immediately when timeoutMs=0 (TCP mode) by guarding touch() against zero/negative values. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 5 implementation: - ConfigManager with CRUD operations and disk persistence - REST API for service management (add/update/remove/import/reload) - Embedded web UI with dark theme, login (username+password and token tabs) - Pluggable AuthProvider interface with TokenAuthProvider (RBAC) - Three roles: admin (full), agent (tools+read), viewer (read-only) - CI/CD deploy pipeline: build, deploy to CT 216, health check, auto-rollback - Cross-compile for Linux x64 in deploy job - tokens.json gitignored (secrets) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 28197636 | Triggered | Generic Password | 0612ac3 | tests/daemon/auth-provider.test.ts | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
|
Claude finished @rodaddy's task —— View job PR Review: feat: Phase 5 -- Web Management UI with RBAC Auth
Overall this is a solid piece of work with good structural decisions (pluggable AuthProvider, timing-safe comparisons, Zod validation). Issues below are ordered by severity. 🔴 Critical Security Issues1. Hardcoded credentials in test file ( GitGuardian flagged this. While these are test fixtures, the strings 2. if (path === "/api/auth/me" && req.method === "GET") {
return Response.json({
success: true,
userId: authCtx?.userId ?? "anonymous",
role: authCtx?.role ?? "admin", // ← defaults to "admin" if authCtx is null
});
}
3. Token returned in login response is the raw bearer token ( return Response.json({
success: true,
token: result.token, // ← plaintext credential in response body
...
});The token in 4. SSRF via const response = await fetch(url);No URL allowlist or scheme validation. An admin can direct the daemon to fetch from internal network addresses ( 🟡 Bugs and Logic Errors5. Route ordering bug: DELETE regex matches before PUT regex is checked ( const putMatch = path.match(/^\/api\/services\/([^/]+)$/); // line 309
if (putMatch && req.method === "PUT") { ... }
const deleteMatch = path.match(/^\/api\/services\/([^/]+)$/); // line 328
if (deleteMatch && req.method === "DELETE") { ... }Both regexes are identical. Since they're just 6. private writeLock = false;
private async writeToDisk(): Promise<void> {
if (this.writeLock) {
throw new ConfigManagerError("Concurrent write detected -- try again");
}
this.writeLock = true;
try {
await Bun.write(...);
} finally {
this.writeLock = false;
}
}In a single-threaded JS runtime this is safe only between synchronous check and the first 7. The function is implemented twice, identically. The one in 🟡 Resource Leaks8. AbortController timer leak on tool call timeout ( const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
let sdkResult: ...;
try {
sdkResult = await Promise.race([...]);
} finally {
clearTimeout(timer);
}The 9. No request body size limit
🟡 Missing Error Handling10. const response = await fetch(url);No 11. const body = await req.json() as { username?: string; password?: string };
if (!body.username || !body.password) { ... }
12. Config reload doesn't update the const server = createDaemonServer({
...
config, // ← stale reference captured at startup
configManager,
...
});Inside the server, 🟠 TypeScript Strictness13. Unsafe const body = (await req.json()) as DaemonCallRequest; // line 105
const body = (await req.json()) as DaemonListToolsRequest; // line 166
const body = (await req.json()) as DaemonSchemaRequest; // line 181
const body = await req.json() as { username?: string; password?: string }; // line 238
14. let authCtx: AuthContext | null = null;
if (!isAuthExempt(path)) {
authCtx = authenticateRequest(req, authProvider);
if (!authCtx) { return 401; }
...
}
// authCtx is null for ALL exempt paths from hereAt 15. let editMode = null; // ui.ts:269
let pollTimer = null; // ui.ts:270
let currentRole = 'admin'; // ui.ts:271 — initialized to 'admin' before auth
✅ What's Done Well
Summary of Must-Fix Before Merge
|
- CSS: .hidden { display: none !important } to override .login-overlay flex
- Race condition: authLock prevents 401 re-showing overlay during login
- Debounce: require 2 consecutive auth failures before showing login
- await refresh() before starting poll interval
- Error handling: try/catch on doLogin() and init()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Claude finished @rodaddy's task —— View job PR Review: feat: Phase 5 -- Web Management UI with RBAC Auth
Overall: solid architecture with a clean 🔴 Critical Security Issues1. Hardcoded credentials in committed test file ( GitGuardian already flagged this. The constants 2. role: authCtx?.role ?? "admin", // ← "admin" fallback is wrong
3. SSRF via const response = await fetch(url); // no scheme/host validationAn admin can direct the daemon to fetch 4.
🟡 Bugs and Logic Errors5. function doLogout() {
clearToken();
currentRole = 'admin'; // ← should be 'viewer' or null
showLogin('');
...
}After logout, before the login overlay appears, 6. Route ordering: The 7. private writeLock = false;
private async writeToDisk(): Promise<void> {
if (this.writeLock) { throw ... } // ← check
this.writeLock = true; // ← set (not atomic!)
try { await Bun.write(...); } finally { this.writeLock = false; }
}Two concurrent HTTP requests calling 8. const key = `${service}.${tool}`; // → label in Prometheus outputPrometheus labels appear in the unauth-accessible 🟡 Resource Leaks9. new Promise<never>((_, reject) => {
controller.signal.addEventListener("abort", () => reject(...));
})The 10. No request body size limit
🟡 Missing Error Handling11. const response = await fetch(url); // hangs indefinitely on slow/unresponsive hostsA slow remote will stall the event loop indefinitely. Add const response = await fetch(url, { signal: AbortSignal.timeout(10_000) });12. const body = await req.json() as { username?: string; password?: string };
if (!body.username || !body.password) { ... }
13. const probe = await fetch('/api/services');If the daemon is slow to respond, the UI hangs on the loading screen. Add 🟠 TypeScript Strictness14. Unsafe All four 15.
const authedCtx = authCtx!; // only after the auth block has returned 401 if null16. Identical implementation exists in both files. The one in ✅ What's Done Well
Must-Fix Before Merge
|
* feat: network deployment with TCP binding, auth, metrics, and remote client
Add TCP listen mode (MCP2CLI_LISTEN_HOST/PORT) so the daemon can serve
multiple machines over HTTP. Bearer token auth with timing-safe comparison
protects authenticated endpoints while /health and /metrics remain open.
Prometheus metrics at GET /metrics expose request counts, latency histograms,
connection pool state, memory usage, and auth failures per service/tool.
Remote client mode (MCP2CLI_REMOTE_URL) lets the CLI connect to a remote
daemon instead of starting a local one. Includes a bash wrapper
(scripts/mcp2cli-remote) for curl-only machines.
LXC deployment files in deploy/ with systemd unit, env template, services
config, and setup script. Ansible playbook lives in the infrastructure repo.
Fixes idle timer firing immediately when timeoutMs=0 (TCP mode) by guarding
touch() against zero/negative values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: web management UI with RBAC auth and CI/CD deploy pipeline
Phase 5 implementation:
- ConfigManager with CRUD operations and disk persistence
- REST API for service management (add/update/remove/import/reload)
- Embedded web UI with dark theme, login (username+password and token tabs)
- Pluggable AuthProvider interface with TokenAuthProvider (RBAC)
- Three roles: admin (full), agent (tools+read), viewer (read-only)
- CI/CD deploy pipeline: build, deploy to CT 216, health check, auto-rollback
- Cross-compile for Linux x64 in deploy job
- tokens.json gitignored (secrets)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: web UI login overlay race condition and CSS specificity bug
- CSS: .hidden { display: none !important } to override .login-overlay flex
- Race condition: authLock prevents 401 re-showing overlay during login
- Debounce: require 2 consecutive auth failures before showing login
- await refresh() before starting poll interval
- Error handling: try/catch on doLogin() and init()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
/) with dark theme, live service dashboardTest plan
🤖 Generated with Claude Code