# OpenChrome v1.10.4

Release date: 2026-05-08

## Summary

v1.10.4 is a **security and reliability patch release** focused on hardening
the HTTP transport, the dashboard REST surface, and tool I/O paths that handle
untrusted input or produce large payloads.

This release lands the post-v1.10.3 verification work as a single publishable
package. The bulk of the changes are defense-in-depth fixes that close
authentication, authorization, traversal, and resource-exhaustion gaps without
changing the public MCP tool surface for normal callers.

Operators running OpenChrome in `--transport http` or `--transport both` mode,
or anyone exposing the dashboard REST endpoints, should upgrade.

---

## Why this patch release exists

After v1.10.3, the security and reliability tracker still had a cluster of
HTTP-transport and tool-input gaps that needed to be addressed before the next
publishable build:

1. unauthenticated HTTP could be served silently when no auth mode was set,
2. browser-origin requests against MCP endpoints were not constrained to an
   explicit allowlist,
3. dashboard REST endpoints did not consistently enforce tenant ownership or
   admin scope,
4. JSON-RPC HTTP batches had no size or concurrency cap, so a single client
   could fan out unbounded work,
5. `RequestQueue` could leak timer handles after success and keep the event
   loop alive,
6. `file_upload` candidate paths could escape outside the working directory or
   the configured upload roots via traversal/symlink resolution,
7. screenshot capture paths could return unbounded inline payloads,
8. `read_page` AX-overflow handling could re-traverse the entire DOM
   implicitly, and
9. several detector failures in `navigate` were collapsed to `null` instead of
   surfacing actionable errors, and the marker/PID orphan reaper was only
   reachable as an internal startup path.

v1.10.4 addresses each of these with focused fixes plus targeted regression
tests, and clears the remaining source-tree ESLint warnings in the process.

---

## Highlights

### 1. HTTP auth now fails closed and CORS is locked to an explicit allowlist

The HTTP transport previously accepted unauthenticated traffic when no auth
mode was configured. v1.10.4 inverts that default:

- starting `--transport http` (or `both`) without an auth mechanism now fails
  closed at startup,
- a single explicit opt-in remains for unauthenticated **loopback** development
  via `--allow-unauthenticated-http` /
  `OPENCHROME_ALLOW_UNAUTHENTICATED_HTTP=1`,
- non-loopback binds still require an auth mechanism even when the
  loopback opt-in is set,
- browser-origin requests with an `Origin` header must match the configured
  CORS allowlist; preflights now include `Authorization` plus the tenant
  request headers,
- `stdio` transport is unchanged.

Existing bearer/API-key/JWT auth flows continue to work without modification.

Closes #681.

### 2. Dashboard REST endpoints enforce tenant ownership and admin scope

The dashboard REST surface now runs through a request-principal authorization
middleware:

- session and screenshot reads are scoped by tenant; cross-tenant requests are
  rejected,
- tool-call listings and metrics endpoints require admin scope,
- authorization failures are audited for operator visibility,
- the auth model is documented in `docs/auth.md`.

Closes #682.

### 3. HTTP JSON-RPC batches are now bounded in size and concurrency

JSON-RPC over HTTP now has explicit guardrails:

- maximum batch size: `32` (configurable via
  `OPENCHROME_HTTP_JSON_RPC_BATCH_MAX_SIZE`),
- maximum concurrent in-flight batch handlers: `4` (configurable via
  `OPENCHROME_HTTP_JSON_RPC_BATCH_MAX_CONCURRENCY`, clamped to at least `1`),
- oversized batches are rejected before any element executes, so there are no
  partial browser side effects,
- response order is preserved for accepted batches,
- per-session/per-worker `RequestQueue` serialization continues to apply once
  individual requests enter the normal pipeline.

In addition, `RequestQueue` itself now clears timer handles on both success
and failure paths and uses `unref()` where supported, eliminating a class of
"server stays alive after work finishes" symptoms in long-running daemons.

Closes #683.

### 4. `file_upload` paths are restricted and traversal-safe

`file_upload` candidate resolution is now constrained to a small, well-defined
set of locations:

- the current working directory,
- the temp upload directory,
- explicitly configured upload roots.

Real paths are resolved before containment checks, so symlink-based escapes
and `..` traversal are rejected. Operators can configure additional roots via
the documented config keys; everything outside the allowlist now returns a
deterministic validation error.

Closes #685.

### 5. Screenshot capture paths cap inline payload sizes

`page_screenshot`, the `computer` screenshot pipeline, and the annotated
vision screenshot path now all share a guard layer that:

- caps capture-area dimensions before encoding,
- caps inline-image payload size before returning,
- handles capture timeouts with a single, well-defined retry rather than
  open-ended polling.

Callers that want raw, unbounded screenshots have not been added; the contract
is "return bounded inline images, fail fast otherwise."

Closes #686.

### 6. `read_page` AX-overflow no longer falls back to a full DOM traversal by default

Large-page `read_page` performance regressions were traced to two issues:

- AX-tree root detection was O(n²) because the root scan walked the entire
  tree per candidate; this is now an O(n) child-id-set check,
- AX overflow implicitly performed a second, expensive DOM traversal.

v1.10.4 changes the default behavior: AX overflow now returns bounded AX
output with actionable guidance, and DOM fallback is **opt-in** via
`fallback: "dom"`. Bounded read depth is also passed into `DOM.getDocument`
without losing iframe/shadow piercing compatibility.

Refs #684.

### 7. `navigate` surfaces detector failures and `oc_reap_orphans` is exposed as a tool

Two verification gaps from prior releases are closed:

- `detectBlockingPage` failures and timeouts during `navigate` are now
  surfaced in the response instead of being collapsed to `null` (#658
  follow-up),
- the marker/PID orphan reaper that previously only ran as a startup
  internal is now available as the `oc_reap_orphans` MCP tool, scoped to
  marker/PID matches and safe in attach mode (#661 follow-up),
- the Cursor cross-environment tool-count check was updated for the new
  lifecycle tool.

### 8. Source ESLint warnings cleared and a changed-files guardrail added

The current source-tree ESLint warnings are removed via no-behavior cleanup
(unused imports/variables, `prefer-const`, underscore-prefixing for unused
callback args). A new `npm run lint:changed` script lints only the changed
`src/**/*.ts` files relative to a base ref and treats warnings as failures,
giving CI and local pre-push a fast guardrail without opting the whole tree
into broader strict rules yet.

Closes #689.

---

## Configuration changes

| Setting | Default | Purpose |
|---|---|---|
| `--allow-unauthenticated-http` / `OPENCHROME_ALLOW_UNAUTHENTICATED_HTTP` | unset (fail closed) | Explicit loopback-only opt-in for unauthenticated HTTP development. Has no effect on non-loopback binds. |
| `OPENCHROME_HTTP_JSON_RPC_BATCH_MAX_SIZE` | `32` | Maximum number of JSON-RPC messages per HTTP batch. |
| `OPENCHROME_HTTP_JSON_RPC_BATCH_MAX_CONCURRENCY` | `4` | Maximum concurrent in-flight batch handlers. Clamped to ≥ `1`. |
| `read_page` `fallback: "dom"` | unset (AX-only) | Opt-in for the legacy DOM traversal fallback when AX output overflows. |
| File-upload roots | cwd + temp upload dir | Additional roots configurable via documented config keys; symlinks resolved before containment check. |

No new runtime dependencies were added. The `npm run lint:changed` helper is
internal and additive.

---

## Operator-impact summary

If you are upgrading from v1.10.3 to v1.10.4, the most important practical
changes are:

- `--transport http` / `both` will refuse to start with no auth mode unless
  you explicitly opt in to loopback-only unauthenticated mode,
- browser-origin MCP requests must come from an allowlisted origin,
- dashboard REST consumers must have correct tenant scope (and admin scope for
  metrics/tool-call listings),
- HTTP clients sending more than 32 JSON-RPC messages per batch must split
  their batches,
- callers that previously relied on `read_page` implicitly falling back to a
  full DOM traversal must now pass `fallback: "dom"` explicitly,
- `file_upload` callers that were uploading files from outside the working
  directory or temp upload directory must configure those locations as
  upload roots,
- `navigate` responses now contain detector failure details instead of `null`
  for cases that previously swallowed errors,
- a new `oc_reap_orphans` tool is available for manual marker/PID orphan
  sweeps.

---

## Compatibility notes

v1.10.4 is intended as a backward-compatible patch release for the typical
authenticated HTTP, stdio, and dashboard-with-correct-scope use cases.

Notable behavior changes to be aware of:

- HTTP transport now fails closed when no auth mechanism is configured,
- browser CORS for MCP endpoints is now an allowlist rather than permissive,
- `read_page` AX overflow no longer auto-promotes to DOM fallback,
- `file_upload` rejects candidates outside the allowlisted roots, including
  symlink targets,
- screenshot inline payloads are size-bounded; oversized captures fail
  deterministically rather than returning huge inline images.

`stdio` callers, authenticated HTTP callers, correctly tenant-scoped dashboard
consumers, and tool callers using normal-sized inputs should see no behavior
change beyond the new tool (`oc_reap_orphans`) and surfaced detector errors
in `navigate`.

---

## Verification performed for v1.10.4

The integrated release branch was validated with fresh repository checks.

### Build / static checks
- `npm run build`
- `npm run lint -- --max-warnings=0`
- `npm run lint:changed -- --base origin/develop`

### Targeted regression coverage merged in this release
- `tests/transports/http-batch-limits.test.ts`
- `tests/transports/http-bearer-auth.test.ts` (expanded for fail-closed + CORS)
- `tests/utils/request-queue-timers.test.ts`
- `tests/security/dashboard-rest-authz.test.ts`
- `tests/tools/file-upload-validation.test.ts`
- `tests/tools/page-screenshot-guards.test.ts`
- `tests/tools/computer.test.ts` (screenshot guard coverage)
- `tests/vision/screenshot-analyzer.test.ts` (screenshot guard coverage)
- `tests/tools/read-page.test.ts`, `tests/tools/read-page-dom.test.ts`,
  `tests/dom/dom-serializer.test.ts`
- `tests/tools/reap-orphans.test.ts`
- `tests/integration/health-endpoint-gating.test.ts`

Each of the underlying PRs (#680, #691–#697) was independently validated with
its own jest run plus `tsc` and `eslint --max-warnings=0` over the changed
files; see the individual PR descriptions for the full per-PR validation
matrix.

During release integration two PR-merge collisions were also resolved on the
release branch: the JSON-RPC batch-limit and dashboard-REST authorization
test suites were updated to use the new `allowUnauthenticatedHttp: true`
loopback opt-in introduced by the fail-closed HTTP auth work, since those
tests had been written against the pre-fail-closed constructor signature.
The production transport behavior is unchanged.

---

## Upgrade recommendation

**Recommended upgrade:** yes — strongly recommended for anyone running
OpenChrome with `--transport http` / `both`, exposing the dashboard REST
endpoints, or accepting `file_upload` requests from agent flows. Stdio-only
users still benefit from the `read_page` performance work, the screenshot
payload guards, the `navigate` detector-error surfacing, and the new
`oc_reap_orphans` tool.
