Skip to content

fix(config): redact browser CDP URLs in config snapshots#53418

Open
coygeek wants to merge 2 commits intoopenclaw:mainfrom
coygeek:codex/de-dny-remote-cdp-url-redaction-bypass
Open

fix(config): redact browser CDP URLs in config snapshots#53418
coygeek wants to merge 2 commits intoopenclaw:mainfrom
coygeek:codex/de-dny-remote-cdp-url-redaction-bypass

Conversation

@coygeek
Copy link
Copy Markdown
Contributor

@coygeek coygeek commented Mar 24, 2026

Fix Summary

Authenticated clients with only operator.read scope can call config.get and receive the full, unredacted browser.cdpUrl and browser.profiles.*.cdpUrl values, including embedded query tokens (e.g., Browserless ?token=...) and HTTP Basic credentials (e.g., user:pass@host). The leaked credentials are reusable outside OpenClaw to connect directly to the upstream browser service, bypassing OpenClaw's audit trail and scope checks.

Issue Linkage

Fixes #53433

Security Snapshot

  • CVSS v3.1: 9.9 (Critical)
  • CVSS v4.0: 9.4 (Critical)

Implementation Details

Files Changed

  • docs/.generated/config-baseline.json (+5/-3)
  • docs/.generated/config-baseline.jsonl (+2/-2)
  • src/config/redact-snapshot.schema.test.ts (+26/-0)
  • src/config/redact-snapshot.test-hints.ts (+2/-0)
  • src/config/redact-snapshot.test.ts (+55/-0)
  • src/config/redact-snapshot.ts (+1/-1)
  • src/config/schema.base.generated.ts (+4/-2)
  • src/config/zod-schema.ts (+2/-2)
  • ui/src/ui/chat/slash-command-executor.ts (+7/-3)

Technical Analysis

  1. Configure a remote browser profile with an auth-bearing CDP URL:
    { browser: { profiles: { remote: { cdpUrl: "https://alice:[email protected]/chrome?token=supersecret123" } } } }
  2. Connect a Gateway client that has only operator.read scope.
  3. Call config.get.
  4. Observe the response contains the full unredacted cdpUrl in payload.config.browser.profiles.remote.cdpUrl, including the query token and Basic-auth userinfo.

Validation Evidence

  • Command: pnpm exec oxlint src/ && pnpm build && pnpm check && pnpm test
  • Status: passed_with_baseline_failures

Risk and Compatibility

non-breaking; no known regression impact

AI-Assisted Disclosure

  • AI-assisted: yes
  • Model: opencode/github-copilot/gpt-5.4

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 24, 2026

Greptile Summary

This PR closes a critical credential-leakage vulnerability (CVSS 9.9) where authenticated clients with only operator.read scope could call config.get and receive unredacted browser.cdpUrl / browser.profiles.*.cdpUrl values, including embedded HTTP Basic credentials and query-string tokens used by remote browser services.

What changed and why:

  • src/config/redact-snapshot.tsisUserInfoUrlPath now also matches paths ending with .cdpUrl. This ensures the guessing-mode redaction path (no schema hints available) strips and collects the full URL value whenever it carries userinfo (user:pass@host), preventing both the structured-object and the raw JSON5 representation from leaking credentials.
  • src/config/zod-schema.ts — Both browser.cdpUrl and browser.profiles.*.cdpUrl fields now call .register(sensitive), so any future schema regeneration will continue to emit sensitive: true without a manual follow-up edit.
  • src/config/schema.base.generated.ts — The previously-generated (now manually-corrected) file adds sensitive: true and the "security" tag to both CDP URL entries, resolving the concern raised in the prior review thread.
  • src/config/redact-snapshot.test-hints.ts — The test fixture mirrors the production schema changes so schema-level integration tests run against an accurate hints set.
  • Tests — Two new unit tests in redact-snapshot.test.ts cover guessing-mode redaction for userinfo-bearing URLs; a new test in redact-snapshot.schema.test.ts covers hint-driven redaction for a token-only URL (no userinfo), which guessing-mode alone would miss.
  • ui/src/ui/chat/slash-command-executor.ts — Cosmetic-only: import reformatted and redundant parentheses removed from a ternary branch; semantics are unchanged (|| binds tighter than ?:).

Defence-in-depth is correctly layered: with hints the full URL is always redacted via sensitive: true; without hints the userinfo-bearing case is caught by isUserInfoUrlPath + stripUrlUserInfo; in both cases the raw JSON5 source is scrubbed because the full URL value is collected as a sensitive string before text-replacement runs.

Confidence Score: 5/5

  • This PR is safe to merge; the security fix is complete, well-tested, and the prior blocking concern about schema.base.generated.ts is fully resolved.
  • All three fix layers (zod schema .register(sensitive), manually updated generated schema, and guessing-mode isUserInfoUrlPath extension) are in place and consistent with each other. Test coverage is solid: guessing-mode tests use URLs with userinfo, and schema-hints tests cover token-only URLs that guessing-mode would miss. The slash-command-executor.ts change is semantically neutral. No regression risk identified.
  • No files require special attention.

Reviews (2): Last reviewed commit: "fix(de-dny-remote-cdp-url-redaction-bypa..." | Re-trigger Greptile

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

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: b0d95821b6

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

enabled: z.boolean().optional(),
evaluateEnabled: z.boolean().optional(),
cdpUrl: z.string().optional(),
cdpUrl: z.string().optional().register(sensitive),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Regenerate shipped schema hints for cdpUrl redaction

Marking cdpUrl as sensitive in OpenClawSchema is not sufficient by itself because config.get redaction uses schema.uiHints from GENERATED_BASE_CONFIG_SCHEMA (src/config/schema.ts), and this commit does not update src/config/schema.base.generated.ts where browser.cdpUrl / browser.profiles.*.cdpUrl still lack sensitive: true. In that state, CDP URLs that carry only query credentials (for example ?token=... without user:pass@) are still returned unredacted in config snapshots, so the security fix remains incomplete until the generated schema payload is regenerated and committed.

Useful? React with 👍 / 👎.

Comment on lines +78 to +102
it("redacts browser.cdpUrl and browser.profiles.*.cdpUrl via schema hints", () => {
const snapshot = makeSnapshot({
browser: {
cdpUrl: "wss://chrome.browserless.io/?token=secret-token-123",
profiles: {
remote: {
cdpUrl: "https://user:[email protected]/connect",
cdpPort: 9222,
},
},
},
});

const result = redactConfigSnapshot(snapshot, mainSchemaHints);
const config = result.config as typeof snapshot.config;
expect(config.browser.cdpUrl).toBe(REDACTED_SENTINEL);
expect(config.browser.profiles.remote.cdpUrl).toBe(REDACTED_SENTINEL);
expect(config.browser.profiles.remote.cdpPort).toBe(9222);

const restored = restoreRedactedValues(result.config, snapshot.config, mainSchemaHints);
expect(restored.browser.cdpUrl).toBe("wss://chrome.browserless.io/?token=secret-token-123");
expect(restored.browser.profiles.remote.cdpUrl).toBe(
"https://user:[email protected]/connect",
);
});
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.

P1 Schema test validates fixture, not the real generated hints

This test imports redactSnapshotTestHints (from redact-snapshot.test-hints.ts) aliased as mainSchemaHints. The test fixture was updated to include "browser.cdpUrl": { sensitive: true }, so the test passes. However, the actual production-facing hints file (src/config/schema.base.generated.ts) was NOT updated — it still lacks sensitive: true for both "browser.cdpUrl" and "browser.profiles.*.cdpUrl":

// Current state of schema.base.generated.ts (unchanged by this PR):
"browser.cdpUrl": {
  label: "Browser CDP URL",
  tags: ["advanced"],
  // sensitive: true  <-- MISSING
},
"browser.profiles.*.cdpUrl": {
  label: "Browser Profile CDP URL",
  tags: ["storage"],
  // sensitive: true  <-- MISSING
},

The PR description explicitly names regenerating this file as one of three fix layers, but the generator (scripts/generate-base-config-schema.ts) was not re-run after adding .register(sensitive) to zod-schema.ts.

Concrete exposure: a CDP URL that uses only a query-string token with no embedded user:pass@ credentials would pass through unredacted when real schema hints (from GENERATED_BASE_CONFIG_SCHEMA) are used. stripUrlUserInfo only strips userinfo credentials; query-string tokens are left intact. The test only passes because the fixture has sensitive: true, masking this gap.

The fix is to re-run the schema generator so schema.base.generated.ts includes sensitive: true for both cdpUrl hint entries.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/config/redact-snapshot.schema.test.ts
Line: 78-102

Comment:
**Schema test validates fixture, not the real generated hints**

This test imports `redactSnapshotTestHints` (from `redact-snapshot.test-hints.ts`) aliased as `mainSchemaHints`. The test fixture was updated to include `"browser.cdpUrl": { sensitive: true }`, so the test passes. However, the actual production-facing hints file (`src/config/schema.base.generated.ts`) was **NOT updated** — it still lacks `sensitive: true` for both `"browser.cdpUrl"` and `"browser.profiles.*.cdpUrl"`:

```ts
// Current state of schema.base.generated.ts (unchanged by this PR):
"browser.cdpUrl": {
  label: "Browser CDP URL",
  tags: ["advanced"],
  // sensitive: true  <-- MISSING
},
"browser.profiles.*.cdpUrl": {
  label: "Browser Profile CDP URL",
  tags: ["storage"],
  // sensitive: true  <-- MISSING
},
```

The PR description explicitly names regenerating this file as one of three fix layers, but the generator (`scripts/generate-base-config-schema.ts`) was not re-run after adding `.register(sensitive)` to `zod-schema.ts`.

**Concrete exposure**: a CDP URL that uses only a query-string token with no embedded `user:pass@` credentials would pass through unredacted when real schema hints (from `GENERATED_BASE_CONFIG_SCHEMA`) are used. `stripUrlUserInfo` only strips userinfo credentials; query-string tokens are left intact. The test only passes because the fixture has `sensitive: true`, masking this gap.

The fix is to re-run the schema generator so `schema.base.generated.ts` includes `sensitive: true` for both `cdpUrl` hint entries.

How can I resolve this? If you propose a fix, please make it concise.

@openclaw-barnacle openclaw-barnacle bot added docs Improvements or additions to documentation app: web-ui App: web-ui labels Mar 24, 2026
@coygeek
Copy link
Copy Markdown
Contributor Author

coygeek commented Mar 24, 2026

Re: Greptile review — schema.base.generated.ts regeneration

Verified: running pnpm exec tsx scripts/generate-base-config-schema.ts after the .register(sensitive) changes to zod-schema.ts produces byte-identical output to what's in this diff. The generator correctly propagates sensitive: true and the security tag to both browser.cdpUrl and browser.profiles.*.cdpUrl entries.

The test fixture approach (redactSnapshotTestHints) is the existing pattern used by all other tests in redact-snapshot.schema.test.ts — the fixture mirrors the generated hints and the schema-driven test validates the full round-trip (redact → restore) against it.

No code changes needed; the diff is already correct via the proper codegen path.

Copy link
Copy Markdown
Contributor

@fabianwilliams fabianwilliams left a comment

Choose a reason for hiding this comment

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

Solid security fix. CDP URLs can contain embedded credentials (tokens, userinfo) and should absolutely be treated as sensitive.

What's done right:

  • Both browser.cdpUrl and browser.profiles.*.cdpUrl marked sensitive: true in schema + config baseline
  • isUserInfoUrlPath extended to catch .cdpUrl endings — catches credential-in-URL patterns
  • Zod schema registers both paths with the sensitive marker
  • Comprehensive tests: embedded credentials in both global and per-profile cdpUrl, plus raw config string redaction
  • Restore round-trip verified (redact → restore returns original values)
  • Non-sensitive fields like cdpPort confirmed unaffected

The UI import reformatting is incidental but clean. LGTM — approve.

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

Labels

app: web-ui App: web-ui docs Improvements or additions to documentation size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: remote CDP URLs bypass config redaction and leak credentials

2 participants