Skip to content

fix(browser): reject non-page json new targets#104129

Merged
steipete merged 3 commits into
openclaw:mainfrom
Leon-SK668:llagy007/browser-cdp-json-new-page-target-20260711
Jul 11, 2026
Merged

fix(browser): reject non-page json new targets#104129
steipete merged 3 commits into
openclaw:mainfrom
Leon-SK668:llagy007/browser-cdp-json-new-page-target-20260711

Conversation

@Leon-SK668

@Leon-SK668 Leon-SK668 commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Remote raw CDP tab listing filters /json/list results to selectable page targets, but the raw /json/new fallback accepted the target returned by Chrome without applying that same target contract. If Chrome returned a worker, browser-internal, or otherwise non-selectable target, OpenClaw could store it as the latest tab and continue as though a user page was opened.

Why This Change Was Made

The fallback path should uphold the same page-target hygiene as the list path. This change applies the existing isSelectableCdpBrowserTarget helper to the /json/new response before adopting the target id.

User Impact

Valid page targets continue to open normally. Non-page or browser-internal targets returned by raw CDP tab creation now fail closed instead of becoming the selected browser tab.

Evidence

  • Added a fallback regression where Target.createTarget is unavailable and /json/new returns a worker target.
  • The test verifies openTab rejects the target and does not store it as the profile's lastTargetId.
  • Real Chrome proof at head 0652ddcfac0586ff444b8d12818c620f3e7189f1: /usr/bin/google-chrome with remote debugging returned a normal /json/new target with type: "page", url: "about:blank", and a webSocketDebuggerUrl.
  • Controlled production fallback proof at the same head: createProfileTabOps.openTab accepted a page-shaped /json/new response and rejected a worker-shaped /json/new response without changing lastTargetId.

Proof

Base: upstream/main at ffc8051cacb50d1f4b39cc2c6f822083caba1f2a.

  • node scripts/run-vitest.mjs extensions/browser/src/browser/server-context.remote-profile-tab-ops.fallback.test.ts -- --reporter=verbose - 1 file / 13 tests passed.
  • git diff --check upstream/main...HEAD -- extensions/browser/src/browser/server-context.tab-ops.ts extensions/browser/src/browser/server-context.remote-profile-tab-ops.fallback.test.ts
  • Real/controlled runtime proof:
{
  "head": "0652ddcfac0586ff444b8d12818c620f3e7189f1",
  "chromeBin": "/usr/bin/google-chrome",
  "rawChromeJsonNew": {
    "ok": true,
    "type": "page",
    "url": "about:blank",
    "webSocketDebuggerUrlPresent": true
  },
  "createTargetProbe": {
    "ok": true
  },
  "fallbackPage": {
    "targetId": "PAGE",
    "type": "page",
    "url": "about:blank",
    "lastTargetId": "PAGE"
  },
  "rejectNonPage": {
    "ok": true,
    "message": "Failed to open tab (non-selectable target)",
    "lastBefore": "before-target",
    "lastAfter": "before-target"
  }
}

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. labels Jul 11, 2026
@clawsweeper

clawsweeper Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 11, 2026, 3:41 PM ET / 19:41 UTC.

Summary
The PR applies the existing selectable-page target predicate to raw CDP /json/new responses before storing the created target id and adds a worker-target regression test.

PR surface: Source +3, Tests +27. Total +30 across 2 files.

Reproducibility: yes. at source level. Current main adopts any raw /json/new response with an id, and the focused test drives a worker-shaped response through that exact production fallback; the contributor also supplied controlled runtime output for the failure and corrected behavior.

Review metrics: none identified.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Risk before merge

  • [P1] The stricter fallback intentionally stops accepting non-Chromium or malformed /json/new descriptors that omit type or return a non-page target; this is consistent with the already-landed list-path policy and upstream Chromium contract, but it is still a compatibility-visible fail-closed change for nonconforming remote CDP services.

Maintainer options:

  1. Accept the established page-target contract (recommended)
    Land the patch because it extends the already-merged page-only target policy to the remaining raw creation path and matches Chromium's required typed descriptor.
  2. Pause for alternate-CDP compatibility evidence
    Hold the PR only if OpenClaw intentionally supports a known remote CDP service whose /json/new response omits type or returns a non-page descriptor for page creation.

Next step before merge

  • [P2] No repair task is needed; maintainers should review the explicit compatibility risk and handle the routine exact-head merge path.

Security
Cleared: The diff introduces no dependency, workflow, permission, secret, download, or supply-chain change and leaves existing navigation and CDP endpoint security checks intact.

Review details

Best possible solution:

Land the shared-predicate check with its regression, preserving the current navigation and endpoint validations; maintainers should explicitly accept the contract-aligned rejection of nonconforming raw CDP target descriptors.

Do we have a high-confidence way to reproduce the issue?

Yes at source level. Current main adopts any raw /json/new response with an id, and the focused test drives a worker-shaped response through that exact production fallback; the contributor also supplied controlled runtime output for the failure and corrected behavior.

Is this the best way to solve the issue?

Yes. Reusing the existing shared selectable-target predicate at the tab-ops owner boundary is narrower and less drift-prone than adding another filter, and it preserves the existing SSRF/navigation and CDP endpoint checks.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 589f1c00de32.

Label changes

Label changes:

  • add merge-risk: 🚨 compatibility: The new fail-closed check can stop previously accepted nonconforming remote /json/new responses that omit type or return a non-page target.
  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • remove rating: 🐚 platinum hermit: Current PR rating is rating: 🦞 diamond lobster, so this older rating label is no longer current.

Label justifications:

  • P2: The PR fixes a bounded browser fallback state-selection bug with limited blast radius and no evidence of an urgent widespread regression.
  • merge-risk: 🚨 compatibility: The new fail-closed check can stop previously accepted nonconforming remote /json/new responses that omit type or return a non-page target.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): Exact-head live output shows normal Chrome returns a selectable page, while controlled production-helper execution accepts that page and rejects a worker without changing lastTargetId.
  • proof: sufficient: Contributor real behavior proof is sufficient. Exact-head live output shows normal Chrome returns a selectable page, while controlled production-helper execution accepts that page and rejects a worker without changing lastTargetId.
Evidence reviewed

PR surface:

Source +3, Tests +27. Total +30 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 4 1 +3
Tests 1 27 0 +27
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 31 1 +30

What I checked:

  • Current-main gap: Current main validates that /json/new returned an id, then immediately stores it as lastTargetId; unlike the raw list path, it does not check the returned target type or browser-internal URL first. (extensions/browser/src/browser/server-context.tab-ops.ts:442, 589f1c00de32)
  • Shared invariant: The same module already filters discovered targets through isSelectableCdpBrowserTarget, so reuse in raw creation keeps one canonical page-target policy rather than adding a parallel rule. (extensions/browser/src/browser/server-context.tab-ops.ts:262, 589f1c00de32)
  • Patch behavior: The PR head rejects a non-selectable created target before mutating lastTargetId, then continues through the existing navigation-result and discovered-endpoint policy checks for accepted targets. (extensions/browser/src/browser/server-context.tab-ops.ts:446, 8a936e173602)
  • Regression coverage: The new test forces the production raw /json/new fallback with Target.createTarget unavailable, returns a worker-shaped descriptor, and verifies rejection without adopting the worker id. (extensions/browser/src/browser/server-context.remote-profile-tab-ops.fallback.test.ts:186, 8a936e173602)
  • Upstream contract: Chromium's /json/new implementation creates a frame target and serializes a target descriptor; the Chrome DevTools Protocol defines TargetInfo.type as required, supporting fail-closed handling for missing or non-page types.
  • Real behavior proof: The PR body reports exact-head real Chrome output for a normal page response and controlled execution of the production fallback showing page acceptance, worker rejection, and unchanged lastTargetId; GitHub marks the proof sufficient. (8a936e173602)

Likely related people:

  • steipete: He authored the recent current-main raw tab-creation refactor and performed the land-ready review and merge of the shared page-only target predicate. (role: recent area contributor and merger; confidence: high; commits: 444d7dfb1a, ea2b558e5ee, 910886814d91; files: extensions/browser/src/browser/server-context.tab-ops.ts)
  • llagy007: Beyond authoring this PR, they authored the previously merged change that introduced the shared non-page CDP target filter now reused here. (role: introduced related behavior; confidence: high; commits: 02043b075ea6, 910886814d91; files: extensions/browser/src/browser/server-context.tab-ops.ts, extensions/browser/src/browser/server-context.remote-profile-tab-ops.fallback.test.ts)
  • eleqtrizit: They authored the merged hardening of discovered CDP WebSocket URLs in the same raw remote fallback and its focused regression surface. (role: adjacent browser fallback contributor; confidence: medium; commits: 1f8597805430, ade5ac03506e; files: extensions/browser/src/browser/server-context.tab-ops.ts, extensions/browser/src/browser/server-context.remote-profile-tab-ops.fallback.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (2 earlier review cycles)
  • reviewed 2026-07-11T03:49:39.811Z sha 0652ddc :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-11T06:11:23.328Z sha 0652ddc :: needs maintainer review before merge. :: none

@Leon-SK668

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Added real behavior proof for the /json/new fallback target filter at head 0652ddcfac0586ff444b8d12818c620f3e7189f1.

Focused validation:

  • node scripts/run-vitest.mjs extensions/browser/src/browser/server-context.remote-profile-tab-ops.fallback.test.ts -- --reporter=verbose - 1 file / 13 tests passed.
  • git diff --check upstream/main...HEAD -- extensions/browser/src/browser/server-context.tab-ops.ts extensions/browser/src/browser/server-context.remote-profile-tab-ops.fallback.test.ts

Real/controlled proof:

  • Started /usr/bin/google-chrome with remote debugging and confirmed raw Chrome /json/new for about:blank returns a selectable page target with a debugger URL.
  • Ran production createProfileTabOps.openTab against a controlled CDP HTTP server where the primary Target.createTarget path is unavailable, forcing the raw /json/new fallback.
  • The same production fallback accepted a page-shaped response and rejected a worker-shaped response without changing lastTargetId.
{
  "head": "0652ddcfac0586ff444b8d12818c620f3e7189f1",
  "chromeBin": "/usr/bin/google-chrome",
  "rawChromeJsonNew": {
    "ok": true,
    "type": "page",
    "url": "about:blank",
    "webSocketDebuggerUrlPresent": true
  },
  "createTargetProbe": {
    "ok": true
  },
  "fallbackPage": {
    "targetId": "PAGE",
    "type": "page",
    "url": "about:blank",
    "lastTargetId": "PAGE"
  },
  "rejectNonPage": {
    "ok": true,
    "message": "Failed to open tab (non-selectable target)",
    "lastBefore": "before-target",
    "lastAfter": "before-target"
  }
}

@clawsweeper

clawsweeper Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 11, 2026
…p-json-new-page-target-20260711

# Conflicts:
#	extensions/browser/src/browser/server-context.tab-ops.ts
@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Jul 11, 2026
@steipete steipete self-assigned this Jul 11, 2026
@steipete

Copy link
Copy Markdown
Contributor

Maintainer verification complete at head 6eb6744f028144df79c61a8c3b84d4e888e9f0b4.

I kept the contributor's shared target-predicate fix and tightened the adoption boundary: a raw-created target now becomes lastTargetId only after its target type, navigation result, and discovered WebSocket endpoint all pass validation. The regression matrix now covers a worker target, a browser-internal page target, and the state leak after a policy-blocked discovered WebSocket.

Validation:

  • Sanitized AWS Crabbox cbx_e380997317da, public network, no Tailscale, no instance profile, exact reviewed head verified:
    • run_bdbcbc93a48c: focused raw-fallback suite, 1 file / 14 tests passed.
    • run_388e0b090b4c: full check:changed extensions + extension-tests lanes passed, including format, production/test types, lint, database-first guard, runtime sidecar guard, and import-cycle guard.
  • Fresh whole-branch autoreview: clean, no accepted/actionable findings, confidence 0.95.
  • Local macOS exact-head pnpm build: passed in 185.9 seconds.
  • Real exact-head Browser scenario: isolated Chrome, loopback Gateway, OpenAI gpt-5.4, and real Chrome DevTools MCP profile chrome-live. Agent run 1c68a2d1-2028-4298-a2b7-24c3b3668848 opened https://example.com, snapshotted and evaluated the same target chrome-mcp:6e56c28cd65c:2, returned title Example Domain plus fresh nonce PR104129_LIVE_PAGE_8QJ, and reported 5 Browser calls / 0 failures with no fallback model.
  • Source-blind behavior contract: 6 passed, 0 failed or blocked; target identity and fresh-nonce anti-cheat probes passed.

Proof boundary: compliant Chrome returns a page descriptor from /json/new, so the invalid worker/internal response is exercised through the exact production fallback in the focused regression rather than claimed as a natural Chrome behavior. The real Gateway/agent run proves ordinary browser functionality remains intact.

@steipete
steipete merged commit 7027d2c into openclaw:main Jul 11, 2026
94 checks passed
@steipete

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 12, 2026
* fix(browser): reject non-page json new targets

* fix(browser): adopt only validated raw CDP targets

---------

Co-authored-by: Peter Steinberger <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: XS status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants