66 Commits

Author SHA1 Message Date
SGavrl
f495cee844 perf(storage): index entries and reuse checked document origins 2026-10-13 19:11:30 +00:00
SGavrl
677f6d2fbb fix(storage): retry profile writes and preserve browser strings 2026-10-12 22:40:18 +00:00
SGavrl
a253ccbcc6 Merge commit '8edfe480133b383f70e17f9d4ea55e3592cdfd24' into repair/1223-web-storage 2026-10-12 21:51:42 +00:00
SGavrl
8edfe48013 Merge pull request #1221 from yvv4git/fix/rod-cdp-compat
fix(cdp): accept go-rod's objectId arguments, getOuterHTML and Page.close
2026-10-12 23:49:58 +02:00
SGavrl
361f15d68d fix(cdp): cancel pending loads and honor beforeunload 2026-10-12 21:45:29 +00:00
SGavrl
73358fdb40 fix(browser): preserve response bodies during runtime suspension 2026-10-11 15:37:17 +00:00
SGavrl
17fe750e62 Merge pull request #1234 from Yi-111-a/fix/plain-text-document-for-non-html
fix(browser): build a plain-text document for non-HTML text responses
2026-10-10 20:02:14 +02:00
SGavrl
335f92989d fix(browser): preserve plain-text document metadata and newlines 2026-10-10 17:12:38 +00:00
SGavrl
c2dae89f64 Merge branch 'main' into fix/mcp-font-directory 2026-10-10 18:52:49 +02:00
SGavrl
816a60aed1 Merge branch 'main' into worker-navigation-wait-until 2026-10-10 17:30:14 +02:00
Seamile
5171717e71 fix(mcp): accept explicit font directories before startup 2026-10-10 12:05:56 +08:00
opencode
f661c2e0f6 fix(js): keep Web Storage across navigation and persist it, and back IndexedDB
Web Storage was a plain object inside the V8 realm, and `Page::init_js` builds a
new realm on every navigation, so every entry was dropped by the next document.
localStorage now lives on the BrowserContext and sessionStorage on the Page, both
in Rust keyed by origin, with per-origin and total quotas.

Under `--storage-dir`, localStorage is written per origin after a document's
scripts run and on shutdown, and read back when a context is created. Only
origins that changed since the last successful write are touched, a clear or a
last-key removal unlinks the file instead of letting the next load resurrect it,
and a failed write leaves the store dirty so it is retried. sessionStorage stays
in memory: it is per tab, like a real browser, but it now survives navigation
and CDP target switching within the process.

`obscura mcp` never passed `--storage-dir` to its BrowserContext, so that path
persisted nothing, cookies included. It reads the profile from
OBSCURA_STORAGE_DIR, set by the CLI from the global flag, and flushes on exit.

IndexedDB was a shim that handed every object store its own Map and never fired
onupgradeneeded, so a schema-created database was unreachable and the first read
threw. Records and the schema live in an origin-keyed store on the context, and
the shim implements the spec shape: versionchange transactions, real
objectStoreNames, NotFoundError, asynchronous request settlement, transactions
that complete on a macrotask so chained requests work, key paths, generators,
indexes and cursors. Values round-trip through JSON.

Fixes #678.
2026-10-09 15:44:25 +05:30
vvinayakkk
77cf630349 Merge upstream/main and retain stealth network regressions 2026-10-05 14:01:27 +05:30
vvinayakkk
215ef5136c Merge upstream/main and preserve worker readiness options 2026-10-05 14:01:22 +05:30
SGavrl
590f79e1a1 Merge pull request #1193 from h4ckf0r0day/fix/fetch-abort-signal-main-20261002
fix(fetch): cancel requests and body readers with AbortSignal
2026-10-04 15:00:24 +02:00
SGavrl
b220aa7f47 Merge main and preserve fetch destinations during cancellation 2026-10-04 12:15:39 +00:00
SGavrl
0420933914 Merge remote-tracking branch 'github/main' into integrate/pr-1183-main-20261004
# Conflicts:
#	crates/obscura-js/js/bootstrap.js
2026-10-04 11:37:19 +00:00
SGavrl
765af54c63 Merge pull request #1129 from vvinayakkk/local-network-error-hints
fix(cli): explain local network access denials
2026-10-04 01:49:27 +02:00
SGavrl
9f3c6e9d05 Merge pull request #1119 from vvinayakkk/worker-navigation-http-status
fix(worker): report final HTTP status on navigation
2026-10-04 01:49:20 +02:00
SGavrl
87369e4851 Merge pull request #1128 from vvinayakkk/cdp-network-observer-events
fix(cdp): deliver target network events to observer sessions
2026-10-04 01:48:45 +02:00
SGavrl
0155807ed4 fix(fetch): cancel requests and body readers with AbortSignal 2026-10-03 23:25:25 +00:00
SGavrl
373bc8b158 fix(js): report unsupported editing commands truthfully 2026-10-03 23:25:22 +00:00
SGavrl
60107d1aa6 fix(js): record navigation lifecycle timestamps 2026-10-02 19:57:23 +00:00
SGavrl
e4b61386b1 feat(js): implement User Timing entries and observers 2026-10-02 19:45:31 +00:00
SGavrl
3e86f04206 Merge main and correct SVG interface ancestry 2026-10-02 12:24:50 +00:00
SGavrl
8bb46be326 fix(cdp): report native V8 heap usage and collect garbage 2026-09-30 23:16:04 +00:00
vvinayakkk
a94d5717f8 fix(stealth): record script network events and bodies 2026-10-01 02:04:17 +05:30
vvinayakkk
9a6dd862dc fix(cli): explain local network access denials 2026-10-01 02:03:14 +05:30
vvinayakkk
b8586dec97 fix(cdp): deliver target network events to observer sessions 2026-10-01 01:15:41 +05:30
vvinayakkk
ec0186bc1d feat(worker): accept per-navigation readiness modes 2026-10-01 00:19:57 +05:30
vvinayakkk
afb0d613ec Keep blocked requests without a fabricated HTTP status 2026-09-30 23:58:44 +05:30
vvinayakkk
11d6ea8e1c Expose final HTTP status in worker navigation responses 2026-09-30 23:48:12 +05:30
SGavrl
0ceac28d35 Merge pull request #1115 from h4ckf0r0day/docs/v8-memory-flags
docs: correct effective V8 heap flag behavior
2026-09-30 16:07:53 +02:00
SGavrl
1eba7ef8ff Correct effective V8 heap flag documentation 2026-09-30 14:04:12 +00:00
SGavrl
e31c5ddbfb Document scoped builds and snapshot recovery 2026-09-30 14:04:12 +00:00
SGavrl
04418a5585 fix: harden security boundaries 2026-09-19 15:09:42 +00:00
SGavrl
d745fb4512 Merge pull request #1010 from lisa0314/fix/995-stealth-tracker-opt-out
fix(net): allow disabling stealth tracker blocking
2026-09-19 11:36:26 +00:00
lisa0314
9ced49602e fix(net): allow disabling stealth tracker blocking 2026-09-18 15:17:23 +08:00
michabbb
99647b44e2 fix(render): keep DOM-only page screenshots network-free 2026-09-16 19:21:56 +02:00
SGavrl
8819c284cb Merge pull request #966 from h4ckf0r0day/fix/879-font-loading
fix(render): cache font databases across documents
2026-09-12 22:31:41 +00:00
SGavrl
343fdc741b fix(render): cache font databases across documents 2026-09-12 22:19:52 +00:00
SGavrl
d6c9ef537c fix(release): shrink binaries and expose plugin interfaces 2026-09-12 21:04:47 +00:00
SGavrl
727cc46d56 Merge pull request #859 from mrummuka/fix/docker-nonroot
fix(docker): run the container as uid 65532 instead of root
2026-09-06 18:46:22 +00:00
kws
88d5cb0651 fix(js): preserve worker message handlers and state
Run each worker source once in its retained scope so bare onmessage assignments do not mutate the page and message closures retain state.

Fixes #867.
2026-09-06 20:21:12 +09:00
mrummuka
20b4a34e0d fix(docker): run the container as uid 65532 instead of root
The runtime image was `gcr.io/distroless/cc-debian12` with no `USER`, so the
process ran as root. Obscura executes untrusted page JavaScript in-process
through V8, so a V8 exploit lands with the process's privileges, and there is
no reason for those to be root's -- the image binds an unprivileged port, reads
the CA bundle, and writes only to the storage dir and a temp dir.

Switch to the `:nonroot` tag.

Verified against a locally built image rather than assumed:

    Config.User                     -> 65532
    --version                       -> obscura 0.1.0
    fetch https://example.com       -> succeeds, so the CA bundle is readable
    fetch --storage-dir /tmp/obs    -> succeeds

The tag is deliberately not pinned to a digest. distroless is rebuilt often
with base-layer security patches and tracking the tag picks those up; a digest
pin freezes them until someone bumps it, which for a *base* image trades a real
ongoing risk for a theoretical one. Reasoning recorded in the Dockerfile so a
reviewer can disagree knowingly.

Docs: the three `docker run` examples already published to host loopback
(`-p 127.0.0.1:9222:9222`), so that part needed no change. What did need
documenting is the consequence of dropping root -- **a mounted `--storage-dir`
must be writable by uid 65532** -- because that is the one thing this change
breaks, and it breaks quietly. Verified: with an unwritable storage dir the run
completes, exits 0, and prints no warning; the cookie jar simply never
persists. `save_to_file` errors are swallowed. Noted in the docs with a
`install -d -o 65532` line, and worth a separate silent-failure fix.

The in-container `--host 0.0.0.0` is left alone and now explains itself: a
container-loopback bind is unreachable through `-p`, so it is required for the
published port to work at all. It is not a licence to expose the port.
2026-09-05 20:20:24 +03:00
SGavrl
7060f97e26 Merge pull request #666 from aech/fix/navigation-chain-limit
The limit for client-initiated navigations becomes adjustable and is named after what it counts
2026-08-31 09:54:26 +00:00
aleekaz
d415fe68f4 docs: drop the WebGL/GPU renderer claim from the stealth profile section
The profile section promises that each profile keeps "the WebGL/GPU renderer
internally consistent" and that Windows profiles report ANGLE Direct3D11 and
macOS profiles ANGLE Metal. That was true when the sentence was written in
#288, against the WebGL shim that existed then.

29d0bce ("render: expose WebGL as unavailable without backend") made
`getContext('webgl')`, `'experimental-webgl'` and `'webgl2'` return `null`,
deliberately, with an in-source comment explaining why. So there is no
renderer string for a page to read, and nothing for the profile to be
consistent with.

Checked on main (f449e6f) and on the v0.2.0 stealth binary:

    obscura fetch "data:text/html,<p>x" \
      --eval "String(document.createElement('canvas').getContext('webgl'))"
    null

Doc-only. The `gpu` and `gpuVendor` fields are still assigned into the
fingerprint object in bootstrap.js and are not read anywhere; whether to
remove them or to wire them back up when WebGL lands is a separate call, so
this change does not touch them.
2026-08-26 23:24:51 +03:00
aech
7aec2e8d66 fix(browser): navigation chain limit adjustable and correctly named
The loop in navigate_with_wait_post_inner limits how many documents
a navigation chain may load. It counts navigations the page itself
triggers after each load: assignments to location and form
submissions, which CDP's Page.ClientNavigationReason calls
scriptInitiated and formSubmission*. HTTP 3xx redirects never reach
this loop. They are followed one layer down, in obscura-net, with
their own limit and their own error.

The message still read "Too many redirects (limit 10)", so a chain
of client-initiated navigations looked like a redirect storm. This
name sends an investigation to the wrong layer. It points at the
redirect chain while the cause is a JavaScript loop.

The limit was also a function-local constant with no way to raise
it. The low default is right and stays 10, but an endpoint that
chains longer than nine navigations for good reasons was simply
unreachable. It is now a per-page value with a fallback to the
environment, in the same form navigation_timeout already has:

  page.set_navigation_chain_limit(20)   programmatic, takes precedence
  OBSCURA_NAV_CHAIN_LIMIT=20            in operation
  neither set                           unchanged default 10

The number counts documents, the first navigation included. So 10
allows the requested document and nine client-initiated navigations
on top. The error message names this unit. The number is now
something an operator acts on, and "limit 10" next to the word
"navigations" would read as ten navigations and send them exactly
one too low.

Values below 1 are raised to 1 on both paths. A zero would let the
loop run without a single iteration, the page would report success
and have loaded nothing. Only an unreadable value from the
environment falls back to the default. A 0 therefore means the same
on both paths: load the first document and do not chain further.
2026-08-23 20:56:40 +02:00
Suydev
598a6d55d1 feat(tools): add live-view tool with target-following and adaptive pacing
Provide a standalone viewer at tools/live-view.mjs that polls
Page.captureScreenshot with connection-aware target discovery.
Unlike the inline doc example, it follows the agent across pages:
it discovers targets through the global HTTP list and re-attaches
whenever the agent opens a new page. Capture pacing is adaptive:
fast while frames change, idle back-off otherwise, so watching
feels continuous without busy looping.

Part of #691.
2026-08-23 13:35:58 +04:00
Suydev
76f96225b5 docs(mcp): add guide for watching agent sessions live
Agents driving obscura through MCP, Puppeteer, or Playwright are
invisible because the browser is headless. Add a guide showing how
to stream what the browser sees to a local tab with a small
dependency-free Node script (Node 21+ native WebSocket).

The script attaches through Target.attachToTarget and polls
Page.captureScreenshot twice a second over Server-Sent Events.
Polling is deliberate: screencast frames stream only to the session
that drives the page, so a passive viewer goes quiet once an agent
session takes over. The guide documents that plus two more
behaviors found while building it: every screencast frame needs
screencastFrameAck or delivery stalls, and a bare page socket
refuses screencast commands with 503.

Fixes #691.
2026-08-22 21:58:55 +05:30