Skip to content

feat: Ralph idle-watch mode — auto-poll for new work after board clears#28

Merged
bradygaster merged 1 commit intobradygaster:mainfrom
spboyer:squad/ralph-idle-watch
Feb 13, 2026
Merged

feat: Ralph idle-watch mode — auto-poll for new work after board clears#28
bradygaster merged 1 commit intobradygaster:mainfrom
spboyer:squad/ralph-idle-watch

Conversation

@spboyer
Copy link
Copy Markdown
Contributor

@spboyer spboyer commented Feb 12, 2026

Summary

When Ralph is active in a Copilot CLI session and clears the board, he previously entered a full idle state — meaning the user had to manually say "Ralph, go" again when new work appeared. This PR adds idle-watch mode: when the board clears, Ralph automatically re-checks for new work on a configurable timer instead of fully stopping.

Problem

Ralph's work loop was one-shot: scan → work → scan → board clear → stop. If new issues were filed while Ralph was idle, nobody noticed until the user manually triggered another scan. This created a gap between "the board is clear" and "new work arrives" where the team sat idle unnecessarily.

Solution: Idle-Watch Mode

When Ralph clears the board, he now enters idle-watch instead of full idle:

  1. Board clears → Ralph reports: "📋 Board is clear. Ralph is watching — next check in 10 minutes."
  2. After the poll interval, Ralph re-scans GitHub
  3. New work found → resumes the active work loop automatically
  4. Still no work → waits and checks again
  5. Repeats until the user explicitly says "Ralph, idle" or the session ends

Configurable Interval

The default poll interval is 10 minutes, configurable via natural language:

Command Effect
Ralph, check every 5 minutes Sets interval to 5 min
Ralph, check every 30 minutes Sets interval to 30 min
Ralph, poll every 15 minutes Sets interval to 15 min

The interval can be set at any time — during active mode, idle-watch, or before activation.

Idle-Watch vs. Full Idle

Mode Behavior How to enter
Idle-watch Polls for new work every N minutes Automatic when board clears
Full idle Completely stopped, no polling Explicit "Ralph, idle" or "stop"

Changes

.github/agents/squad.agent.md

  • Updated critical behavior block to describe idle-watch semantics
  • Added Ralph, check every N minutes to intent router and triggers table
  • Added new Idle-Watch Mode section with full behavioral spec
  • Updated Ralph State to include poll_interval and three-state model (active/idle/watching)
  • Updated Integration with Follow-Up Work pipeline to show idle-watch steps 6-10
  • Updated "No work found" action from full idle to idle-watch entry

docs/features/ralph.md

  • Updated in-session flow to show idle-watch as step 5
  • Added Ralph, check every N minutes to Talking to Ralph table
  • Added complete Idle-Watch Mode section with how-it-works, configuration, and comparison table
  • Updated Notes section to reflect three-state model and poll interval in session state

Testing

This is a behavioral spec change (agent markdown) — no code logic to unit test. The changes are validated by reviewing the spec for consistency across all Ralph references in both files.

When Ralph clears the board, instead of fully stopping he now enters
idle-watch mode: automatically re-checking for new work every N minutes
(default: 10). The polling interval is configurable via natural language
(e.g., 'Ralph, check every 30 minutes').

- Add idle-watch polling behavior to Ralph's state machine
- Add 'Ralph, check every N minutes' trigger command
- Update Ralph state: active/idle/watching + poll_interval
- Distinguish idle-watch (automatic on board clear) from full idle
  (explicit 'Ralph, idle' / 'stop')
- Update docs/features/ralph.md with idle-watch documentation
@bradygaster bradygaster merged commit cc9eb66 into bradygaster:main Feb 13, 2026
tamirdresher added a commit to tamirdresher/squad that referenced this pull request Mar 2, 2026
P0 fixes:
- #2: EISDIR crash — directory check before createReadStream in static handler
- bradygaster#8: Unhandled createReadStream errors — add stream error handler
- bradygaster#18: decodeURIComponent crash on malformed URLs — wrap in try/catch

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

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>
williamhallatt added a commit to williamhallatt/squad that referenced this pull request Mar 16, 2026
…er#341)

Add 39 tests covering 5 SDK features that were previously untested:

- bradygaster#27 Manual Ceremonies: trigger types, schedule, hooks, defineSquad composition
- bradygaster#28 Ceremony Cooldown: schedule-gated cadence, multi-ceremony schedules
- bradygaster#36 Human Team Members: agent status lifecycle (active/inactive/retired)
- bradygaster#49 Constraint Budget: ask_user rate limiter, file-write path guards,
  shell command restrictions, combined constraints, defineHooks builder
- bradygaster#50 Multi-Agent Artifact: per-artifact lockout, handoff, HookPipeline integration

All tests exercise real SDK implementations (builders, HookPipeline,
ReviewerLockoutHook) — no stubs.
bradygaster pushed a commit that referenced this pull request Mar 16, 2026
… (#425)

SDK feature parity batch 2: 64 tests for worktree awareness, config resolution, routing, hook lifecycle, and casting overflow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
joniba pushed a commit to joniba/squad that referenced this pull request Mar 26, 2026
This comprehensive research document covers:
- .NET Aspire architecture and why it's transformational for AI-assisted development
- MCP integration patterns for system-wide agent visibility
- Port isolation problem (blocker for parallel worktrees) and Tamir Dresher's solution
- Four strategies for port isolation with trade-off analysis
- Distributed tracing (OpenTelemetry) and health checks patterns
- Squad infrastructure integration recommendations
- Production readiness assessment for ms-pa
- References to Tamir Dresher's blog posts, code samples, and official documentation

Key finding: Adopt Aspire + MCP for Squad observability, implementing dynamic port allocation
+ MCP proxy pattern. This enables true parallel multi-agent development with system-wide
visibility into distributed tracing, logs, and health checks.

Research validates Issue bradygaster#28 acceptance criteria:
1. Aspire capabilities for AppHost orchestration ✅
2. MCP integration for observability ✅
3. Port isolation solution (3+ strategies documented) ✅
4. Distributed tracing approach (OpenTelemetry built-in) ✅
5. Squad infrastructure integration recommendations ✅

Co-authored-by: Copilot <223556219+Copilot@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.

2 participants