Skip to content

Custom panes built with Chief, and an App Store review flow - #26713

Open
austinywang wants to merge 119 commits into
feat-cmux-nextfrom
feat-cmux-next-custom-panes
Open

austinywang wants to merge 119 commits into
feat-cmux-nextfrom
feat-cmux-next-custom-panes

Conversation

@austinywang

@austinywang austinywang commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #26579

Chief can build a local custom pane, have the person install it through the App Store's native consent sheet, and open it beside the chat. Saving its files reloads the pane. The store adds versioned submissions, a reviewer queue, and approved Community listings. The App Store entry stays behind the default-off Preview Features setting.

Design: custom-panes-DESIGN.md.

What changed

  • Host manifest-v2 web panes at an app-specific origin, with a nonpersistent data store, restrictive CSP, closed network hosts, root-contained background file reads, and main-frame bridge validation. Disable, uninstall, disconnect, policy changes, and missing packages clear an already-open document and revoke its bridge, including cached detached views; re-enabling restores the eligible pane.
  • Route bridge calls through the verified native connection and the daemon's current install, enablement, and grant checks. Chief's bundled custom-pane skill and Workspace Board example use the companion for supervisor reads and normal app actions for the store and pane placement.
  • Add authenticated submissions, separate reviewer decisions, and approved Community downloads. Publication, review decisions, and installed-version replacement require native consent. Reviewers cannot decide their own submissions. Downloads validate bundle hash and package contents before replacing the catalog entry.
  • Preserve exact numeric SemVer ordering beyond JavaScript's safe integer range, accept long valid version identifiers consistently, and serialize registry publication with local catalog reloads. Install the approved Community version even when an older uninstalled bundle is cached, and preserve a newer unsent draft when an earlier send finishes.
  • Fix defaults-guard membership resolution for genuine independent exact-head review receipts. The guard still uses trusted base policy and rejects stale and self reviews.

Changelog

  • Added: Ask Chief to build a custom pane beside your chat, then submit it for App Store review.

Verification

Head fc840535c397e46ea94f8ff51c95c8b48d601266, tree 425d8d30851d47f3e3bc7dba4acd5c896360500c, normally merges base e899518539cd40b12ce14fb95281bddf38635a42. Both parents and incoming changes are preserved; no pushed history was rewritten. The registry/trajectory migration conflict preserves TrajectoryDO v20 in all environments and adds AppRegistryDO v21 only in local/development/staging. Production has neither the registry binding nor its creation migration.

The lifecycle regression was committed before the fix. This focused command executed at regression commit 024b1dd30fcf24156d388f7f79bccd2419066649 (fleet step ca220df482267e603bff8f15) and at this head (step 3213c9dd36fde39bf949f3ef, which also selected the other seven focused suites):

cmux-ci run --class light --script scripts/ci/package-test-lane.sh --ref SHA \
  --arg suite --arg Packages/macOS/CmuxNext --arg AppWebPaneUpdateTests \
  --env CMUX_CI_XCODE_APP=/Applications/Xcode_26.6.app

Before the fix, all seven revocation cases failed with 35 assertions: stale package resources/document and a still-enabled bridge. On this head, all seven pass, as do restoration, initial stale-disabled rejection, crash callbacks, and manifest replacement. This proves actual WebKit behavior, not a source-text assertion.

Observed / Not run

  • Exact-head fleet execution: all eight native suites PASS, 43 named tests including parameterized cases. Suites: AppWebPaneTests, AppWebPaneSchemeHandlerTests, AppWebPaneUpdateTests, AppRegistrySubmissionTests, AppRegistryPageProviderTests, AppOpenTargetTests, AppsPageBridgeTests, RightPanePlacementTests. Long-version downloads, package-root containment, bridge restrictions, and pane placement were exercised.
  • Exact final tree on Freestyle: full web-lint recipe PASS across webviews/web/cmux-tui frontend, 30 registry model/UI tests PASS, four sample-draft tests PASS, 31 backend registry/trajectory tests PASS, and backend type checking PASS. The receipt verifies the full tree before and after execution. Backend tests used the Workers Vitest runner; an earlier plain Bun invocation executed no tests and is not evidence.
  • Exact-head rbx: 189 apps tests PASS; strict Clippy across the five affected packages PASS. Defaults suite: python3 tests/test_defaults_guard.py, 64 PASS. Static prepush gate and actual push hooks PASS. Release compile and defaults-guard rerun PASS.
  • Exact-head tagged fleet build issue-26579-pane-flow-twentytwo, job 725f5f9ebb83dfbd859d8174, PASS with workspace_reset: true; artifact SHA256 87cbd96e2d05127b8158d8e5d98a2d53cb5e72d5343b316ad230ffad8fd61a64. Try the tagged build. Local backend mode; this does not establish shared-service or production behavior.
  • Exact-head fresh-home launch PASS: Home rendered in 6.1 seconds, alive for 30 seconds, no crash or uncaught exception. Screenshot upload was withheld because agent-author privacy metadata is absent; the privacy check was not bypassed.
  • Independent correctness-first review: SHIP on this exact head. Current risk-evidence, merge-freeze, and capture checks pass. The Rust panic-ratchet red matches the base-branch failure: indexing 832 > 831 and string slicing 83 > 82 in the unchanged cmux-tui crate. Classification and evidence.
  • Earlier live Chief verification created and discovered a synthetic Linear-style local pane without restarting; it remained uninstalled, disabled, and without grants. Not run: live installation/public review submission, production registry deployment, macOS 14/15 runtime, or a full Swift-suite pass. Native consent is retained. The original 26579-custom-panes-app-store tag is preserved because it contains active chats and agents.
  • Earlier advisory gallery review inspected Community and My Submissions in light/dark at width 1000, with no blocking visual finding (artifacts). Those images are prior-head evidence; current-head native screenshot publication remains withheld by privacy controls.

Risk

risk:high: daemon socket commands, publication in the person's name, and a registry creation migration for local/development/staging. Preview Features defaults off; native install/update/publication consent and current daemon grants remain required. Review routes return 404 until reviewers are configured.

cmux-platform: This existing registry implementation stays production-unbound pending its move to cmux-platform, a per-person pending-submission cap or write rate limit, and the required migration/promotion validation. This PR does not enable production.

austinywang and others added 8 commits October 27, 2026 21:17
…ue 26579)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…26579)

apps-web-call decides a web pane's cmux.call like a host call (grant,
sandbox, no gesture); apps-web-log puts its console in the app's log
ring; apps.registry.put lands an approved bundle after checking its
hash and package. Only the hosting app connection may send them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AppRegistryDO keeps publishers, versioned submissions, their files and
an audit log; /v1/apps/registry/ serves approved listings and bundles,
person submissions, and an APP_REVIEWERS-gated approve/reject queue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An app with implements["cmux.pane/1"].web opens as its own page: the
files of runtime.web.root under cmux-app://<namespace>/ with the host's
CSP floor (the manifest's policy can only narrow it), a non-persistent
store, no navigation off its origin, and one message handler. cmux.call
goes to the supervisor as apps-web-call; the console goes to the app
log. A local-reload or registry-put of the app reloads the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#26579)

The App Store page lists approved community apps (Get hands the bundle
to the supervisor, then the usual install sheet), submits a local app
under a publisher, shows the person's submissions, and gives reviewers a
queue with the files and Approve/Reject. cmux.registry.* is answered on
the Mac over the person's Cloud session; submit and a decision pass a
native sheet. The registry refuses a manifest whose repository is not
the publisher's, since such a bundle could never install.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…26579)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@austinywang austinywang added the risk:high Risk tier (docs/ci/risk-tiers.md) label Oct 28, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator

Bead cx-s1ufx9 created for this PR by the cmux coordinator.

- A manifest runtime.web.csp may use fetch directives, base-uri,
  form-action and sandbox only. report-uri, report-to and navigate-to could
  name a URL the page's data is sent to; the daemon's manifest rules, the
  registry and the pane host all refuse them.
- A web pane has at most 64 calls in flight per app (app.limit), as an app
  host does; each call runs on its own daemon thread.
- One App Store id pattern everywhere, the manifest schema's (a publisher may
  start with a digit and has at most 39 characters).
- A bundle holds one manifest file: the registry read cmux-app.v2.json first
  and the catalog cmux-app.json, so a reviewer could approve one and the Mac
  install the other. The Mac packer reads the catalog's file.
- Get takes the hash from the app's listing, not from the bundle it checks.
- Registry puts run one at a time; a put fails when another copy shadows it.
- The submit body is read in chunks and dropped past its limit.
- The pane's root must resolve inside the package; the reload task ends with
  its view; console lines are capped over the pane's life too.
- Tests: the navigation refusal is awaited, not timed; rustfmt on the
  daemon and Chief tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@austinywang

Copy link
Copy Markdown
Contributor Author

Correctness review (subagent) at ef49254: FIX, resolved in b7a4ade

Blocking, all fixed

  1. A manifest runtime.web.csp could add report-uri/report-to, which sends violation reports (with the document and blocked URIs) to any host: a way out of the sandbox. The CSP is now limited to fetch directives, base-uri, form-action and sandbox, refused in three places (each with a test):
    • the daemon's manifest rules (csp.directive)
    • the registry's submit check (registry.csp)
    • the pane host (AppWebPane returns nil)
  2. No in-flight cap on web pane calls: each call runs on its own daemon thread. A pane now has at most 64 calls in flight per app (app.limit, retryable), the same as an app host. Covered by a_web_pane_has_at_most_64_calls_in_flight.
  3. App-id patterns differed between the daemon/Mac and the schema/backend, so an approved acme/2048 could never be installed. The schema's pattern is now used everywhere: the daemon registry_app, the Mac Get and the Mac packer. Covered by tests.
  4. The registry read cmux-app.v2.json first while the catalog reads cmux-app.json, so with both files a reviewer could approve one manifest and the Mac install the other.
    • Both the daemon and the backend now refuse a bundle with both names.
    • The Mac packer reads the catalog's file.

Non-blocking

  • Fixed:
    • Get now takes the hash from the app's listing and checks the downloaded bundle against it.
    • Registry puts run one at a time, and a put fails when another copy of the id shadows it.
    • The submit body is read in chunks and dropped past its limit.
    • The pane's root must resolve inside the package.
    • The reload task ends with its view.
    • Console lines are also capped over the pane's whole life.
    • The off-origin navigation test now waits on the refusal instead of a timer.
    • The ../ fetch test's comment now says what it actually covers.
  • Kept as known MVP limits, listed in the design doc:
    • publisher names are first come, with no GitHub ownership proof
    • no rate limit on submit
    • rejected files are retained
    • the sheet names the app from the page's own params (the page is first-party)

austinywang and others added 2 commits October 28, 2026 09:57
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Swift Testing refuses a nested #require expansion; the manifests are
unwrapped first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#26579)

The one-manifest check compared exact names, but a Mac disk ignores case:
cmux-app.v2.json (reviewed) plus Cmux-App.json would install the second
through catalog::manifest_file. The registry and the daemon now refuse a
second manifest in any case or one not named in lowercase, and the daemon
refuses paths that differ only in case, as the registry does.

A put whose id another source shadows is now refused before anything is
written, and a web pane reloads on return to its window when its files
changed while it was away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WKWebView exposes RTCPeerConnection, and ICE reaches any STUN or TURN host
that neither the content policy nor the egress gate sees: a sandboxed pane
could send data out. The document-start script removes the WebRTC
constructors, unrecoverably, before the page's first script.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rame (#26579)

A same-origin about:blank child frame had its own RTCPeerConnection, since
the removal script ran in the main frame only. Turn peer connections and DNS
prefetch off in the pane's WebKit preferences (private keys, set only when
WebKit answers their setter, as WebKitEngine does), and run the removal
script in every frame. The test now checks two child frames.

Crash-allow-OK: claude-subagent review round 4 of #26713 (recommended the responds(to:)-guarded peerConnectionEnabled preference)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@austinywang

Copy link
Copy Markdown
Contributor Author

Fresh-home launch of fc840535c397 on a fleet capture Mac (empty HOME, CFFIXED_USER_HOME, TMPDIR and defaults): PASS

Verdict PASS
Build fleet job 725f5f9ebb83dfbd859d8174 of fc840535c397
Validator scripts/ci/fresh_home_launch_smoke.py from feat-cmux-next at 35bf9b2ff4bc
Home rendered 6.1s after launch
Up 30s
first_window_frame_committed 4630 ms
Crash reports / uncaught exceptions 0 / 0

Screenshot withheld: the pr-media privacy check or upload refused it.

cmux-ci fresh-home --job 725f5f9ebb83dfbd859d8174 --pr N (commit status cmux-ci/fresh-home on fc840535c397)

@austinywang

Copy link
Copy Markdown
Contributor Author

Verification completed on fc840535c397e46ea94f8ff51c95c8b48d601266 after the independent SHIP review:

  • Fleet step 3213c9dd36fde39bf949f3ef: all eight focused native suites PASS (43 named tests, including seven pane-revocation cases, restoration, initial stale-disabled rejection, long-version downloads, sandbox/bridge behavior, and placement). The same lifecycle suite actually failed before the fix at 024b1dd30fcf24156d388f7f79bccd2419066649, step ca220df482267e603bff8f15.
  • rbx: two Chief custom-pane skill integration tests and 13 prompt rendering tests PASS, in addition to the 189 apps tests and strict Clippy already reported. The standalone prompt crate has no committed lockfile; its first --locked invocation ran no tests, and the offline retry executed all 13.
  • Fresh-home startup PASS on exact-head build 725f5f9ebb83dfbd859d8174; tagged build published and verified. Local backend. Screenshot publication was withheld by the privacy check, which was preserved.

The description now records the final source, regression commands, base-red classification, and limits: no live public registry publication, macOS 14/15 runtime, or full Swift-suite pass is claimed. ready-to-land is set for the independently reviewed head. Autoland's shared report is still working from its earlier batch; no additional push or history rewrite was made.

@austinywang

austinywang commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor Author

The current autoland report (2026-10-31 23:55 UTC) still classifies this head's Clippy ratchet failure as its own. The latest completed base run at 5eb0539d10fe52513d2888c8025958af38fb8890 and this head's job report the same two violations: cmux-tui indexing_slicing 832 > baseline 831 and string_slice 83 > baseline 82.

The full repository classifier was executed against GitHub's completed jobs and exited 0:

python3 scripts/ci/base_red_excuse.py --repo manaflow-ai/cmux \
  --base feat-cmux-next --sha fc840535c397e46ea94f8ff51c95c8b48d601266 \
  --check 'clippy (changed cmux-tui packages)'

It reports: excused 'clippy (changed cmux-tui packages)': feat-cmux-next run 41254156587 at 5eb0539d10 ... fails the same way and names both ratchet violations above. The independent reviewer also compared all 42 diagnostic rows and found no PR-caused defect (audit).

Head remains fc840535c397e46ea94f8ff51c95c8b48d601266, independently reviewed, verified, and conflict-free. The remaining blocker is reconciling autoland's classification with its base-red evidence.

@austinywang

Copy link
Copy Markdown
Contributor Author

Review (fc840_correctness, inherited-red audit): SHIP at fc84053

The independent correctness reviewer rechecked autoland's Rust classification against the latest base failure and found no PR-caused ratchet defect. All 42 printed failure rows/locations match; the repository's actual ratchet_unmatched(head_log, base_log) returns [].

The entire cmux-tui crate tree is identical between this head and merged base e899518 (tree 3ed1b34505d579d31b67ab149c5b6b7ece0fcfb4). Ratchet baseline, implementation, wrapper, lockfile, and toolchain match across head, merged base, the base-run commit 5eb0539, and fetched base tip 701a03f. Differing wrapper package lists describe selected validation scope, not additional failures. The original exact-head SHIP stands.

@lawrencecchen

Copy link
Copy Markdown
Contributor

Review (claude-review-subagent-cw): SHIP at 49dc3ba

Scope: the merge commit 49dc3ba (parents 7c00e3b and feat dd61ce5). I read git show --remerge-diff, the PR's own diff, feat's diff, and the net diff over feat. The PR's own diff over its merge base is the same as at fc84053, which got SHIP before. 7c00e3b is a clean merge (no remerge diff). I did not compile or run tests. CI on this head is pending.

Merge resolution

  • local_reload.rs:73-125. One function reload_local_apps_from stays. The order is catalog_updates, then PrintsBefore::take, then the catalog read, then before.of(...). This keeps both intents. Feat's fix(apps): local apps watch never loses a save (Linux idle CPU loop) #29748 fix stays: fingerprints are taken before the read. The PR's guarantee stays: a registry update cannot interleave with a reload read and commit. The PR's old local_prints (fingerprints after the read) is gone. No caller remains (git grep).
  • The lock is held when self.inner is locked (line 93) and is released before emit (line 122). This is the documented order (supervisor.rs:243-245: catalog_updates before inner, release before emit). registry_put uses the same order (registry.rs:157, 238, 246). Only these two sites write inner.catalog. No path locks inner and then catalog_updates. Thus no deadlock.
  • Startup (supervisor.rs:257-261) takes PrintsBefore and reads the catalog before the supervisor exists. No registry put can run then, so no lock is necessary there.
  • supervisor_web_tests.rs:381 now calls reload_local_apps_from. The lock is taken before the test's load closure, so the test's try_lock sees WouldBlock and drives the "reload first, put second" order. The oracle (both versions stay in the catalog) is unchanged. Feat's supervisor_local_reload_tests.rs calls the same function. Its load closures write only under apps/local and take no lock.
  • watch_local (local_reload.rs:379) and feat's is_change (local_reload.rs:397) are both kept. arm calls watch_local (line 300). next_burst calls is_change (line 371).
  • Joined test module (local_reload.rs:410-529). use super::* brings in the parent's private imports (Path, PathBuf, channel, EventKind, AccessKind, AccessMode, json, catalog). The explicit notify::event::{DataChange, Event, Flag, ModifyKind} and Duration are all used. getrandom is a normal dependency of cmux-tui-core (Cargo.toml:51).
  • Other files that both sides changed (PageDescriptor.swift, CmuxNextPages Localizable.xcstrings, crash-safety-baseline.json, supervisor.rs, supervisor_tests.rs) merged cleanly. The two JSON files parse. Every catalog::Sources literal has the PR's registry field.

Blocking time

  • The reload now fingerprints the watched folders while it holds catalog_updates. The PR's pre-merge version also did this (local_prints ran under the lock), so the cost is the same. The work is bounded (MAX_FILES 2000, MAX_HASHED_BYTES 4 MiB per package). It runs on the apps-local-reload thread. The only waiter is apps.registry.put on its socket connection, which does filesystem work under the same lock. No UI main thread waits on it.

Non-blocking

  • registry.rs:205. If create_dir_all(&root) fails, the function returns before it removes staging. The folder stays in registry-staging. The catalog never reads that folder, so this is only a disk leak. A follow-up can remove staging on that error path.

Verdict: no blocking defect.

@lawrencecchen

Copy link
Copy Markdown
Contributor

Conflict wave: merged feat-cmux-next dd61ce5 into this branch (merge 49dc3ba). There was one conflict, in cmux-tui-core apps/local_reload.rs. One reload_local_apps_from now takes this PR's catalog_updates lock first, then feat's #29748 PrintsBefore fingerprints, then the read. This PR's watch_local and feat's is_change are both kept. The two test modules are joined.

Fleet evidence at 49dc3ba:

  • Compile (cmux-ci compile-check --package CmuxNext --app, job 15742444bc1465d99df8b172): CmuxNext and app passed.
  • Rust (macOS): test apps:: 169 passed (all local_reload tests included); clippy green.
  • Swift suites: AppWebPaneTests, AppWebPaneSchemeHandlerTests, AppRegistrySubmissionTests, AppRegistryPageProviderTests, AppOpenTargetTests, AppsPageBridgeTests, RightPanePlacementTests passed.
  • AppWebPaneUpdateTests failed twice, and this suite is this PR's own (it is not on feat): anOpenPaneStopsWhenItsAppCannotRun. On Blacksmith (step 9886e5e2717a), the .policy and .detached cases failed at line 49 (document.title == ""). On the display Mac cmuxs-Mac-mini-3 (step c1b3d5da3851), .policy failed at line 63 (document.title == "one" after the restore). The PR's earlier evidence had the suite green.
  • Review: SHIP at this head (comment 6552026401). The review read the code only.

I did not add ready-to-land back. The PR's own test is red at the merged head. Please check whether feat's changes make the pane's document load or unload timing different (the expectations read document.title right after onLoaded), and fix the test or the code.

Copy link
Copy Markdown
Collaborator

SW-08 independent security review — App Store publisher identity is first-claim, not verified (P1)

Source at this PR's exact head 49dc3ba2adf5b6f669dc2b7764fb61c2c57c8c3f, reviewed with the macOS custom-pane install and registry routes. The sandbox, per-app grants, origin checks, approved-bundle SHA-256 verification, and separate reviewer gate are valuable protections; this finding concerns who is entitled to claim a publisher name.

Verified admission path:

  1. app-registry-routes.ts admits any cmux person session with verified email to POST /v1/apps/registry/submissions; no GitHub user/org identity or repository ownership attestation is collected.
  2. app-registry-bundle.ts checks that the submitted manifest has id: <publisher>/<name> and a string repository: https://github.com/<publisher>/<repo>. It verifies the URL's owner text, not that the authenticated submitter controls the URL, repo or GitHub account.
  3. app-registry-do.ts assigns the publisher to the first user to submit with that spelling: if publishers lacks a row, INSERT (publisher,user) occurs in the submission transaction before review and remains owned even when the submission is later rejected.
  4. Review requires an allowlisted reviewer and prevents self-approval, which mitigates malicious publication. It does not prevent namespace squatting, and approval has no explicit identity/proof-of-repository-ownership gate.

Reproduction as a deterministic fixture: cmux user A, verified email but without control of GitHub somecompany, submits a valid, self-authored bundle with id=somecompany/pane and repository=https://github.com/somecompany/project. Both name checks pass and the first submit assigns somecompany to A. The legitimate controller B later submits with the same publisher and receives registry.publisher_taken, regardless of whether A's submission was rejected. A reviewer could also approve an impersonating app unless external, manual repo ownership checks catch it.

Principled correction:

  • Separate provisional name reservations from verified publisher ownership. Only bind publisher -> user after a trusted GitHub identity/repository-ownership proof (signed GitHub OAuth/App installation tied to authenticated cmux person; for organizations require verified membership/approved admin proof). An arbitrary manifest repository URL is untrusted assertion.
  • Reject unverified attempts to claim a GitHub namespace before persistent assignment. If review is intended to verify ownership manually, make that an explicit required approval fact, with audit evidence, and allow rejected/unverified provisional claims to expire or be recovered; add a publisher release/ownership transfer flow for verified operators.
  • Tests: A's syntactically valid github.com/somecompany/project cannot reserve somecompany without ownership proof; B with proof can claim; rejection expires any provisional claim; reviewer approval rejects unverified ownership; and concurrent claims serialize to one verified publisher. Keep existing bundled/first-party namespaces reserved.

Scope and proof: code-confirmed first-claim behavior on an open, unmerged PR. No external account, GitHub repo or registry endpoint was accessed or tested; this is a pre-ship source finding. It is independent of WebKit's sandbox and of the store's manual code review. SW-08 parent: https://github.com/manaflow-ai/cmuxterm-hq/issues/5599.

Copy link
Copy Markdown
Collaborator

W2-17 independent source audit (advisory, at PR head 49dc3ba2adf5b6f669dc2b7764fb61c2c57c8c3f): the example's permission explanation is narrower than the capability the daemon actually grants.

  • Workspace Board manifest labels optional terminal:execute as “Send the line you type to a terminal.” Its sample UI indeed sends from a form.
  • scopes.json maps terminal.input.write, terminal.input.keys, and terminal.input.mouse to terminal:execute. apps/web_calls.rs admits an installed/enabled pane call with that grant and forwards it through the daemon; grants.rs requires gesture tokens for specified view-state operations but does not include terminal.input.write among them.
  • Consequently, after the person grants terminal:execute, arbitrary installed pane JavaScript can call cmux.call("terminal.input.write", ...) on page load or on a timer, even without the sample form's Send click. That's expected scope-based authorization, not an authorization bypass. The disclosure should describe the capability, independent of the sample's courteous use of it.

Small fix suggested to this existing owner: change the sample's optionalScopes["terminal:execute"] copy to something like “May send input and commands to your terminals while this app is enabled,” and teach the Chief skill to use equally accurate descriptions when generating apps. If per-click consent is actually intended, that is a distinct host-owned gesture/one-use approval feature, with separate review and tests; a JavaScript click listener alone would provide no enforcement.

Targeted falsification test (in an isolated test daemon/terminal, with user grant explicitly simulated): after terminal:execute grant, have a synthetic pane call terminal.input.write in its initial script without a click; expect admission. Without the grant, expect scope.missing. Then verify the native install/permissions sheet displays the stronger capability wording. Revocation behavior is already separately covered by this PR's lifecycle tests.

Evidence level: current GitHub source, no local app install or test execution by this reviewer. This is a product-safety copy finding, not a merge HOLD request. I am retaining your custom-pane implementation and ownership; broader W2-17 results will be linked on HQ #5944.

@teamleaderleo

Copy link
Copy Markdown
Collaborator

For awareness: this PR has a SHIP review at its head, but GitHub reports it as conflicting with feat-cmux-next, so the lander's update-branch can't merge it and it can't land as is. Files that conflict in a test merge against the current base: Packages/macOS/CmuxNext/Package.swift, backend/apps/api/wrangler.jsonc, cmux-tui/crates/cmux-tui-core/src/apps/local_reload.rs. Once feat-cmux-next is merged into the branch and the conflicts resolved, it needs only a quick review of the resolution before it can land. (From the conflict sweep; no push was made to this PR.)

Conflicts:
- Package.swift: CmuxNextPages keeps the PR's CmuxNextIcons dependency next to base's BrowserCore and Files.
- wrangler.jsonc: base's CloudDO script_name and production vars, plus the PR's AppRegistryDO binding outside production.
- apps/local_reload.rs: base's Folders watcher (it watches a missing local folder appearing) supersedes the PR's create-dir watch_local; the PR's catalog_updates lock stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
austinywang and others added 4 commits November 2, 2026 21:00
…ubmission

A submit no longer claims an unowned publisher, so a pending or rejected
submission cannot squat a name (SW-08). Approving the first submission under
a publisher binds it to the submitter, audited as the reviewer's
publisher.verify; approving one under a publisher another person owns is
refused with registry.publisher_taken.

Test: backend/apps/api test/app-registry.test.ts 'never lets an unapproved
submission squat a publisher' (left to CI: the workers pool does not warm on
this host).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A granted scope is not tied to a click: any code in the pane can send
terminal input. The Workspace Board sample and the Chief skill now say so
(W2-17).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… prompt

The merge of feat-cmux-next brought a seventh built-in skill and the
mcp-contact golden; count both skills in BUILTIN and give the new golden
the custom-pane prompt line the other goldens carry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page logs "ready one" while it parses, so its didFinish could arrive
after that and be taken as the revocation's blank document: the title read
then found the old page ("one"). The test now waits for the entry page's
didFinish, and takes only an about:blank finish as the revocation.

Red: cmux-next swift test on 49dc3ba, all seven Revocation cases at
AppWebPaneUpdateTests.swift:49 (document.title).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
austinywang and others added 2 commits November 3, 2026 16:44
Conflicts kept both sides: Package.swift deps (ChiefMemory and Icons),
Chief BUILTIN skills (10 entries), PageFactory routes (registry and the
new panels route), AppsClient state (changedFiles and eventListeners),
manifest fixture tests, and production wrangler vars with the app
registry left unbound.

defaults_guard.py takes base's type-aware helper matching (#33161),
which supersedes this branch's name-only fix; the three tests that
asserted the old conservative name matching are dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The merge with feat-cmux-next joined test doubles from each side with
protocol and struct changes from the other:
- base's FakePanelsTransport now implements the PR's webCall, webLog
  and registryPut;
- the PR's UpdatingPaneTransport now implements base's
  sessionInfoPanels;
- base's sidebar integration Sources literal sets the PR's registry
  field.

Found by review of the merge; no behavior change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
austinywang and others added 2 commits November 3, 2026 17:43
Picks up #34476 (Windows workflow help and automation clippy).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b078357 added automation ops to cmux-app-host/generated/scopes.json
without resyncing the CmuxNextApps copy, so the app platform check
(sync-app-runtime.sh --check) is red on feat-cmux-next and on this PR.
Output of scripts/cmux-next/sync-app-runtime.sh; --check now passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Nov 4, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

The send-path cap guard (#33145) now covers backend/apps/api/src and
flags the App Store registry limits: MAX_FILES and MAX_DECODED_BYTES
for one app bundle, MAX_SUBMIT_BODY and MAX_DECIDE_BODY for the submit
and review bodies. These bound app packages a reviewer reads, not a
message, prompt or attachment send, so they get safe: entries.
tests/test_send_path_caps.py passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@austinywang

Copy link
Copy Markdown
Contributor Author

Ready for review at ae0bdac765ad4dae4c0ef73778b1b3edc4c8eb16. It is mergeable with current feat-cmux-next, and every check has finished. No red is caused by this PR's changes. I did not merge it.

What changed in this round

  • Merged the latest feat-cmux-next twice (e29fdb31, 471ce760) and resolved seven conflicts, keeping both sides:
    • Package.swift dependencies.
    • The Chief built-in skills (now 10).
    • The PageFactory routes (registry plus the new panels route).
    • The AppsClient state.
    • The manifest fixture tests.
    • Production wrangler.jsonc, with the app registry still unbound in production.
  • scripts/ci/defaults_guard.py takes the base's type-aware matching from defaults guard: a shared helper name is no tunable source (6182 -> 449); an unchecked BLOCK still counts #33161, which replaces this branch's name-only fix. I dropped the three tests that asserted the old behavior. All 75 tests in tests/test_defaults_guard.py pass.
  • 465afe8d: test doubles from each side were missing the other side's new requirements. Base's FakePanelsTransport was missing webCall, webLog and registryPut. The PR's UpdatingPaneTransport was missing sessionInfoPanels. The sidebar integration Sources literal was missing registry: None.
  • aae2e99e: resynced the bundled scopes.json (sync-app-runtime.sh). It was stale on the base after b07835767f.
  • ae0bdac7: the send-path cap guard from NEXT: remove draft and unsent prompt size caps #33145 now covers backend/apps/api/src. The four App Store registry limits (bundle file count and size, submit and review body sizes) bound app packages that a reviewer reads, not messages, prompts or attachments. They now have safe: entries in tests/send-path-cap-allowlist.json, and tests/test_send_path_caps.py passes.

Review

  • Earlier review of 882bbda3: SHIP. Its follow-ups are low priority and not blocking:
    • The Submissions.tsx publisher field stays empty until the first approval.
    • A refused approval leaves the submission pending.
    • publisher_taken is 403 from submit but 409 from decide.
    • The about:blank wait in anOpenPaneStopsWhenItsAppCannotRun would hang until the timeout if revocation stopped using about:blank.
  • Review of the merge resolution e29fdb31: FIX. It found the three test fakes above that would not compile. They were fixed in 465afe8d, and the seven conflict resolutions were confirmed.
  • I checked the later commits myself:
    • The scopes resync is the script's own output, and --check passes.
    • The allowlist change adds two lines.
    • The second base merge applied cleanly, with no new apps-area changes from the base.

Remaining reds, all also red on feat-cmux-next or unrelated PRs

Dogfood

  • A fleet build of e29fdb31 is published as tag 26713-custom-panes-app-store (local backend).
  • To try it:
    1. Turn on labs.previewFeatures.
    2. Open the App Store and install a default app (cmux docs, quick links, standup, hello deploy) as a pane.
    3. Ask Chief for a custom pane; it uses the cmux-custom-pane skill.
  • Registry submission needs the dev or staging backend.

This PR is labeled risk:high, so it still needs the independent review receipt on the exact head before it lands.

This branch was previously deployed

3 inactive deployments
artifacts — 0e726a95 Deployed Nov 4, 2026 by austinywang via publish tree-addressed cmux-tui #109921
sentry-dsyms — 0e726a95 Deployed Nov 4, 2026 by austinywang via upload cmux-tui dSYMs to Sentry #109921
ci-routing-agent — 0e726a95 Deployed Nov 4, 2026 by austinywang via namespace autofix #14036
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

media-tour Run the cmux-next UI tour (cmux-next-media.yml) on every push risk:high Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants