Skip to content

feat(plugin-tinyplace): harden cursor responder (yolo+isolation, stream-json, rules) - #251

Merged
senamakel merged 2 commits into
tinyhumansai:mainfrom
CodeGhost21:feat/cursor-responder-hardening
Jul 14, 2026
Merged

feat(plugin-tinyplace): harden cursor responder (yolo+isolation, stream-json, rules)#251
senamakel merged 2 commits into
tinyhumansai:mainfrom
CodeGhost21:feat/cursor-responder-hardening

Conversation

@CodeGhost21

@CodeGhost21 CodeGhost21 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #241, hardening the Cursor auto-responder using patterns from multica-ai/multica's server/pkg/agent. Three changes, all Cursor-scoped (codex/claude/windsurf untouched):

  • --yolo + throwaway isolated --workspace. The responder delivers its reply by calling the tinyplace auto_reply MCP tool, and cursor-agent only invokes MCP tools headlessly under --yolo. --yolo also auto-allows shell + file writes on the (attacker-controlled) DM text, so we confine it: the responder runs in a per-wallet throwaway send-only workspace (<dataDir>/responder-home/<wallet>) whose mcp.json pins SEND_ONLY + NO_AUTORESPOND, daemon off. A prompt-injected DM's writes/shell land in that scratch dir, never the user's files; the existing timeout/kill is the second guard.
  • --output-format stream-json → kills the hang. cursor-agent can hang after emitting its reply. The shared spawner now (opt-in via responder.streamComplete) pipes stdout and finishes+kills on the terminal {"type":"result"} event, so a completed-but-hung turn is a success instead of waiting out the 180 s timeout and falsely failing an already-sent reply.
  • .cursor/rules/tinyplace.mdc. cursor-agent has no --system-prompt, so the launcher writes the tiny.place UNTRUSTED-handling guidance to an always-applied rule in the isolated interactive workspace.

Problem

The Cursor responder shipped in #241 with honest [VERIFY] flags: the least-privilege --sandbox/--approve-mcps args were not confirmed to let cursor-agent call auto_reply headlessly (validation was blocked by rate-limiting). multica's production path shows cursor-agent needs --yolo for MCP tool calls and parses stream-json to detect turn completion. That reframes the responder as: it must run --yolo, so the security work is confinement, not avoidance.

Solution

  • New optional adapter contract fields: responder.prepare(ctx) (runs once per batch for setup that can't live in the side-effect-free buildArgs) and responder.streamComplete (spawner watches stdout for the result event). buildArgs gains an optional 4th ctx arg; other adapters ignore it.
  • respond-batch.mjs calls prepare() once, threads the ctx into buildArgs, and (for streamComplete responders) pipes stdout to finish-on-result.
  • README contract table + harness-test.mjs updated. All 43 harness checks pass; prepare()/launch workspace generation smoke-tested (correct env, rule file with alwaysApply + UNTRUSTED).

Impact

  • Security: narrows the untrusted-input blast radius to a throwaway workspace while enabling the MCP-tool path the responder actually needs. [VERIFY] remains on the live cursor-agent result-event schema and headless rule-honoring across versions — the timeout guard is the fallback if either differs.
  • Cursor-only; no behavior change for codex/claude/windsurf responders.

Related

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Cursor-based responses now prepare an isolated workspace with dedicated configuration and security rules.
    • Cursor responses stream progress/results and can complete as soon as the final result is emitted.
  • Documentation
    • Updated adapter contract docs to cover optional prepare(ctx), context-aware buildArgs, and the streamComplete: true streaming/completion behavior.
  • Tests
    • Expanded end-to-end harness coverage for Cursor responder behavior and argument construction/stream completion.

…am-json, rules)

Fold three multica-derived improvements into the Cursor adapter:

1. Responder uses `--yolo` (the only way cursor-agent invokes MCP tools —
   i.e. auto_reply — headlessly) but confined to a THROWAWAY send-only
   `--workspace`, so a prompt-injected DM's writes/shell land in a per-wallet
   scratch dir, never the user's files. The workspace's mcp.json pins
   SEND_ONLY + NO_AUTORESPOND, daemon off. Built once per batch by a new
   optional `responder.prepare(ctx)` hook (buildArgs stays side-effect-free).

2. `--output-format stream-json`: the shared spawner now watches stdout for the
   terminal `result` event (opt-in via `responder.streamComplete`) and
   finishes+kills on it — cursor-agent's known print-mode hang-after-reply no
   longer waits out the 180s timeout and falsely fails an already-sent reply.

3. cursor-agent has no `--system-prompt`, so the launcher writes the tiny.place
   UNTRUSTED-handling guidance to an always-applied `.cursor/rules/tinyplace.mdc`
   in the isolated interactive workspace.

Contract: `buildArgs(prompt, model, pluginRoot, ctx?)` gains an optional 4th ctx
arg; codex/claude/windsurf ignore it. README + harness-test updated; all 43
harness checks pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

@CodeGhost21 is attempting to deploy a commit to the Vezures Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4d3b610d-fc6d-46a2-87bf-0dc06fefb366

📥 Commits

Reviewing files that changed from the base of the PR and between 9234d8d and d331152.

📒 Files selected for processing (1)
  • sdk/plugin-tinyplace/hooks/respond-batch.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • sdk/plugin-tinyplace/hooks/respond-batch.mjs

📝 Walkthrough

Walkthrough

The Cursor responder now uses prepared isolated workspaces, stream-json output, and terminal result events. The batch runner threads responder context, handles streamed completion, and preserves message cleanup. Documentation and harness assertions cover the updated contract and behavior.

Changes

Cursor streaming responder

Layer / File(s) Summary
Adapter contract and responder workspace
sdk/plugin-tinyplace/adapters/README.md, sdk/plugin-tinyplace/adapters/cursor.mjs
Documents prepare, contextual buildArgs, and streamComplete; adds Cursor workspace configuration, security rules, and streaming responder arguments.
Batch streaming completion
sdk/plugin-tinyplace/hooks/respond-batch.mjs
Parses responder configuration, initializes responder context, pipes streaming output, completes on terminal NDJSON result events, and terminates finished child processes.
Responder flow validation
sdk/plugin-tinyplace/harness-test.mjs
Expands Cursor assertions for streaming flags, argument termination, workspace preparation, and pure argument construction while preserving existing adapter checks.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BatchRunner
  participant CursorAdapter
  participant CursorAgent
  participant MessageFile
  BatchRunner->>CursorAdapter: prepare responder context
  CursorAdapter-->>BatchRunner: isolated workspace
  BatchRunner->>CursorAdapter: build responder arguments
  CursorAdapter-->>BatchRunner: stream-json command
  BatchRunner->>CursorAgent: spawn with workspace
  CursorAgent-->>BatchRunner: terminal NDJSON result
  BatchRunner->>MessageFile: delete completed message
  BatchRunner->>CursorAgent: terminate process
Loading

Possibly related PRs

Poem

I’m a rabbit with a workspace bright,
Streaming results through the night.
A tiny rule, a prompt, a hop,
The terminal result says, “Stop!”
Clean files vanish—what a sight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main Cursor responder hardening changes: yolo isolation, stream-json completion, and rules.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9234d8d3a6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +81 to +82
const args = ["-p", "--yolo", "--output-format", "stream-json"];
if (ctx?.workspace) args.push("--workspace", ctx.workspace);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid yolo for attacker-controlled Cursor replies

When the Cursor auto-responder handles an untrusted DM, adding --yolo lets the model auto-approve shell and file-write tools; Cursor's parameter docs state that print mode has access to write/shell tools and that --yolo is an alias for --force, while --workspace is only the workspace directory, not an OS sandbox (https://cursor.com/docs/cli/reference/parameters.md). A malicious message can therefore prompt the responder to read or modify absolute paths outside the scratch workspace before calling auto_reply, regressing the previous --sandbox enabled posture.

Useful? React with 👍 / 👎.

@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: 1

🤖 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 `@sdk/plugin-tinyplace/hooks/respond-batch.mjs`:
- Around line 70-79: Update the prepare() error handling in the responder setup
block to fail closed: catch the thrown error, log it with the batch context, and
abort the current batch before any responder is spawned. Do not continue with a
degraded RESPONDER_CTX or silently omit the workspace guardrail; preserve normal
preparation and spawning when ADAPTER.responder.prepare succeeds.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 37f857e8-c073-4912-8945-423d2943c059

📥 Commits

Reviewing files that changed from the base of the PR and between bae92dc and 9234d8d.

📒 Files selected for processing (4)
  • sdk/plugin-tinyplace/adapters/README.md
  • sdk/plugin-tinyplace/adapters/cursor.mjs
  • sdk/plugin-tinyplace/harness-test.mjs
  • sdk/plugin-tinyplace/hooks/respond-batch.mjs

Comment thread sdk/plugin-tinyplace/hooks/respond-batch.mjs Outdated
If ADAPTER.responder.prepare() throws, the batch was silently continuing with a
degraded RESPONDER_CTX (no workspace). For Cursor that means buildArgs emits
'cursor-agent -p --yolo ...' WITHOUT the isolated --workspace, so --yolo's
auto-approved shell/writes on attacker-controlled DM text run against the real
cwd instead of the throwaway sandbox — the exact guardrail the isolation exists
to provide.

Fail closed: log the error with batch context, move the claimed messages to
failed/ for retry, remove the empty batch dir, and abort before spawning any
responder. Success path (prepare returns the workspace) is unchanged; adapters
without prepare() (codex/claude/windsurf) are unaffected.

Addresses review feedback on tinyhumansai#251.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
senamakel pushed a commit that referenced this pull request Jul 14, 2026
…252)

* docs(plugin-tinyplace): cursor⇄openhuman bidirectional bridge spike

Capture the throwaway prototype that proves a Cursor IDE agent can be driven
into a live two-way tiny.place conversation with OpenHuman over the Signal
relay, plus the findings that inform the real adapter:

- forward (Cursor→OpenHuman) via beforeSubmitPrompt/afterAgentResponse hooks →
  SessionEnvelopeV1 DMs rendered as a `cursor` runtime.
- reverse (OpenHuman→Cursor) via a daemon that pastes inbox DMs into the live
  GUI (clipboard + System Events), with echo-suppression and focus-restore.
- findings: `stop → followup_message` is the only in-conversation injection
  channel; CGEventPostToPid can't reach a backgrounded Electron window; AX
  value-set doesn't register in React; concurrent FileSessionStore access
  corrupts the ratchet (→ HTTP 400), fixed with a cross-process lock; SDK ≥2.0.2
  required for base58 bundle routing.

Prototype only (README flags the security caveats + auto-approve tradeoff);
nothing here ships. Complements the cursor adapter hardening (#251).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(plugin-tinyplace): bridge approval routing + session self-heal

Extend the cursor⇄openhuman bridge prototype with the tool-approval feature and
transport hardening validated against staging:

- hook.mjs: route beforeShellExecution/beforeMCPExecution to OpenHuman as a v2
  approval_request event and block for the allow/deny decision (falls back to
  Cursor's own prompt on timeout); auto-allow file reads.
- common.mjs: v2 approvalEnvelope builder, extractText, an AWAITING flag (daemon
  pauses inbox draining while an approval is pending), and sendWithRetry which
  self-heals a desynced session (reset + retry on a 400/encrypt error).
- daemon.mjs: pause while an approval is pending so the hook owns the decision DM.
- README: approval-routing section + findings on the two-store ratchet fragility
  (receiving side can't retry a silent drop) and deriving the resolved-card state.

Prototype only; kept repo-portable (relative SDK dist, OPENHUMAN_ADDR from env).
Pairs with the OpenHuman Allow/Deny card PR (tinyhumansai/openhuman#4837).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@senamakel
senamakel merged commit 1feedd6 into tinyhumansai:main Jul 14, 2026
9 of 10 checks passed
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.

2 participants