Skip to content

feat(init): add Cloudflare DO agent transport with server-controlled split - #1180

Closed
betegon wants to merge 3 commits into
mainfrom
feat/init-cf-agent-cli
Closed

betegon wants to merge 3 commits into
mainfrom
feat/init-cf-agent-cli

Conversation

@betegon

@betegon betegon commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

Adds a second transport for sentry init: a reconnecting WebSocket client that talks to the new Cloudflare Durable Object agent (server side: getsentry/cli-init-api#187). Which transport a run uses is decided server-side via /route, so the canary split changes without shipping a new CLI. The default and fallback is the existing Mastra path, so this is a no-op for current users until the split is turned up.

Older CLIs never call /route, so they are unaffected — they keep using Mastra.

Architecture

This diagram is shared with the server PR.

flowchart TB
  U["Developer runs: sentry init"]

  subgraph CLI["CLI (sentry binary, Node)"]
    PF["preflight: org / project / team + Sentry auth"]
    RT["resolveTransport(): GET /route"]
    ADR["agent-do-runner: reconnecting WebSocket client"]
    LT["local tools + prompts (executeTool, handleInteractive)"]
  end

  subgraph NET["Transport"]
    WS["WebSocket - live and resilient (client pings + reconnect)"]
    LEGACY["HTTP suspend/resume (Mastra, existing)"]
  end

  subgraph SRV["Server: Cloudflare Worker (agents SDK)"]
    RTE["/route: server-controlled canary split (AGENT_DO_PERCENT)"]
    DO["SentryInitAgent: Durable Object"]
    AG["one agent: analyze + gates + implement + verify"]
  end

  subgraph INF["Infra and libraries"]
    SQL["DO embedded SQLite: durable phase + pending (no D1)"]
    GW["Vercel AI Gateway via ai SDK -> LLM"]
    SAPI["Sentry API: ensure project + DSN"]
    MASTRA["Mastra worker + D1 (existing path)"]
  end

  U --> PF --> RT
  RT -->|"decision"| RTE
  RT -->|"agent-do"| ADR
  RT -->|"mastra"| LEGACY
  ADR <-->|"tool-request / prompt / result"| WS
  WS <--> DO
  DO --> AG
  AG -->|"LLM turns"| GW
  AG -->|"ensure project"| SAPI
  DO --> SQL
  ADR -.->|"runs tools locally, returns results"| LT
  LEGACY <--> MASTRA
Loading

Changes

  • agent-do-runner.ts (new): the WebSocket client. Reuses the existing executeTool registry and handleInteractive prompts, reconnects on drop (deploys / laptop sleep), and sends client-side WebSocket pings for idle keepalive (Cloudflare auto-answers pong without waking the DO).
  • wizard-runner.ts: resolveTransport() asks the server /route and obeys; runViaAgentDO() drives the run. The Mastra suspend/resume path is untouched. SENTRY_INIT_AGENT_DO=1|0 is a manual override; SENTRY_INIT_AGENT_DO_PERCENT is a local fallback if /route is unreachable.
  • Adds ws (bundled into the single-file build).

Test plan

  • Ran against the deployed worker; server-controlled split verified at 50% and forced to 100%.
  • /verify-sentry confirmed real errors + traces for node-express and nextjs.
  • Mastra path unchanged (default when /route says so or is unreachable).

Paired with getsentry/cli-init-api#187 (server side).

betegon and others added 2 commits July 2, 2026 21:43
…split

New reconnecting WebSocket client that runs sentry init against the
Durable Object agent, reusing the existing local tools + interactive
prompts. Transport is chosen server-side via /route, defaulting to the
existing Mastra path, so the canary percentage changes without a CLI
release. Older CLIs are unaffected.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Jul 2, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://cli.sentry.dev/_preview/pr-1180/

Built to branch gh-pages at 2026-07-02 20:09 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Codecov Results 📊

❌ Patch coverage is 11.69%. Project has 5392 uncovered lines.
❌ Project coverage is 81.05%. Comparing base (base) to head (head).

Files with missing lines (3)
File Patch % Lines
src/lib/init/agent-do-runner.ts 2.08% ⚠️ 188 Missing
src/lib/init/wizard-runner.ts 56.25% ⚠️ 14 Missing and 6 partials
src/lib/init/constants.ts 71.43% ⚠️ 2 Missing and 1 partials
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
- Coverage    81.62%    81.05%    -0.57%
==========================================
  Files          408       409        +1
  Lines        28227     28458      +231
  Branches     18384     18569      +185
==========================================
+ Hits         23040     23066       +26
- Misses        5187      5392      +205
- Partials      1886      1894        +8

Generated by Codecov Action

Comment thread src/lib/init/agent-do-runner.ts
Comment on lines +627 to +631
const base = (args.agentDoUrl ?? INIT_AGENT_DO_URL).replace(
TRAILING_SLASHES_RE,
""
);
const url = `${base}/agents/sentry-init-agent/${runId}`;

@sentry-warden sentry-warden Bot Jul 2, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Server-supplied agentDoUrl used as WebSocket target without scheme validation, risking auth-token exposure over cleartext

resolveTransport() returns agentDoUrl from the /route JSON body with no validation, and runViaAgentDO uses it verbatim to build the WebSocket URL (args.agentDoUrl ?? INIT_AGENT_DO_URL). There is no check that the value uses the secure wss:// scheme. connectOnce then sends the Sentry auth token as an Authorization: Bearer header to whatever host/scheme the URL specifies. If the server returns (or is misconfigured to return) a ws:// URL, the bearer token is transmitted in cleartext and can be captured by an on-path network attacker. This is a downgrade/defense-in-depth weakness independent of server trust. Note the escalated 'arbitrary code execution' concern is largely inherent to the intended design: a server that routes a run to the DO transport already drives local tool execution (run-commands via executeTool) over the legitimate connection, so a compromised/malicious /route server is already fully trusted in this flow; the distinct, avoidable risk here is the lack of a secure-scheme (and ideally host allowlist) check before the token is sent.

Evidence
  • resolveTransport() (wizard-runner.ts:464-469) casts the /route body as { agentDoUrl?: string } and returns data.agentDoUrl with no scheme or host validation.
  • runViaAgentDO (agent-do-runner.ts:627) uses args.agentDoUrl ?? INIT_AGENT_DO_URL directly to form the WS URL; a ws:// value bypasses TLS.
  • connectOnce (agent-do-runner.ts:387-390) attaches headers: { Authorization: 'Bearer ${token}' } to the WebSocket regardless of scheme, so a cleartext ws:// target leaks the token.
  • The /route request itself is TLS-protected (customFetch over the https-normalized base), so injecting a malicious URL requires a compromised server; that same server can already issue run-commands, so the RCE angle is not a new privilege — the concrete avoidable defect is the missing wss:// enforcement.
Also found at 3 additional locations
  • src/lib/init/wizard-runner.ts:466-469
  • src/lib/init/wizard-runner.ts:49
  • src/lib/init/wizard-runner.ts:934

Identified by Warden find-bugs, security-review · YJR-CDF

If the first WebSocket connect failed before `open` (e.g. a transient on
the upgrade), the reconnect loop still marked start as sent, so the retry
never sent `start` and the run hung. Track whether start was actually sent
and emit it on the first socket that opens.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions github-actions Bot added the risk: medium PR risk score: medium label Sep 21, 2026
@betegon betegon closed this Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant