Skip to content

feat: add daemon websocket subscriptions#290

Merged
Yeachan-Heo merged 1 commit into
devfrom
issue-289-gjc-workflow-gate-listener
Jul 15, 2026
Merged

feat: add daemon websocket subscriptions#290
Yeachan-Heo merged 1 commit into
devfrom
issue-289-gjc-workflow-gate-listener

Conversation

@Yeachan-Heo

Copy link
Copy Markdown
Owner

Summary

  • add first-class clawhip subscribe validate|list|status|start|stop
  • run config-backed Rustls WebSocket subscriptions inside the daemon with deterministic bounded reconnect and graceful cancellation
  • filter and allowlist-project local frames into env-cleared argv adapters, then enqueue restricted IncomingEvent values through the existing Router
  • protect lifecycle controls with loopback peer, Host/Origin, and fixed non-simple control-header checks
  • expose only redacted stable lifecycle snapshots and document guarded GJC workflow_gate/question examples

Security boundaries

  • endpoint values remain daemon-environment-only and never appear in status, health, logs, or emitted events
  • raw frames, unprojected fields, stderr, rejected output, and inherited daemon secrets never cross the adapter/event boundary
  • adapter delivery controls and reserved metadata are rejected recursively
  • unmatched subscription events cannot fall back to the default channel

Verification

  • cargo fmt --all -- --check
  • cargo clippy -- -D warnings
  • focused subscription/config/daemon/client/router tests
  • cargo test (705 unit tests plus all integration suites)
  • cargo build --release
  • scripts/internal-pr-format-gate.sh
  • hermetic release-daemon list/status replay with endpoint/token absence checks

Closes #289

[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Terminal implementation evidence:

  • exact head: 42fe88d554a37176cd031a35f991d662572faa3d
  • ancestry: origin/dev is an ancestor; origin/dev..HEAD contains exactly one issue commit
  • focused: subscription 9 passed; documented config 3 passed; daemon 47 passed; client/router coverage passed
  • full: cargo test passed with 705 unit tests plus all integration suites
  • quality: cargo fmt --all -- --check, cargo clippy -- -D warnings, release build, and scripts/internal-pr-format-gate.sh passed
  • live QA: hermetic release daemon reported one configured disabled subscription; list/status schemas were correct and endpoint/token material was absent
  • independent frozen-head review: architecture/product/code CLEAR + APPROVE; executor QA/e2e/red-team passed with no blockers

Merge remains guarded on exact-head hostile review and green CI.

[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Fresh exact-head hostile GJC review completed for 42fe88d554a37176cd031a35f991d662572faa3d.

Verdict: PASS (contractStrength=strong, cleanPassEligible=true, zero findings). Artifact validation passed for the hermetic daemon API/CLI report and fresh security/lifecycle test report. Independent architecture/product/code review is CLEAR with APPROVE; executor QA, e2e, and red-team statuses are passed with no blockers.

CI is still running; merge remains blocked until every required exact-head check is green.

[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo
Yeachan-Heo merged commit 516c471 into dev Jul 15, 2026
12 checks passed
@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

MERGE_READY terminal receipt:

  • exact reviewed head: 42fe88d554a37176cd031a35f991d662572faa3d
  • exact-head GJC hostile review: PASS, strong contract, clean-pass eligible, zero findings
  • GitHub checks: all required exact-head checks completed successfully; PR mergeable state was clean
  • merge: squash-merged to dev as 516c471e06cb3cae67fadb2ceee6099be1c1eae8
  • verification: focused security/lifecycle/routing suites, full cargo test (705 unit tests plus integrations), clippy, release build, PR gate, and hermetic daemon API/CLI redaction replay passed

No inherited dev history was repushed as issue work; the branch contained exactly one issue commit above origin/dev.

[repo owner's gaebal-gajae (clawdbot) 🦞]

@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: 42fe88d554

ℹ️ 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 thread src/daemon.rs
Comment on lines +488 to +490
async fn list_subscriptions(
_peer: LoopbackLanePeer,
State(state): State<AppState>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply the Host guard to subscription reads

When a DNS-rebinding page resolves an attacker-controlled hostname to 127.0.0.1, the peer address is loopback but the Host header is not; these GET handlers only require LoopbackLanePeer, so /api/subscriptions and /api/subscriptions/{name} can expose lifecycle snapshots despite the new Host-check helper and README saying subscription operations are protected by numeric loopback Host checks. Add the same Host validation used by the control extractor for these read endpoints, even if they do not require the mutation header.

Useful? React with 👍 / 👎.

Comment thread src/daemon.rs
Comment on lines +1358 to +1361
host.parse::<std::net::IpAddr>().is_ok_and(|ip| {
ip.is_loopback()
|| matches!(ip, std::net::IpAddr::V6(ip) if ip.to_ipv4().is_some_and(|ip| ip.is_loopback()))
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make loopback host rules consistent

If a user configures daemon.base_url as http://localhost:25294, the client accepts it as a private loopback daemon URL, but request_host_is_loopback passes localhost into this helper and it returns false because only numeric IPs parse here; subscribe start/stop then fail with local_control_rejected even though other private client calls allow the same base URL. Either reject localhost client-side with a clear error for subscription control or accept it in this Host check.

Useful? React with 👍 / 👎.

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.

1 participant