Skip to content

Browser components (B1): manifests, installer, egress child, fail-closed runner, router skeleton - #501

Open
SecureCloudGroup wants to merge 1 commit into
mainfrom
round20/browsers-core
Open

SecureCloudGroup wants to merge 1 commit into
mainfrom
round20/browsers-core

Conversation

@SecureCloudGroup

Copy link
Copy Markdown
Owner

Step B1 of the Round 20 plan (ruled 2026-10-09): a standalone, replaceable, hash-pinned browser component around the Obscura headless browser. Nothing renders in production yet: every real platform reports unavailable (sandbox_pending, userns_pending, docker_no_sidecar, platform) and the runner never starts an engine without a wall. The OS walls land in B2 from the measured spike (5-rule deny-default Seatbelt profile on macOS, the Docker sidecar relay, Linux user namespaces proven in CI).

Package app/smartbrain_3000/browsers/

  • manifest.py + engines/obscura.json, engines/obscura-stealth.json: strict manifests (v0.2.4 release assets with sha256 + size per platform, per-member pins, argv templates with a closed placeholder set, limits, identity pool 0–7, min glibc) and an always-forbidden token floor no manifest can lower (--allow-private-network, --host, --eval, --quiet, serve/mcp/scrape, --stealth outside the stealth engine, any --v8-flags but the heap form; --v8-flags must precede fetch).
  • release.py + install.py: pinned download with a single allowlisted redirect, streamed hashing, abort at size + 1, sha256 before unpack, safe untar (top-level regular files only, pinned per member, no links/devices/dirs, member and byte caps), .partial → INSTALLED.json → atomic rename, prune, self-test hook, phases, SMARTBRAIN_NO_BROWSER=1, hash re-verified on every launch.
  • jail_egress.py + egress.py: the key-less egress child per the P3 design: CONNECT only, hostnames only (IP literals and numeric labels refused), port 443 or the main host's explicit port, site policy own|open|sealed by registrable domain, resolution on a bounded thread with every answer judged by netguard's unchanged _is_unsafe, dial the validated address, caps (tunnels, concurrency, sites, bytes, idle), closed-key census on stdout, exits on stdin EOF.
  • cmdline.py, proc.py, runner.py: https-only IDNA-normalized URLs, the argv builder re-walked against the forbidden list, a closed environment (home/tmp/XDG inside the run dir, zone, identity profile, no proxy variables), spawn under jail hygiene (session, rlimits CPU/CORE/NOFILE/NPROC, PDEATHSIG on Linux), a watchdog that treats any child process or failed wall check as confinement, a tree RSS bound, stderr classified (heap kill, script watchdog, final URL), tripwires (main-document CONNECT, final URL on policy, output cap), one engine at a time, run dir removed on every path.
  • walls.py: the fail-closed gate; a test-only null wall that accepts only the fake-engine fixture.
  • router.py: static tier first, code-computed need signals (js_shell, blocked, want_miss), escalation only on a plain 403 and only when the caller allows a render, a circuit breaker per engine, browse_both for discovery.
  • Status: /api/status/overview gains browsers[] and storage.browser_bytes, readable while locked; doctor lists engines, leftovers and engine processes.
  • Docs: ni-format.md §35; CHANGELOG Unreleased.

Gates (Docker smartbrain_3000:dev): ruff clean; browser + status + data tests 303 passed, 8 skipped (real-engine rows); installer 69 passed; with the pinned linux-aarch64 binary the 8 real-engine rows pass; full suite 4,375 passed, 26 skipped. Ten safety checks were mutation-tested one at a time. The lead ran 50 constructed probes (URL and argv injection, CONNECT parsing, resolver judging, tar members, the walls gate): 50/50.

netguard._MAX_BYTES carries the same 8 MB value as #500 so the html-cap invariant holds whichever merges first.

Open for B2: the real walls and the SG canary legs in CI. Open for B3: flow wiring, sealing, consent, the render worker, XHR promotion, the Status card, a challenge FetchError kind, a full public-suffix list, a THIRD_PARTY_LICENSES row for Obscura.

… egress child, fail-closed render runner, router skeleton, status and doctor
assert got["code"] == "hash"
assert os.listdir(root / "obscura") == []
row = install.status_row(m)
assert row["phase"] == "error" and "github.com" in row["error"] and "://" not in row["error"]
assert "/" not in sentence and "{" not in sentence, code # no URL, path or template
if code in ("network", "timeout", "truncated", "server", "refused", "redirect",
"oversize", "hash", "unsafe_tar"):
assert "github.com" in sentence, code
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.

2 participants