Skip to content

Add Ralph — built-in work monitor squad member#15

Merged
bradygaster merged 3 commits intobradygaster:mainfrom
spboyer:feature/ralph-work-monitor
Feb 11, 2026
Merged

Add Ralph — built-in work monitor squad member#15
bradygaster merged 3 commits intobradygaster:mainfrom
spboyer:feature/ralph-work-monitor

Conversation

@spboyer
Copy link
Copy Markdown
Contributor

@spboyer spboyer commented Feb 11, 2026

Closes #14

Summary

Adds Ralph as a built-in squad member (like Scribe) whose job is keeping tabs on work. Once activated, Ralph self-chains the coordinator's work loop — checking for open issues, draft PRs, review feedback, CI failures — and keeps the squad working through the backlog without manual nudges.

Ralph never stops on his own while work remains. He keeps cycling until every issue is closed, every PR is merged, and CI is green. The only things that stop Ralph: the board is clear, the user says "idle", or the session ends.

What's new

🔄 Ralph — Work Monitor

  • Built-in squad member, always on the roster, exempt from universe casting
  • Badge: 🔄 Monitor (alongside 📋 Silent for Scribe)
  • Self-chaining work loop: after every agent batch, Ralph checks GitHub for more work and immediately continues without asking permission
  • Periodic check-in every 3-5 rounds with summary (reports status, keeps going)
  • Configurable scope: issues only, skip CI, watch everything

Critical loop behavior

Ralph's loop is explicit and non-negotiable in the coordinator instructions:

  • MUST NOT stop and wait for user input between work items
  • DO NOT stop. DO NOT wait for user input. IMMEDIATELY go back to Step 1
  • Check-ins report progress but say "Continuing..." — they do NOT ask permission
  • User must explicitly say "idle" or "stop" to break the loop

Trigger phrases

Action Phrases
Start "Ralph, go", "Keep working", "Work until done"
Stop "Ralph, idle", "Take a break", "Stop monitoring"
Status "Ralph, status", "What's on the board?"
Scope "Ralph, just issues", "Ralph, skip CI"

Work categories monitored

Category Signal Action
Untriaged issues squad label, no member sub-label Lead triages and assigns
Assigned issues squad:{member} label, unstarted Spawn agent to pick it up
Draft PRs Squad member PR in draft Check if stalled, nudge
Review feedback Changes requested Route to author agent
CI failures PR checks failing Notify agent to fix
Approved PRs Ready to merge Merge and close issue

Heartbeat workflow (between sessions)

squad-heartbeat.yml — runs on cron (every 30 min) + event triggers:

  • issues.closed — check for next item in backlog
  • pull_request.closed — check for follow-up work
  • issues.labeled — triage new squad-labeled issues
  • workflow_dispatch — manual trigger

The heartbeat auto-triages issues based on team roles and keywords, and assigns copilot-swe-agent[bot] for @copilot issues (if auto-assign is enabled with COPILOT_ASSIGN_TOKEN).

Files changed

File Change
.github/agents/squad.agent.md Ralph section with CRITICAL loop behavior, routing signals, casting exemption, follow-up work hook
templates/roster.md Ralph row: | Ralph | Work Monitor | — | 🔄 Monitor |
templates/workflows/squad-heartbeat.yml New — heartbeat workflow with auto-triage and @copilot assignment
index.js Fix upgrade early-exit path to refresh workflows and agent.md
test/index.test.js 10 new tests for Ralph
docs/features/ralph.md New — full feature documentation
README.md Ralph in team table, architecture diagram, What's New

Upgrade safety

  • Upgrade does NOT touch .ai-team/ — team state is preserved
  • Ralph appears on the roster template; existing teams get Ralph on next init or by adding the row manually
  • Heartbeat workflow is squad-owned and refreshed on upgrade

Bug fixed

Upgrade early-exit: When version matches (isAlreadyCurrent), the upgrade path now refreshes workflows and squad.agent.md instead of exiting without updating them.

Testing

  • 43 tests passing (10 new for Ralph)
  • Tested on spboyer/waza — identified and fixed issue where Ralph stopped after Round 1 (loop instructions were too passive)

Ralph is a built-in squad member (like Scribe) whose job is keeping
tabs on work. Once activated, Ralph self-chains the coordinator's
work loop: checks for open issues, draft PRs, review feedback, CI
failures, and keeps the squad working through the backlog.

- Add Ralph section to squad.agent.md with triggers, work-check
  cycle, periodic check-in, and scope configuration
- Add Ralph to roster template (🔄 Monitor badge)
- Add Ralph to routing signals table and casting exemptions
- Create squad-heartbeat.yml workflow for between-session automation
  (cron + event triggers, auto-triage, @copilot assignment)
- Fix upgrade early-exit path to refresh workflows and agent.md
- Add 10 tests for Ralph behavior
- Document in docs/features/ralph.md
- Update README with Ralph in team table and architecture diagram
Ralph was stopping after Round 1 instead of continuing through the
backlog. Made the loop behavior explicit and non-negotiable:

- CRITICAL BEHAVIOR block: coordinator MUST NOT stop between items
- Step 3: DO NOT stop, DO NOT wait, IMMEDIATELY scan again
- Check-in reports status but does NOT ask permission to continue
- Integration section: do NOT return control to user
- Updated docs to match: Ralph keeps going, user must say idle
@bradygaster bradygaster merged commit 7a4e18c into bradygaster:main Feb 11, 2026
tamirdresher added a commit to tamirdresher/squad that referenced this pull request Mar 2, 2026
Cherry-picked from cli-tunnel PR bradygaster#15:
- stripInvisible(): ANSI escape bypass protection
- NFKC normalization before redaction
- 6 new token patterns (GitLab, Vault, GitHub PAT, Databricks, HuggingFace, credential-in-URL)
- Heartbeat interval 30s → 120s (prevents phone disconnect in background)

18/18 remote-control tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
bradygaster added a commit that referenced this pull request Mar 4, 2026
…547)

* feat: migrate SDK + CLI source files into workspace packages

Phase 1 (SDK):
- Copy 15 directories + 4 standalone files from src/ into packages/squad-sdk/src/
- Remove CLI re-exports from SDK barrel (src/index.ts)
- Update SDK package.json exports map with all subpath exports

Phase 2 (CLI):
- Copy src/cli/ and src/cli-entry.ts into packages/squad-cli/src/
- Copy templates/ into packages/squad-cli/templates/
- Rewrite 6 cross-package imports to use @bradygaster/squad-sdk/* package imports
- Intra-CLI imports left as relative paths

Root src/ preserved for now (cleanup after tests pass).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: complete SDK/CLI workspace migration and publish to npm

- Updated tsconfigs with composite builds and project references
- Fixed package.json exports maps, dependencies, and build scripts
- Aligned versions to 0.8.0 (clean break from 0.7.0 stubs)
- Root package now private workspace orchestrator
- SDK: 306 files, 18 subpath exports, pure library
- CLI: 204 files, ink/react UI, templates included
- Both packages published to npm as @bradygaster/squad-sdk@0.8.0
  and @bradygaster/squad-cli@0.8.0

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(ai-team): sdk-cli-split execution, version alignment, test verification

Session: 2026-02-22T041800Z-npm-publish-migration
Orchestration: Keaton (sync), Fenster (sync), Edie (background), Rabin (background), Kobayashi (background), Hockney (background), Coordinator (background)

Changes:
- Logged 7 orchestration entries (.squad/orchestration-log/) and session summary (.squad/log/)
- Merged 8 decision inbox files to .squad/decisions.md (keaton-sdk-cli-split-plan, kobayashi-version-alignment, hockney-test-import-migration, edie-build-system-migration, edie-subpath-exports, kujan-barrel-files, copilot-directive repo scope)
- Cleared .squad/decisions/inbox/ (all 8 files merged)
- Updated 5 agent histories with team updates (keaton, fenster, edie, rabin, kobayashi, hockney)
- Verified: Build clean, 1719 tests passing, SDK/CLI split complete, versions aligned to 0.8.0, packages published to npm

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: clean up stale root artifacts

- Removed 13 orphaned .test-cli-* temporary directories from pre-migration era
- Removed stale root dist/ directory (each package has its own dist/)
- .test-cli-* and dist/ already in .gitignore, no changes needed there
- Verified build succeeds after cleanup

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add coverage config and package exports integration test

- Configure @vitest/coverage-v8 with text, text-summary, and html reporters
- Add coverage include/exclude patterns for src and packages
- Create package-exports.test.ts verifying SDK exports map (8 subpaths)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor: migrate test imports to workspace packages

Migrate all 56 test files from relative ../src/ imports to workspace
package imports (@bradygaster/squad-sdk and @bradygaster/squad-cli).

SDK imports use subpath exports (e.g., @bradygaster/squad-sdk/config,
@bradygaster/squad-sdk/agents, @bradygaster/squad-sdk/runtime).

CLI imports use @bradygaster/squad-cli subpath exports for shell,
core, and command modules.

Changes:
- 173 import path replacements across 56 test files
- Added 8 new SDK subpath exports (adapter/errors, config/migrations,
  runtime/event-bus, runtime/benchmarks, runtime/i18n,
  runtime/telemetry, runtime/offline, runtime/cost-tracker)
- Added 16 CLI subpath exports for shell, core, and command modules
- Added missing barrel re-exports: selectResponseTier/getTier in
  coordinator/index.ts, onboardAgent/addAgentToConfig in
  agents/index.ts
- Updated consumer-imports test to import CLI functions from
  @bradygaster/squad-cli instead of SDK barrel
- All 1727 tests pass across 57 test files

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* main to dev (#299)

* test artifact (#183)

* Bradygaster/dev (#184)

* test artifact

* fixed readme

* chore: remove squad-main-guard workflow

The .squad folder is already excluded from npm distribution via the
files field in workspace package.json configs. The guard that prevented
.squad/ from reaching protected branches is no longer needed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Squad Remote Control — bridge, CLI command, and PWA client

Phase 1 MVP implementation:
- RemoteBridge class (SDK): WebSocket server, EventBus integration,
  message history buffer, streaming delta broadcast
- Wire protocol: 15 message types (server events + client commands),
  JSON-based, versioned
- squad rc CLI command: starts bridge, creates devtunnel with squad
  labels, shows QR code for phone access
- PWA client: chat UI with streaming, agent sidebar, tool call
  visibility, permission dialogs, dark theme, mobile-optimized
- Devtunnel helper: create/host/cleanup lifecycle with labels for
  discovery (repo, branch, machine hostname)

New files: 11 | Modified: 4 | Zero breaking changes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: use http protocol for devtunnel port (bridge is plain HTTP, tunnel handles TLS)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: wire Phase 2 — agent roster, streaming responses, /commands, @agent routing

- Load team.md roster on startup (8 Star Trek agents loaded)
- onPrompt routes to lead agent with simulated streaming response
- onDirectMessage routes to specific @agent (e.g. @Worf)
- /status and /agents slash commands return live session info
- Agent status updates (idle→streaming→idle) broadcast to PWA
- Full conversation history maintained and synced to new clients

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove manifest.json link causing blank screen on mobile via devtunnel

Devtunnel's auth interceptor redirects manifest.json requests, causing
mobile browsers to show a blank page. Also added:
- Visible 'Connecting...' system message on startup
- Exponential backoff reconnection (1s → 30s max)
- Better error display in chat area

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Copilot ACP bridge with version compatibility check

- CopilotBridge spawns copilot --acp --stdio and relays JSON-RPC
- Static checkCompatibility() method tests if copilot responds to
  initialize request before attempting connection
- Graceful fallback to simulated responses when ACP isn't available
- Current Copilot CLI v0.0.419 doesn't support ACP stdio yet

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor: switch to dumb pipe architecture for Copilot ACP

Bridge now transparently relays WebSocket ↔ copilot stdin/stdout.
The ACP protocol (initialize, session/new, session/prompt) is driven
by the PWA client, not the server — matching Uplink's architecture.

Added setPassthrough() and passthroughFromAgent() to RemoteBridge.
When passthrough is active, raw JSON-RPC flows through untouched.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: rewrite PWA to speak ACP protocol directly

PWA now drives the ACP lifecycle over WebSocket:
- Sends initialize → session/new → session/prompt (JSON-RPC 2.0)
- Handles session/update notifications for streaming chunks
- Handles session/request_permission for tool approvals
- Bridge is transparent dumb pipe (WebSocket ↔ copilot stdio)

Note: copilot --acp --stdio on v0.0.419 doesn't respond to
initialize. Architecture is correct (matches Uplink) but
blocked on Copilot CLI version.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: real Copilot responses working! Fix ACP startup + cwd injection

Key fixes:
- Use native copilot.exe path for reliable stdio on Windows
- Bridge intercepts session/new to inject correct cwd (PWA doesn't
  know server filesystem paths)
- PWA retries initialize 5x with 5s delays (Copilot needs ~20s to
  load MCP servers before responding)
- Increased timeouts: 60s for initialize, 120s for prompts

Tested: real Copilot response received through dumb pipe relay!

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: terminal-style UI + ACP event recording for history replay

Major rewrite:
- Raw terminal renderer (monospace, dark bg, green prompt, blinking cursor)
- Tool calls as collapsible cards with icons (📖read, ✏️edit, ▶️shell, 🔍search)
- Diff rendering (red/green) for file edits
- Streaming text with live cursor animation
- Bridge records all ACP events and replays to late-joining clients
- Permission dialogs as overlay modals
- User input echoed with ❯ prompt (CLI-style)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: permission dialog overflow — truncate raw JSON, add scroll limits

Permission dialogs now show clean title + first line of command
instead of raw JSON blob. Added max-height with scroll to prevent
blocking the entire viewport on mobile.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: permission dialog pinned to bottom, max 40vh, buttons always visible

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: session dashboard — list all running Squad RC sessions

- Bridge serves /api/sessions endpoint (runs devtunnel list --labels squad)
- PWA shows dashboard with session cards: repo, branch, machine, online status
- Tap a session card → navigates to that session's tunnel URL
- Toggle between Dashboard and Terminal views via header button
- Sessions from any machine show up (devtunnel scoped to user identity)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: session dashboard with filter + cleanup

- By default only shows online sessions
- 'Show offline' toggle reveals stale sessions
- 'Clean offline' bulk-deletes all stale tunnels
- Individual ✕ button to delete single offline sessions
- Refresh button to reload session list
- Delete API: /api/sessions/:id (DELETE)
- Fixed devtunnel delete flag (--force not -y)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: squad start — local CLI + remote mirror (bidirectional)

New command: squad start [--tunnel] [--port <n>]
- Spawns copilot --acp with full capabilities (/plugin, /skills, /agent etc.)
- Local readline with green ❯ prompt — type in terminal as usual
- Renders streaming responses, tool calls, permissions in terminal
- Remote bridge mirrors ALL output to phone via tunnel
- Phone can send messages INTO the same session (bidirectional)
- Auto-approves permissions in terminal mode
- Initializes ACP with retry (waits for MCP servers)

Usage:
  squad start --tunnel    # start with remote access
  squad start             # local only

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: PTY mirror mode — see exact Copilot CLI output remotely

Rewrote squad start to use node-pty instead of ACP mode:
- Copilot runs in a PTY with full TUI (diffs, colors, progress bars)
- Raw terminal output mirrored to phone via WebSocket
- ANSI escape codes converted to HTML colors on phone
- Bidirectional: phone input injected as PTY keystrokes
- Local terminal shows exact same output as running copilot directly
- stdin in raw mode for full terminal experience (Ctrl+C, arrows, etc.)

All copilot features work: /plugin, /skills, /agent, interactive
prompts, file edits with diffs — the real deal.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: xterm.js integration — real terminal emulator on phone

Replace DIV-based ANSI renderer with xterm.js — a full terminal
emulator running in the browser. Renders ALL ANSI escape codes
correctly: colors, cursor movement, screen clearing, box drawing.

The phone shows the EXACT same output as the CLI terminal.
Keyboard input from xterm.js goes directly to the PTY.

Loaded from CDN: @xterm/xterm@5.5.0 + @xterm/addon-fit@0.10.0

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: sync PTY terminal size with phone's xterm.js dimensions

When phone connects, xterm.js sends its cols/rows to the bridge,
which resizes the PTY. This fixes spinner animations appearing as
new lines instead of overwriting — copilot now knows the correct
terminal dimensions for cursor positioning.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: include port in tunnel labels so dashboard generates correct URLs

Each tunnel now gets a 'port-NNNN' label. The sessions API reads
it back to construct the correct URL (e.g., port-3457 → -3457 in URL).
Fixes session 2 showing unreachable URL when using different port.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: pass copilot flags through squad start

squad start --tunnel --yolo → copilot --yolo
squad start --tunnel --model gpt-5.2 → copilot --model gpt-5.2
All flags after squad's own (--tunnel, --port) pass to copilot.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: mobile key bar + --command flag for custom CLI

Mobile: added toolbar with ↑↓→← Tab Enter Esc Ctrl+C Space ⌫
buttons above the input for navigating copilot menus on phone.

Custom CLI: --command flag to use a different binary:
  squad start --tunnel --command 'agency copilot'

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add start command to help, remove rc, add 18 tests

- Added 'start' to squad --help with usage examples
- Removed 'rc' command (superseded by 'start' with PTY mode)
- Added 18 tests covering:
  - Protocol serialization/parsing (5 tests)
  - Bridge lifecycle, WebSocket, broadcasting (4 tests)
  - Message history + cap (2 tests)
  - Streaming deltas, agent roster (2 tests)
  - Passthrough mode, event recording/replay (2 tests)
  - Ping/pong, HTTP serving, cwd injection (3 tests)

All 18 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: sanitize dots in tunnel labels (devtunnel only allows [\w-=])

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: escape hyphen in label regex character class

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: keep key bar visible in PTY mode — only hide text input form

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: apply security audit fixes to remote control bridge

CRITICAL-1: OS Command Injection - Replace execSync with execFileSync
  array syntax in handleDeleteSession, add regex validation for tunnel IDs
HIGH-1: CDN without SRI - Add integrity/crossorigin attributes to all
  CDN script/link tags (xterm CSS, xterm JS, addon-fit JS)
HIGH-2: CORS wildcard - Remove Access-Control-Allow-Origin: * from
  handleSessionsAPI and handleDeleteSession response headers
HIGH-7: Path traversal - Add decodeURIComponent, reject '..' in URLs,
  use path.resolve in static file handler
HIGH-8: Rate limiting - Add per-connection WebSocket message rate limit
  (100 msg/sec, disconnect with 1008 if exceeded)
MEDIUM-2: Unbounded WS - Add maxPayload: 1048576 to WebSocketServer
MEDIUM-3: XSS - Add single quote escaping to escapeHtml function
MEDIUM-4: PTY resize - Clamp cols [1,500] and rows [1,200] on
  pty_resize messages
MEDIUM-5: Security headers - Add CSP, X-Frame-Options, and
  X-Content-Type-Options to all HTTP responses
LOW-1: Reconnection - Replace fixed 3s reconnect delay with exponential
  backoff (1s initial, 30s max, reset on connect)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* security: apply 5 remaining security fixes

CRITICAL-2: WebSocket authentication token (crypto.randomUUID, verified on connect)
CRITICAL-4: PTY input audit logging to temp file
CRITICAL-5: ACP JSON-RPC method allowlist for passthrough mode
HIGH-3: Redact secrets from replay events before sending to late-joining clients
HIGH-5: Filter sensitive environment variables from PTY spawn

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: update tests to use WebSocket auth token

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address P0/P1/P2 security findings from red team audit

- F-01: Add session token auth check on all /api/* routes
- F-03: Fix redactSecrets regex that preserved matched secrets via \$& backreference
- F-04: Bind HTTP server to 127.0.0.1 instead of 0.0.0.0
- F-05: Add WebSocket Origin header validation in verifyClient
- F-07: Expand env var sensitive patterns filter (auth, bearer, db, cert, signing)
- F-08: Log and limit non-JSON raw PTY input in catch blocks
- F-09: Replace all execSync template-literal devtunnel calls with execFileSync
- F-10: Add max 5 WebSocket connections cap
- F-14/F-15: Fix XSS in dashboard error and permission dialog (escapeHtml, remove inline onclick)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: apply 5 security hardening fixes to remote control bridge

1. Enhanced redaction: expand redactSecrets with OpenAI sk-, GitHub ghp_,
   AWS AKIA, Azure connection strings, database URLs, Bearer tokens
2. Audit log location: move from tmpdir to ~/.cli-tunnel/audit/ with JSONL format
3. Replay opt-in: add enableReplay config option, disable replay by default
4. CSP tighten: restrict connect-src to ws://localhost:* wss://*.devtunnels.ms
5. XSS: verified app.js already escapes err.message and msg.id via escapeHtml()

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* security: implement F-02 ticket auth, F-07 env allowlist, F-18 session expiry

F-02: Token out of URL — one-time ticket-based WebSocket auth.
POST /api/auth/ticket exchanges session token for single-use 60s ticket.
verifyClient accepts both ticket (preferred) and token (backward compat).
PWA connect() uses ticket flow with token fallback.

F-07: Env var allowlist — replace denylist pattern filter with explicit
SAFE_ENV_VARS allowlist for PTY environment.

F-13: No code change needed — SRI hashes on CDN scripts already protect
against CDN compromise.

F-18: Session TTL — 24-hour session expiry enforced in verifyClient and
HTTP handler. Session expiry time printed on startup. Added getSessionExpiry()
to RemoteBridge.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: apply P0+P1 security audit findings for remote-control

P0 fixes:
- #2: EISDIR crash — directory check before createReadStream in static handler
- #8: Unhandled createReadStream errors — add stream error handler
- #18: decodeURIComponent crash on malformed URLs — wrap in try/catch

P1 fixes:
- #10: Session TTL bypass — enforce periodically via setInterval
- #14: Audit log writes cleartext — apply redactSecrets to pty_input data
- #16: Dashboard XSS — replace inline onclick with data attributes + addEventListener
- #17: Add Referrer-Policy: no-referrer and Cache-Control: no-store headers
- #21: Audit log write stream error handler
- #24: Audit dir permissions 0o700
- #28: Origin substring bypass — use URL parsing instead of .includes()
- #30: Ticket GC — clean expired tickets every 30s

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: add EISDIR, NaN, stream error, non-JSON PTY, and decodeURI guards

- #2: Add statSync directory check before createReadStream in start.ts
- #7: Add Number.isFinite guard for pty_resize cols/rows in start.ts
- #8: Add error handler on createReadStream stream in start.ts
- #3: Remove raw non-JSON PTY write in bridge.ts and start.ts catch blocks
- #18: Wrap decodeURIComponent in try/catch in start.ts static handler

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* security: backport 10 security fixes from cli-tunnel

Backported from cli-tunnel v1.2.0-beta.12 security audits:

1. HSTS header added to all responses
2. Session TTL reduced from 24h to 4h
3. Secret redaction extended (JWT, Slack, npm, PEM patterns)
4. Per-IP WS connection limit (2 per IP)
5. WS ping/pong heartbeat (30s interval)
6. Env var filtering: allowlist → blocklist approach
7. CSP: removed unsafe-inline from script-src
8. escapeHtml: added double-quote escaping (XSS fix)
9. Origin validation moved before ticket check
10. HTTP rate limiting (30 req/min per IP, 429 response)

All 18 remote-control tests passing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: enhanced redaction + heartbeat backport from cli-tunnel

Cherry-picked from cli-tunnel PR #15:
- stripInvisible(): ANSI escape bypass protection
- NFKC normalization before redaction
- 6 new token patterns (GitLab, Vault, GitHub PAT, Databricks, HuggingFace, credential-in-URL)
- Heartbeat interval 30s → 120s (prevents phone disconnect in background)

18/18 remote-control tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add 74 comprehensive tests for remote control feature

Coverage added for:
- Security: auth (token/ticket), rate limiting (HTTP 429), connection limits (5 global, 2/IP), origin validation, ACP method allowlist, session expiry
- Secret redaction: 18 patterns tested via replay (OpenAI, GitHub, AWS, Azure, DB URLs, JWT, Slack, npm, GitLab, Vault, Databricks, HuggingFace, PEM, credentials-in-URL, generic key=value)
- ANSI/invisible char stripping: ESC CSI, C1 codes, zero-width chars, braille blank
- Client commands: prompt, direct, slash command, permission response callbacks
- Static file serving: security headers, path traversal, MIME types
- Tunnel utilities: getMachineId, getGitInfo, isDevtunnelAvailable
- Protocol edge cases: empty input, ping, permission_response, extra fields
- Error handling: double start, stopped bridge ops, sendError, sendToolCall, sendPermissionRequest, sendUsage, updateAgentStatus
- Replay buffer: enable/disable toggle, 2000-event cap

Total: 92 tests (18 existing + 74 new), all passing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: 404 on phone — strip query params from static file URL + PTY-only client

- Static handler now uses URL.pathname instead of raw req.url, so
  ?token=xxx no longer becomes part of the file path (caused 404)
- Fixed in both start.ts and rc.ts static handlers
- Enabled replay buffer (enableReplay: true) so late-joining phones
  get terminal history instead of a blank screen
- Removed ACP protocol code from client — PTY terminal only
- Stripped ~270 lines of unused ACP code (sendRequest, initializeACP,
  streaming, tool calls, permission dialog, formatText)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: bradygaster <bradyg@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Brady Gaster <41929050+bradygaster@users.noreply.github.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.

Add Ralph — built-in work monitor squad member

2 participants