Skip to content

feat: harden the ClawBox MCP (auth, structured errors, health, bash safety, durable webapps) - #178

Merged
KrasimirKralev merged 6 commits into
betafrom
feature/mcp-hardening
Jun 9, 2026
Merged

KrasimirKralev merged 6 commits into
betafrom
feature/mcp-hardening

Conversation

@KrasimirKralev

@KrasimirKralev KrasimirKralev commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Hardens the ClawBox MCP server + CLI across six areas. Each was verified on a Jetson device (MCP server loads + functional JSON-RPC tool calls; unit tests).

Fixes

  1. CLI auth (the Failed to parse JSON bug). clawbox-cli.ts called /setup-api/* with no Authorization header, so once setup completes middleware.ts 307'd it to /login and JSON.parse choked on the login HTML. It now reads CLAWBOX_MCP_TOKEN (falling back to the data/.mcp-token file the CLI may not inherit via env) and injects the Bearer, mirroring clawbox-mcp.ts. Verified: clawbox-cli system info returns JSON.

  2. Durable webapps. webapp_create / code_project_build only emitted a ui:pending-action — dropped silently if the desktop wasn't open, so the app's HTML saved but it never reached the grid. The webapps POST route + buildProject now also register the app in desktop preferences server-side (registerWebappInPreferences), mirroring the live register_webapp handler, so the desktop picks it up from /setup-api/preferences on next load. (Implemented server-side rather than writing prefs from the MCP — the durability backstop belongs at the deploy chokepoint, and uninstall stays sticky with no resurrection.)

  3. Structured errors. A tool() wrapper around all 44 handlers catches throws and returns a parseable { error, code, message, details } envelope; api() throws a typed ApiError classified by status (AUTH_FAILED 401/403, NOT_FOUND, ENDPOINT_DOWN 5xx, TIMEOUT, INVALID_RESPONSE, DANGEROUS_COMMAND, INTERNAL). Agents branch on code instead of scraping English.

  4. clawbox_health tool. Verifies the MCP bearer + /setup-api/* reachability, returning { healthy, checks } — so a broken token is diagnosable up front. Verified: healthy with a token, and correctly reports mcp_token: not set / Failed to parse JSON without one.

  5. Docs. mcp/README.md — auth flow, full tool catalog, test commands, failure-mode table.

  6. bash safety. Destructive commands (rm -rf /, dd, mkfs, fork bombs, …) are now hard-blocked unless allowDangerous: true, enforced at the shared spawnBackground chokepoint so the agent tool can't bypass it (it previously could). Git-safety patterns still only warn. Verified: bash and agent both block rm -rf; override + git-safety warn paths work.

Notes

  • mcp/ is excluded from the Next tsconfig and runs under bun (types stripped), so the bar is runtime — hence the on-device smoke tests.
  • New unit test: webapp-registry.test.ts (3 cases). /simplify applied (closed the agent bypass, parallelized the health checks, collapsed registerWebappInPreferences to a single config read).

Summary by CodeRabbit

  • New Features

    • Added bearer token authentication for MCP CLI
    • New system health check tool for connectivity and configuration validation
    • Enhanced bash command blocking with allowDangerous override
  • Documentation

    • Comprehensive MCP tool catalog with error codes and troubleshooting guide
  • Refactor

    • Standardized error responses across all tools with structured error codes
    • Improved webapp deployment and preference registration workflow

The CLI called /setup-api/* with no Authorization header, so once setup completes middleware.ts 307'd it to /login and JSON.parse choked on the login HTML ("Failed to parse JSON"). Read CLAWBOX_MCP_TOKEN from the env, falling back to the data/.mcp-token file the gateway pre-start script writes (the CLI is launched separately from the MCP server and may not inherit its env), and inject it as a Bearer header — mirroring clawbox-mcp.ts. Loaded lazily so token-free commands like `app list` still work.
… durable webapps

- Structured errors: wrap every tool handler (tool()) so failures return a
  parseable { error, code, message, details } envelope; api() throws a typed
  ApiError classified by status (AUTH_FAILED on 401/403, ENDPOINT_DOWN on 5xx, …).
- clawbox_health tool: verifies the MCP bearer + /setup-api/* reachability so a
  broken token is diagnosable up front instead of via a cryptic tool failure.
- bash safety: hard-block destructive commands (rm -rf /, dd, mkfs, fork bombs,
  …) unless allowDangerous:true; enforced at the spawnBackground chokepoint so
  the agent tool can't bypass it. Git-safety patterns still only warn.
- Durable webapps: the webapps POST route and buildProject now register the app
  in desktop preferences server-side (registerWebappInPreferences), so a webapp
  created while the desktop is closed still appears on its next load instead of
  relying on the lossy ui:pending-action handoff.
- Docs: mcp/README.md (auth flow, tool catalog, testing, failure modes).
@KrasimirKralev
KrasimirKralev requested a review from a team as a code owner June 8, 2026 22:30
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@KrasimirKralev, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 8 minutes and 24 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: da80f884-5c81-40fb-9f35-5730aa029fb4

📥 Commits

Reviewing files that changed from the base of the PR and between 4850051 and 7385a2e.

📒 Files selected for processing (8)
  • mcp/README.md
  • mcp/clawbox-cli.ts
  • mcp/clawbox-mcp.ts
  • src/app/setup-api/webapps/route.ts
  • src/lib/code-projects.ts
  • src/tests/routes/webapps.test.ts
  • src/tests/unit/clawbox-mcp-browser-guidance.test.ts
  • src/tests/unit/code-projects.test.ts
📝 Walkthrough

Walkthrough

This PR hardens the ClawBox MCP server by adding bearer-token authentication, implementing structured error handling across all tools, enforcing bash command safety with override capability, and refactoring webapp deployment through a durable preference-registration mechanism.

Changes

MCP Server Hardening, Tool Error Handling, & Webapp Deployment

Layer / File(s) Summary
MCP Bearer Token Authentication & Health Diagnostics
mcp/clawbox-cli.ts, mcp/clawbox-mcp.ts, mcp/README.md
CLI loads token from CLAWBOX_MCP_TOKEN environment variable or on-disk data/.mcp-token file with caching, injecting Authorization: Bearer ... header into /setup-api/* requests. New clawbox_health tool validates token presence, strength, and endpoint reachability. Documentation specifies token sources, auth flow, testing procedures, and common failure modes with remediation steps.
MCP Tool Error Infrastructure & Bash Security
mcp/clawbox-mcp.ts, mcp/README.md
Introduces ApiError and DangerousCommandError exception types, classifyError function to map failures (HTTP status, timeout, parse errors) to stable error codes, and toolErrorResult to format standard error envelopes. Refactors detectDangerousCommand to separate hard-blocking destructive patterns from advisory git-safety warnings. Extends spawnBackground to enforce dangerous-command blocking centrally via DangerousCommandError with allowDangerous override. Updates bash tool to hard-block destructive commands unless explicitly overridden while still emitting advisory warnings.
Tool Error Wrapper & Migration Pattern
mcp/clawbox-mcp.ts, mcp/README.md
Introduces tool(...) wrapper that catches handler exceptions and converts them into structured error envelopes, preserving both no-arg and Zod-validated tool signatures. Documents complete MCP tool catalog (~45 tools) organized by category: diagnostics, shell, files, web, agent/task management, system, browser automation, app store, network, preferences, desktop UI, webapps, and code projects.
Tool Registration Migration to Error Wrapper
mcp/clawbox-mcp.ts
Migrates all MCP tool registrations to use the tool(...) wrapper: file operations (read_file, write_file, edit_file, list_directory, glob, grep), web tools (web_fetch, web_search), notebook editing, agent/task management, system, browser automation, app/network/preferences, desktop UI, webapp creation/update, and code-project tooling. Runtime logic remains unchanged; error handling is centralized through the wrapper.
Webapp Deployment & Preference Registration
src/lib/webapp-registry.ts, src/lib/code-projects.ts, src/app/setup-api/webapps/route.ts, src/tests/unit/*
New webapp-registry.ts module exports registerWebappInPreferences to idempotently update installed_apps, installed_meta, and hidden_installed preferences via single config-store bulk operation. New deployWebapp helper writes webapp HTML and metadata, then registers via preference-store. Refactored buildProject and POST /setup-api/webapps now delegate creation/registration to deployWebapp, with branch logic for create vs update. Tests verify hidden-app unhiding, idempotent list updates, and default-metadata application.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • ID-Robots/clawbox#135: Implements the same bearer-token auth flow for /setup-api/* requests by sourcing CLAWBOX_MCP_TOKEN / data/.mcp-token and injecting Authorization: Bearer headers in the client-side api() helper.

Suggested reviewers

  • yalexx
  • GeorgiK77

🐰 A token in the CLI, errors wrapped tight,
Bash commands blocked till allowDangerous shines bright,
Webapps register with preference-store care,
Health checks and health flows, everywhere—ware!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: harden the ClawBox MCP (auth, structured errors, health, bash safety, durable webapps)' directly and comprehensively describes the main changes: authentication hardening, error handling, health checks, bash command safety, and webapp durability.
Description check ✅ Passed The description is comprehensive and well-structured. It clearly addresses all major changes with detailed explanations of the six hardening areas, specific fixes (including the JSON parse bug fix), verification evidence from device testing, and architectural notes about implementation choices.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/mcp-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

CI Summary

✅ Tests

  • Result: passed
  • View run
  • Coverage: statements 69.65%, branches 59.4%, functions 64.85%, lines 71.73%

✅ E2E

✅ E2E Install

- Extract deployWebapp(appId, html, {name, color, icon}) — the one place that
  writes data/webapps/<id>/{index.html,meta.json} and registers the app on the
  desktop. The webapps POST route and buildProject both call it, so their
  on-disk layout / meta.json shape / desktop registration can't drift (or be
  half-applied by one caller forgetting a step).
- webapps POST: deploy+register only on create (name present); an update now
  just rewrites index.html instead of clobbering meta.json's saved name.
- Fix code-projects.test.ts: stub @/lib/webapp-registry so buildProject's new
  registration call doesn't hit real config IO — this was failing #178's `test`
  job (EACCES mkdir /home/clawbox).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
mcp/clawbox-mcp.ts (1)

139-152: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

AUTH_FAILED classification is bypassed when auth redirects are auto-followed.

For /setup-api/*, redirect-follow can turn missing/invalid bearer into a 200 login HTML response; then res.json() throws and gets classified as INVALID_RESPONSE instead of AUTH_FAILED.

Suggested fix
 async function api(path: string, options?: RequestInit) {
   const headers = new Headers(options?.headers);
+  if (!headers.has("accept")) headers.set("accept", "application/json");
   if (API_TOKEN && !headers.has("authorization")) {
     headers.set("authorization", `Bearer ${API_TOKEN}`);
   }
-  const res = await fetch(`${API_BASE}${path}`, { ...options, headers });
+  const res = await fetch(`${API_BASE}${path}`, { ...options, headers, redirect: "manual" });
   if (!res.ok) {
     const body = await res.text().catch(() => "");
     throw new ApiError(res.status, body);
   }
   return res.json();
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp/clawbox-mcp.ts` around lines 139 - 152, The api function can accidentally
follow redirects and return the login HTML as a 200, causing res.json() to throw
and be misclassified; update api (the async function api(path: string, options?:
RequestInit)) to detect auth redirects for /setup-api/* by either forcing fetch
to not auto-follow redirects (set options.redirect = 'manual') and treating any
3xx/redirect or redirected login URL as an auth failure, or by checking the
response content-type (e.g., text/html) and the path prefix when res.ok is true
and throwing an ApiError with an AUTH_FAILED classification (using API_TOKEN and
API_BASE context) instead of calling res.json(); ensure the header injection
logic (authorization via API_TOKEN) remains unchanged.
mcp/clawbox-cli.ts (1)

55-63: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent auth redirects from being misreported as JSON parse failures.

fetch currently follows /setup-api/* auth redirects to /login, which can turn auth failures into HTML parse errors. Force JSON semantics for API calls to preserve actionable HTTP auth failures.

Suggested fix
 async function api(path: string, options?: RequestInit) {
   const headers = new Headers(options?.headers);
+  if (!headers.has("accept")) headers.set("accept", "application/json");
   if (!headers.has("authorization")) {
     headers.set("authorization", `Bearer ${getApiToken()}`);
   }
-  const res = await fetch(`${API_BASE}${path}`, { ...options, headers });
+  const res = await fetch(`${API_BASE}${path}`, { ...options, headers, redirect: "manual" });
   if (!res.ok) {
     const body = await res.text().catch(() => "");
     console.error(`Error ${res.status}: ${body}`);
     process.exit(1);
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp/clawbox-cli.ts` around lines 55 - 63, The code is misreporting auth
redirects as HTML/JSON parse failures because fetch follows redirects to /login;
to fix, ensure API calls include explicit JSON semantics and avoid
auto-following redirects: add headers.set("accept", "application/json") when
constructing Headers (alongside the existing authorization header) and pass a
redirect: "manual" into the fetch options unless options.redirect is already
set, so the fetch call (using API_BASE, path, options, headers) will surface 3xx
auth responses instead of returning HTML from /login.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mcp/clawbox-cli.ts`:
- Around line 40-43: The CLI's root resolution (variable root) skips the
development-mode process.cwd() behavior used in src/lib/mcp-token.ts; update the
root resolution to match that logic by either importing and using the same
resolver from src/lib/mcp-token.ts or changing the assignment to prefer
process.env.CLAWBOX_ROOT, then process.cwd() in dev/local runs, and finally the
hardcoded "/home/clawbox/clawbox" fallback so the readFileSync(join(root,
"data", ".mcp-token")) logic will find the same token file as the server.

In `@mcp/clawbox-mcp.ts`:
- Around line 628-636: The fetch in checkApiEndpoint (and the health check
logic) currently calls await res.json() unconditionally so 401/403 or HTML
redirect pages can surface as JSON parse errors; change the logic to first
inspect res.status and immediately return explicit auth failures for 401/403
(e.g., detail `HTTP ${res.status} (token rejected)`), and only attempt
res.json() when res.ok and the Content-Type header indicates application/json —
otherwise return a non-JSON detail like `HTTP ${res.status} (non-JSON response)`
so auth/connectivity errors aren’t misdiagnosed as parse failures.

In `@mcp/README.md`:
- Around line 9-12: The fenced code block in the README lacks a language
identifier which triggers markdownlint MD040; update the block that contains the
diagram referencing clawbox-mcp.ts and clawbox-cli.ts to include a language tag
(e.g., use ```text) immediately after the opening backticks so the block reads
as a labeled text/code fence.

In `@src/app/setup-api/webapps/route.ts`:
- Around line 76-89: Change the truthy check for "name" into explicit payload
validation: treat a request as a create only when the body has a "name" property
that is a non-empty string (e.g. typeof name === "string" && name.trim().length
> 0) and call deployWebapp(appId, html, { name, color, icon }); if "name" is
present but empty return a 400 error; treat requests that do not include the
"name" property as updates but first verify the app exists under WEBAPPS_DIR
(check fs.stat or fs.access for path.join(WEBAPPS_DIR, appId)) and return 404 if
missing, otherwise continue with the existing mkdir/writeFile update flow;
ensure error responses have appropriate HTTP status codes instead of silently
falling into the wrong branch.

In `@src/lib/code-projects.ts`:
- Around line 474-477: The PR currently routes rebuilds through
deployWebapp(projectId, html, { name, color }), but deployWebapp performs
create-time persistence (rewrites meta.json, sets icon: "" when no icon passed
and re-registers the app in prefs), which wipes existing icon and clears
pref:hidden_installed on rebuilds; modify the flow so rebuilds only write
deployed files and do not perform initial registration: split deployWebapp into
two responsibilities (e.g., writeDeployedFiles or deployFiles and registerWebapp
or keep an explicit updateMode flag on deployWebapp) and when called from the
rebuild path use the "update" mode or deployFiles helper that preserves existing
meta.json fields (icon) and does not touch preferences (pref:hidden_installed)
or re-register the app, ensuring only create-time registration path calls the
registration logic.

In `@src/tests/unit/code-projects.test.ts`:
- Around line 41-45: Add an assertion to the unit test that the
durable-registration side effect is invoked: after calling buildProject(...) in
the test, assert that the mocked registerWebappInPreferences function was called
(e.g., vi.fn().toHaveBeenCalled() / toHaveBeenCalledWith(...)) with the expected
registration payload; locate the mock defined for registerWebappInPreferences
and the call site where buildProject is exercised and add a single test
expectation referencing registerWebappInPreferences and buildProject to ensure
desktop registration occurs.

---

Outside diff comments:
In `@mcp/clawbox-cli.ts`:
- Around line 55-63: The code is misreporting auth redirects as HTML/JSON parse
failures because fetch follows redirects to /login; to fix, ensure API calls
include explicit JSON semantics and avoid auto-following redirects: add
headers.set("accept", "application/json") when constructing Headers (alongside
the existing authorization header) and pass a redirect: "manual" into the fetch
options unless options.redirect is already set, so the fetch call (using
API_BASE, path, options, headers) will surface 3xx auth responses instead of
returning HTML from /login.

In `@mcp/clawbox-mcp.ts`:
- Around line 139-152: The api function can accidentally follow redirects and
return the login HTML as a 200, causing res.json() to throw and be
misclassified; update api (the async function api(path: string, options?:
RequestInit)) to detect auth redirects for /setup-api/* by either forcing fetch
to not auto-follow redirects (set options.redirect = 'manual') and treating any
3xx/redirect or redirected login URL as an auth failure, or by checking the
response content-type (e.g., text/html) and the path prefix when res.ok is true
and throwing an ApiError with an AUTH_FAILED classification (using API_TOKEN and
API_BASE context) instead of calling res.json(); ensure the header injection
logic (authorization via API_TOKEN) remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8bbfcfaa-f0e8-4308-8bd2-f135644a3119

📥 Commits

Reviewing files that changed from the base of the PR and between bfbc2fe and 4850051.

📒 Files selected for processing (8)
  • mcp/README.md
  • mcp/clawbox-cli.ts
  • mcp/clawbox-mcp.ts
  • src/app/setup-api/webapps/route.ts
  • src/lib/code-projects.ts
  • src/lib/webapp-registry.ts
  • src/tests/unit/code-projects.test.ts
  • src/tests/unit/webapp-registry.test.ts

Comment thread mcp/clawbox-cli.ts Outdated
Comment thread mcp/clawbox-mcp.ts
Comment thread mcp/README.md Outdated
Comment thread src/app/setup-api/webapps/route.ts Outdated
Comment thread src/lib/code-projects.ts Outdated
Comment thread src/tests/unit/code-projects.test.ts
The deployWebapp() extraction made the create POST call it, but webapps.test.ts only mocked WEBAPPS_DIR + APP_ID_RE — so deployWebapp was undefined and the create test 500'd. Stub it (its desktop registration is covered by code-projects/webapp-registry tests).
The structured-error wrapper renamed server.tool( -> tool(, so the source-string assertion for the browser_open registration needs the same.
- cli: resolve token-file root via dev-mode cwd like mcp-token.ts
- health: don't follow 302->/login (manual redirect + accept json) so
  auth failures aren't misreported as JSON parse errors
- readme: tag the fenced diagram block as text (markdownlint MD040)
- webapps route: treat missing 'name' as update (404 if app absent) and
  reject empty-name creates, instead of a truthiness branch
- code-projects: split writeWebappIndex from deployWebapp so rebuilds
  refresh index.html only and don't clobber icon / re-surface hidden apps
- tests: assert buildProject registers on the desktop; cover the route's
  empty-name (400), update (200) and missing-app (404) branches
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant