Skip to content

fix(runtime): harden browser sessions, fleet SSH and tool trust boundaries - #6681

Open
Hmbown wants to merge 2 commits into
mainfrom
fix/runtime-surface-hardening
Open

Hmbown wants to merge 2 commits into
mainfrom
fix/runtime-surface-hardening

Conversation

@Hmbown

@Hmbown Hmbown commented Sep 27, 2026

Copy link
Copy Markdown
Owner

Tightens several runtime and tool trust boundaries:

  • Fleet SSH hosts are checked against the configured known-hosts file with strict host-key checking; unsupported fingerprint settings fail explicitly.
  • An agent can resume only from an agent it controls.
  • Recording a task gate result requires approval; plain polling stays automatic.
  • A plugin tool can no longer take a native tool's name; the collision is a load error.
  • Bridge action tokens come from the platform CSPRNG.
  • The runtime web client authenticates API calls with a request proof held outside cookies, and event streams use single-use tickets.
  • The release-asset verifier sends credentials only to the HTTPS GitHub API origin and refuses non-HTTPS redirects.

Local: focused codewhale-tui tests 35 passed; 0 failed (and 15 passed with real loopback sockets); bridge-core 17 passed; runtime web client + release-assets 53 passed; 0 failed.

🤖 Generated with Claude Code

Several runtime surfaces relied on implicit trust or static policy where
request-specific authority was needed. Require browser request proofs and
single-use stream tickets, descendant ownership for transcript continuation,
and approval for task gate recording. Enforce strict fleet known-host checks
and reject unsupported fingerprint settings. Refuse discovered plugin name
collisions and keep explicit overrides bound to their configured names.
Use cryptographic bridge action tokens and scope release metadata credentials
to the HTTPS GitHub API origin, refusing non-HTTPS metadata redirects.

Validation (Cargo used CARGO_BUILD_JOBS=4 and CARGO_NET_OFFLINE=true):
- cargo test -p codewhale-tui --lib -- runtime_surface_hardening_
  fleet_host_ssh_command fleet_host_ssh_config tools::plugin::tests
  tools::tasks::tests::task_shell_wait_keeps runtime_api::web::tests
  runtime_api::auth::tests --test-threads=1 --nocapture
  test result: ok. 35 passed; 0 failed; 0 ignored; 0 measured; 13439 filtered out; finished in 0.09s
- Final focused plugin diagnostics and browser bootstrap fixture check:
  cargo test -p codewhale-tui --lib --
  runtime_surface_hardening_plugin_collisions_preserve_registered_tools
  web_bootstrap_sets_strict_cookie_once_and_preserves_v1_auth
  --test-threads=1 --nocapture
  test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 13472 filtered out; finished in 0.05s
  Browser bootstrap: needs socket run; its fixture explicitly skipped because
  the sandbox refuses loopback binding. Pure request-proof and ticket checks
  are included in the 35-test run above.
- npm test --prefix integrations/bridge-core: 17 tests, 17 pass, 0 fail.
- node --test crates/tui/tests/runtime_web_client.test.mjs
  npm/codewhale/test/release-assets.test.js: 53 tests, 53 pass, 0 fail.
- The three focused JavaScript regressions each failed when their respective
  production protection was temporarily removed, then the fixes were restored.
- cargo fmt --all; sh scripts/sync-changelog.sh; git diff --check.
- OpenSSH offline configuration inspection confirmed strict host-key checking
  and the configured known-hosts file, including a path with spaces.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 27, 2026 07:54
@Hmbown Hmbown added this to the v0.10.1 milestone Sep 27, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-27T08:00:26.819646Z 3a9e4d8 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: 3a9e4d8bc2

ℹ️ 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".

"Cannot load plugin tool '{}': name is already registered; use an explicit tool override",
crate::safe_label::SafeLabel::identifier(tool.name())
);
continue;

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 Propagate plugin-name collisions from loading

When a plugin declares a built-in or duplicate tool name, this branch only emits a tracing event and continues, while load_plugins returns () and configure_plugin_tools consequently reports successful initialization with that plugin silently absent. This makes a self-contained configuration error depend on whether the user inspects tracing output instead of failing startup/load as promised; return an error through the initialization path when a collision is detected.

AGENTS.md reference: AGENTS.md:L79-L81

Useful? React with 👍 / 👎.

- Recover browser request proofs on cookie-authenticated same-origin or direct
  page loads; retain origin-scoped sessionStorage and document recovery limits.
  Keep bounded independent stream tickets for tabs connecting together.
- Place IgnoreUnknown before KnownHostsCommand so OpenSSH 7.x clients retain
  strict known-host checks without rejecting the newer option.
- Correct the SSH authentication example and protocol field documentation;
  document the host_key_fingerprint migration to known_hosts in the changelog.
- Retry transient stream-ticket failures with capped backoff, stop on HTTP
  401/403, and discard superseded or stopped connection attempts.

Validation (all Cargo commands used CARGO_BUILD_JOBS=4 CARGO_NET_OFFLINE=true):
- cargo test -p codewhale-tui --lib -- runtime_surface_review_
  runtime_surface_hardening_ssh_requires_known_host_verification
  fleet_host_ssh_command fleet_host_ssh_config runtime_api::web::tests
  runtime_api::auth::tests --test-threads=1
  test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 13461 filtered out; finished in 0.01s
- node --test crates/tui/tests/runtime_web_client.test.mjs
  tests 42; pass 42; fail 0.
- The proof recovery, independent-ticket and SSH compatibility checks fail
  when their respective production changes are removed. The corrected docs
  regression run against the old example reports:
  test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 13476 filtered out; finished in 0.06s
  It fails with the expected unsupported host_key_fingerprint error.
- Each focused JavaScript regression fails without its corresponding change:
  page proof recovery, transient ticket retry, and pending-stream ownership.
  Each reports tests 1; pass 0; fail 1. All changes were restored afterward.
- cargo fmt --all; sh scripts/sync-changelog.sh;
  sh scripts/sync-changelog.sh --check; git diff --check: passed.
- OpenSSH 10.0p2 ssh -G checks preserve strict checking and a known-hosts path
  containing spaces, and confirm IgnoreUnknown handling. No native 7.x run.
- web_bootstrap_sets_strict_cookie_once_and_preserves_v1_auth:
  needs socket run; the sandbox cannot bind the loopback listener.
- cargo clippy -p codewhale-tui --all-targets -- -D warnings (run once):
  exit 101; could not compile `codewhale-tui` (lib test) due to 19 previous errors.
  All 19 diagnostics are clippy::too_many_arguments. Each reported function
  signature is unchanged from both the starting HEAD and HEAD^; these are
  pre-existing, unrelated signatures. No lint allowances were added.

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

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

Devin Review

Comment thread CHANGELOG.md
Comment on lines +19 to +27
### Security

- Harden local runtime browser sessions, fleet SSH trust, agent continuation
ownership, task gate approval, plugin tool registration, bridge action tokens,
and release metadata credential forwarding. Browser sessions recover across
reloads and new tabs, and stream tickets retry after transient failures.
Fleet SSH known-host checks support OpenSSH 7.x and later. SSH host configs
using `host_key_fingerprint` must migrate to `known_hosts` with verified host
keys; the unsupported fingerprint field now fails at configuration load.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 Changelog entries precede the merge

The repository reserves changelog edits for a batched commit on main. These branch edits can conflict with other pending PRs.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread docs/RUNTIME_API.md
Comment on lines +571 to +575
Web fetches require the session cookie plus an origin-scoped request proof;
streams use a fresh single-use ticket. The initial redirect carries the proof
in a fragment, which the client removes and saves in origin-scoped
`sessionStorage`. On reload or in a second tab, an authenticated `GET /` also
embeds the proof in a meta tag when `Sec-Fetch-Site` is `same-origin` or `none`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 Bootstrap redirect documentation conflicts

The earlier bootstrap description still says the browser redirects to /; the new flow redirects to a proof-bearing fragment.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +264 to +277
if headers
.get("sec-fetch-site")
.is_some_and(|site| site == "same-origin" || site == "none")
&& web.matches_session_cookie(headers.get(header::COOKIE).and_then(|v| v.to_str().ok()))
&& let Some(proof) = web
.request_proof
.lock()
.unwrap_or_else(|p| p.into_inner())
.as_deref()
{
html = html.replace(
"name=\"codewhale-web-request\" content=\"\"",
&format!("name=\"codewhale-web-request\" content=\"{proof}\""),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟥 Direct navigation exposes the browser API proof

An authenticated direct navigation to / embeds the API proof when Sec-Fetch-Site is none. This makes the proof available to any same-user local browser navigation that can read the returned page.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Hmbown pushed a commit that referenced this pull request Sep 27, 2026
crates/tui/src/runtime_api/web.rs: #6601 (already merged) replaced the
file-local constant_time_eq with the shared
codewhale_core::secret_eq::constant_time_eq; #6681 kept the local helper
and widened secured_asset's body parameter to impl IntoResponse. Took
#6681's secured_asset signature and dropped the local helper, so every
runtime_api surface keeps using the one shared comparison.

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

This branch has not been deployed

No deployments
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