Skip to content

fix(session-bridge): harden the view-bridge watcher, token and local-user defences #6109

Open

Description

@kyle-sexton

Context

Found in the review of #6094 (feat(session-bridge): claude-interactive triage board and plan view). Found while delivering spec container #5835. All items are hardening; none is exploitable beyond the stated limits.

Problem

  1. An orphaned watch.sh after a session crash loops on timeouts and keeps the view-bridge server and its token alive.
  2. The page and the watcher share one token, so page script could call /api/wait or release the lease (denial of service only).
  3. Sec-Fetch-Site is a browser-only defence; it does not stop other local OS users.
  4. json.dumps(ensure_ascii=False) passes U+2028 raw into the watcher line (cosmetic).

Suggested fix

  1. Add a parent-liveness check to watch.sh so it exits when the owning session is gone, which lets the server and token expire.
  2. Use separate tokens (or scopes) for the page and the watcher.
  3. Document the local-user limit, or bind the listener with a per-user credential.
  4. Escape U+2028 and U+2029 in the watcher line.

Related: #5835

Activity

  1. kyle-sexton commented on Oct 13, 2026

    @kyle-sexton
    MemberAuthor

    Owner decision (2026-10-13): approved the recommendation.

    • How does an orphaned watcher stop? → It exits after N consecutive empty waits, with its own exit message telling a live session to re-arm. Choose N well above a normal reading pause (each wait defaults to 90 s). No parent-pid check: on Windows the Bash shell's parent pid is 1.
      Basis: origin/main lib/session-bridge/watch.sh:18-26, :91-100; README.md:132 (server idle exit)
    • Separate tokens for the page and the watcher? → Yes. A watcher-only token gates /api/wait and the lease release; it lives in the 0600 session env file and is never served by /api/token. The page token keeps POST /api/action only.
      Basis: session_bridge.py:439-442; view_bridge.py:207-213
    • Other local OS users? → Document the limit for single-user workstations instead of building a URL-fragment credential. Fix README line 132 either way: /api/token checks only headers that a non-browser client such as curl can send, so any local process can fetch the page token.
      Basis: view_bridge.py:207-213; session_bridge.py:420-426; https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site
    • Escape U+2028 and U+2029? → Yes, after json.dumps at session_bridge.py send() (around :393-398) and ChannelServer.send (around :1319-1321). Do not switch to ensure_ascii=True.
      Basis: session_bridge.py; RFC 8259 section 7

    Next: an agent lane, via admission, ships all four and the README fix in one PR; it is security work, so a human merges it.

    This was generated by AI.

  2. kyle-sexton commented on Oct 13, 2026

    @kyle-sexton
    MemberAuthor

    Owner batch-ratified this admission in an attended session on 2026-10-12: work-class: scoped. Rule: class C1-C3, no trust surface, the independent check confirmed the recorded decision, and no owner step remains.

  3. added
    work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    and removed
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    on Oct 13, 2026
  4. added theissue type on Oct 15, 2026
  5. added
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    on Oct 15, 2026
  6. kyle-sexton commented on Oct 18, 2026

    @kyle-sexton
    MemberAuthor

    Agent Brief

    Type: Bug
    Summary: Harden the view-bridge watcher, its token and its documented local-user limit.

    Current behavior:
    An orphaned watcher after a session crash loops on wait timeouts and keeps the server and token alive. The page and the watcher share one token, so page script can call the wait endpoint or release the lease. The README overstates what the token endpoint's header checks stop. U+2028 and U+2029 pass raw into the watcher line.

    Desired behavior:

    • The watcher exits after N consecutive empty waits (N well above a normal reading pause; each wait defaults to 90 s), printing an exit message that tells a live session to re-arm. No parent-pid check.
    • A watcher-only token gates the wait endpoint and the lease release. It lives in the 0600 session env file and is never served by the token endpoint. The page token keeps the action endpoint only.
    • The README documents that the bridge assumes a single-user workstation, and states that the token endpoint's header checks do not stop a non-browser local client.
    • Output lines escape U+2028 and U+2029 after JSON encoding in both send paths, without switching to ASCII-only encoding.

    Key interfaces:

    • Watcher exit condition and message.
    • Token scopes: page token (action) and watcher token (wait, lease release).
    • Session env file: new watcher token entry.

    Acceptance criteria:

    • A test shows the watcher exits after N empty waits with the re-arm message.
    • Tests show the page token is refused on the wait and lease-release endpoints and the watcher token is accepted; the token endpoint never returns the watcher token.
    • A test shows a message holding U+2028 and U+2029 is emitted with both escaped.
    • scripts/run-test-file.sh passes on each session-bridge test file touched.
    • The README states the single-user limit and the header-check limit.

    Out of scope:


    This was generated by AI during backlog shaping. Held, not admitted: this brief is drafted and checked, but the issue lane would stop on it today. It is admitted, by adding agent-ready, once the lane can resync shared library copies (scripts/sync-shared-copies.sh; a separate fix is approved). Held under the owner's 2026-10-18 lane-narrowing course correction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.priority: lowNice-to-have, cosmetic, or speculative; opportunistic.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Type

    Fields

    No fields configured for Task.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions