Skip to content

Local request origin checks, single-use approvals under clock rollback, browser cleanup and migration logging - #15

Merged
hyperdrivebeep merged 1 commit into
mainfrom
public-20261019
Oct 19, 2026
Merged

hyperdrivebeep merged 1 commit into
mainfrom
public-20261019

Conversation

@hyperdrivebeep

Copy link
Copy Markdown
Owner

The local server ran any request it received. A web page open in the
browser could send a text/plain POST to the local port with its own
Origin, or reach it under a foreign Host name (DNS rebinding), and create
projects or cancel running work. Local mode now checks every request
before reading its body or touching the runtime or storage:

  • Host must be 127.0.0.1, localhost or [::1] on any port; otherwise
    403 LOCAL_HOST_REJECTED.
  • A request that can change state and carries an Origin must come from
    an http Origin on one of those names; otherwise 403
    LOCAL_ORIGIN_REJECTED. Local programs that send no Origin still work.
  • /rpc, /rpc/query, /rpc/async and operation cancel require an
    application/json body; otherwise 415 JSON_CONTENT_TYPE_REQUIRED.
  • A rejected request writes nothing, the response never echoes the
    received headers, and no CORS headers or settings are added. Hosted
    review mode is unchanged.

The built-in UI, the Vite development proxy and local JSON clients
behave as before. docs/KNOWN_LIMITATIONS states the local single-user
premise and that these checks are not authentication.

Reading a page with the headless browser could also lose a page it had
already captured: closing the browser ran inside the reading deadline,
and its own 2 second limit raised over the result. Under machine load
the research step then recorded zero sources.

  • The reading deadline now covers launch and capture only. Cleanup runs
    afterwards: 2 seconds for a graceful close, then this call's driver is
    stopped so the browser is forced to exit, observed for up to 2 more
    seconds. A slow close is logged and keeps the captured page.
  • If cleanup cannot be confirmed, the call fails with
    ANONYMOUS_BROWSER_CLEANUP_UNCONFIRMED instead of leaving a process
    behind silently. Read failures, read timeouts and outside cancellation
    behave as before.

Tests pin the local HTTP behavior before and after the checks (57
observations), and 12 parallel runs of the browser tests that failed 11
of 12 before this change pass 12 of 12 with no leftover browser process.
A real browser attack, DNS rebinding and the Vite proxy were not
exercised end to end.

A single-use approval could be consumed twice when the system clock
moved backwards: the current approval revision was chosen by created_at,
so the earlier-stamped CONSUMED revision did not read as current. The
current revision is now chosen by storage order, only the latest
revision of each approval counts, and consumption checks in its
transaction that the expected revision is current and not superseded;
otherwise it is refused with reason_code AUTHORIZATION_ALREADY_CONSUMED
and writes nothing. The normal response is unchanged: a second start
reports the step as protected and creates no new attempt. Past records
are not modified and there is no schema change.

Running database migrations disabled every logger that existed before
the workspace was opened (fileConfig's default), so THOTH diagnostics
stopped reaching the console. Existing loggers now stay enabled; log
levels are unchanged.

Verification: the full local check (Python 5246 tests, web tests, lint,
typecheck, build, architecture and module budget, README smoke) ran on
the origin-check and browser changes; its only failures were four
browser cleanup log assertions caused by the migration logging issue,
which is fixed here. The approval and logging changes were verified with
their focused and existing regression tests.

…k, browser cleanup and migration logging

The local server ran any request it received. A web page open in the
browser could send a text/plain POST to the local port with its own
Origin, or reach it under a foreign Host name (DNS rebinding), and create
projects or cancel running work. Local mode now checks every request
before reading its body or touching the runtime or storage:

- Host must be 127.0.0.1, localhost or [::1] on any port; otherwise
  403 LOCAL_HOST_REJECTED.
- A request that can change state and carries an Origin must come from
  an http Origin on one of those names; otherwise 403
  LOCAL_ORIGIN_REJECTED. Local programs that send no Origin still work.
- /rpc, /rpc/query, /rpc/async and operation cancel require an
  application/json body; otherwise 415 JSON_CONTENT_TYPE_REQUIRED.
- A rejected request writes nothing, the response never echoes the
  received headers, and no CORS headers or settings are added. Hosted
  review mode is unchanged.

The built-in UI, the Vite development proxy and local JSON clients
behave as before. docs/KNOWN_LIMITATIONS states the local single-user
premise and that these checks are not authentication.

Reading a page with the headless browser could also lose a page it had
already captured: closing the browser ran inside the reading deadline,
and its own 2 second limit raised over the result. Under machine load
the research step then recorded zero sources.

- The reading deadline now covers launch and capture only. Cleanup runs
  afterwards: 2 seconds for a graceful close, then this call's driver is
  stopped so the browser is forced to exit, observed for up to 2 more
  seconds. A slow close is logged and keeps the captured page.
- If cleanup cannot be confirmed, the call fails with
  ANONYMOUS_BROWSER_CLEANUP_UNCONFIRMED instead of leaving a process
  behind silently. Read failures, read timeouts and outside cancellation
  behave as before.

Tests pin the local HTTP behavior before and after the checks (57
observations), and 12 parallel runs of the browser tests that failed 11
of 12 before this change pass 12 of 12 with no leftover browser process.
A real browser attack, DNS rebinding and the Vite proxy were not
exercised end to end.

A single-use approval could be consumed twice when the system clock
moved backwards: the current approval revision was chosen by created_at,
so the earlier-stamped CONSUMED revision did not read as current. The
current revision is now chosen by storage order, only the latest
revision of each approval counts, and consumption checks in its
transaction that the expected revision is current and not superseded;
otherwise it is refused with reason_code AUTHORIZATION_ALREADY_CONSUMED
and writes nothing. The normal response is unchanged: a second start
reports the step as protected and creates no new attempt. Past records
are not modified and there is no schema change.

Running database migrations disabled every logger that existed before
the workspace was opened (fileConfig's default), so THOTH diagnostics
stopped reaching the console. Existing loggers now stay enabled; log
levels are unchanged.

Verification: the full local check (Python 5246 tests, web tests, lint,
typecheck, build, architecture and module budget, README smoke) ran on
the origin-check and browser changes; its only failures were four
browser cleanup log assertions caused by the migration logging issue,
which is fixed here. The approval and logging changes were verified with
their focused and existing regression tests.
@hyperdrivebeep
hyperdrivebeep merged commit 699d007 into main Oct 19, 2026
1 check passed
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.

1 participant