Skip to content

feat: admin tunnel — connect local dev to admin.deco.cx#79

Open
JonasJesus42 wants to merge 4 commits intomainfrom
feat/admin-tunnel
Open

feat: admin tunnel — connect local dev to admin.deco.cx#79
JonasJesus42 wants to merge 4 commits intomainfrom
feat/admin-tunnel

Conversation

@JonasJesus42
Copy link
Copy Markdown
Contributor

@JonasJesus42 JonasJesus42 commented Apr 10, 2026

Summary

  • Adds a daemon layer to the Vite dev server that exposes the local environment to admin.deco.cx through a reverse WebSocket tunnel (@deco-cx/warp-node)
  • When DECO_SITE_NAME is set during vite dev, the plugin registers with deco.host and serves daemon APIs for real-time file editing
  • Ported from deco-cx/deco Fresh/Deno daemon, adapted for Node.js/Vite

New modules in src/daemon/:

  • tunnel.ts — warp-node connect() with auto-reconnect
  • auth.ts — JWT verification via Web Crypto (admin.deco.cx RSA public key)
  • volumes.ts — File CRUD + JSON patch + WebSocket realtime broadcast
  • watch.ts — SSE endpoint for file change events (uses Vite chokidar watcher)
  • middleware.ts — x-daemon-api header interception, auth, routing

Usage

DECO_SITE_NAME=mysite DECO_ENV_NAME=dev-local npm run dev

Test plan

  • Set DECO_SITE_NAME and run vite dev — verify tunnel connects
  • Open admin URL — verify /live/_meta returns schema through tunnel
  • Edit block in admin — verify .deco/blocks/ file updates locally
  • Edit .deco/blocks/ locally — verify admin sees change via WebSocket
  • npm run typecheck passes

Generated with Claude Code


Summary by cubic

Connects local Vite dev to admin.deco.cx via a reverse WebSocket tunnel using @deco-cx/warp-node, and adds a daemon API for live, authenticated file editing and previews. Enables real-time .deco editing with JSON patches, file CRUD, SSE updates, and instant previews from the admin UI.

  • New Features

    • Auto-starts a tunnel when DECO_SITE_NAME is set; uses DECO_ENV_NAME (default dev), auto-reconnects, prints preview/admin URLs, and allows .deco.host and .decocdn.com in Vite allowedHosts. Usage: DECO_SITE_NAME=mysite DECO_ENV_NAME=dev-local npm run dev.
    • Daemon middleware intercepts x-daemon-api/x-hypervisor-api, applies JWT auth + CORS (bypass with DANGEROUSLY_ALLOW_PUBLIC_ACCESS=true), and routes to Volumes CRUD, FS REST, and Watch SSE; other requests fall through to Vite; adds /_healthcheck with version.
    • Volumes API serves GET/PATCH on /volumes/:id/files/* with JSON patch support, atomic commit, and WebSocket broadcasts; integrates with Vite’s watcher and ignores node_modules, .git, etc.
    • FS REST API at /fs/file/* supports GET/PATCH/DELETE with timestamps and metadata; broadcasts fs-sync events.
    • Watch/SSE on /watch or / streams an initial .deco snapshot, worker-status, and meta-info fetched from http://localhost:<port>/live/_meta.
    • Admin schema adds an action registry and auto-registration; commerce loaders and actions are registered automatically; fs-sync metadata includes blockType, __resolveType, name, and path.
  • Bug Fixes

    • Added bounds check in URN matcher used by JWT scope validation.
    • Prevented path traversal with safePath() across Volumes and FS APIs.
    • Deferred TextFileSet writes to the atomic commit step.
    • Middleware now parses URL pathnames; /live/_meta and related admin routes run in Vite SSR; meta cache shared via globalThis with proper ETag invalidation.
    • Tracked active tunnel connection for proper close() cleanup.
    • inferMetadata now defaults to { kind: "file" } for non-JSON files.

Written for commit bd626f2. Summary will update on new commits.

Adds a daemon layer to the Vite dev server that exposes the local
environment to admin.deco.cx through a reverse WebSocket tunnel
(@deco-cx/warp-node). When DECO_SITE_NAME is set, the plugin
registers with deco.host and serves daemon APIs for file editing,
JSON patching, and real-time change broadcasting.

New modules in src/daemon/:
- tunnel.ts: warp-node connect() with auto-reconnect
- auth.ts: JWT verification via Web Crypto (admin.deco.cx public key)
- volumes.ts: file CRUD + JSON patch + WebSocket realtime broadcast
- watch.ts: SSE endpoint for file change events
- middleware.ts: x-daemon-api header interception + auth + routing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

7 issues found across 9 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/daemon/tunnel.ts">

<violation number="1" location="src/daemon/tunnel.ts:41">
P0: Remove the hardcoded tunnel token fallback; require `DECO_TUNNEL_SERVER_TOKEN` from environment.</violation>

<violation number="2" location="src/daemon/tunnel.ts:118">
P1: `close()` does not actually close the active tunnel connection; it only disables reconnect logic.</violation>
</file>

<file name="src/daemon/auth.ts">

<violation number="1" location="src/daemon/auth.ts:121">
P2: Missing bounds check: if `urnParts` has fewer colon-separated segments than `resourceParts`, accessing `urnParts[idx].split("/")` at the last index throws a `TypeError`. Add a length guard to return `false` instead of crashing.</violation>
</file>

<file name="src/daemon/middleware.ts">

<violation number="1" location="src/daemon/middleware.ts:99">
P1: Root SSE daemon requests with query params are misrouted because watch routing compares the full URL string instead of pathname.</violation>
</file>

<file name="src/daemon/volumes.ts">

<violation number="1" location="src/daemon/volumes.ts:152">
P0: Path traversal vulnerability: `filePath` from the URL is joined with `cwd` without verifying the resolved path stays within the project root. A path like `/../../../etc/passwd` would escape the project directory, allowing reads of arbitrary files on the host.

Resolve the path and validate it's a child of `cwd` before any filesystem access.</violation>

<violation number="2" location="src/daemon/volumes.ts:215">
P1: Premature write breaks the atomicity guarantee. The `isTextFileSet` branch writes the file to disk immediately, but the "atomic" commit block later only writes if *all* patches succeed. If a subsequent patch fails, these early writes persist on disk — defeating the stated atomicity.

Remove the `ensureFile`/`writeFile` calls from this branch and let the atomic section handle all writes.</violation>
</file>

<file name="src/daemon/watch.ts">

<violation number="1" location="src/daemon/watch.ts:94">
P2: Initial SSE scan incorrectly skips non-JSON `.deco` files, causing incomplete first sync.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/daemon/tunnel.ts
};

const localAddr = `http://localhost:${port}`;
const apiKey =
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 10, 2026

Choose a reason for hiding this comment

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

P0: Remove the hardcoded tunnel token fallback; require DECO_TUNNEL_SERVER_TOKEN from environment.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/daemon/tunnel.ts, line 41:

<comment>Remove the hardcoded tunnel token fallback; require `DECO_TUNNEL_SERVER_TOKEN` from environment.</comment>

<file context>
@@ -0,0 +1,122 @@
+      };
+
+  const localAddr = `http://localhost:${port}`;
+  const apiKey =
+    process.env.DECO_TUNNEL_SERVER_TOKEN ??
+    "c309424a-2dc4-46fe-bfc7-a7c10df59477";
</file context>
Fix with Cubic

Comment thread src/daemon/volumes.ts Outdated
Comment thread src/daemon/tunnel.ts
Comment thread src/daemon/middleware.ts Outdated
Comment thread src/daemon/volumes.ts Outdated
Comment thread src/daemon/auth.ts
Comment thread src/daemon/watch.ts Outdated
JonasJesus42 and others added 3 commits April 10, 2026 14:57
- auth: add bounds check in URN matches() to prevent undefined access
- volumes: add safePath() to prevent path traversal attacks
- volumes: defer TextFileSet writes to atomic commit section
- middleware: use parsed pathname for route matching instead of raw URL
- tunnel: track activeConn for proper close() cleanup
- watch: inferMetadata returns { kind: "file" } for non-JSON files instead of null

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tadata

- Add /fs/file/* REST API (GET/PATCH/DELETE) matching Deno daemon protocol
- Enrich fs-sync SSE metadata with blockType, __resolveType, name, path
- Auto-register commerce loaders (131) and actions (24) in admin schema
- Fix module isolation: /live/_meta falls through to Vite SSR
- Fetch meta-info SSE event via HTTP localhost
- Add .deco.host to Vite allowedHosts for tunnel domain requests
- Add .ts extensions to all daemon import chains for Bun/Node native ESM

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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