Automation
The hard part of giving ChatGPT a shell is the trust boundary
How I separated file, terminal, and browser permissions when connecting AI clients to a local workstation with WebHarness.
Repository snapshot
WebHarness
Experimental public WSL MCP Personal Workstation reference.
- Domains
- 4
- Profiles
- 3
- Browser
- 2 surfaces
Work done
- Dev / Code / Terminal / Local
- browser-fast + browser-devtools
- OAuth + durable human handoff
Getting a model to run bash was not the milestone. The milestone was being able to answer one boring question: what can this session actually do?
A web AI session, an OAuth callback, a Linux user, a repository search tool, and a persistent terminal all have different authority. Treating them as one generic “connected machine” made the system easier to demo and harder to trust.
In 30 seconds
- Problem: remote development access becomes ambiguous when files, native shell execution, repository intelligence, browser state, and long-lived terminals all look like the same permission.
- Decision: split the bridge into explicit trust profiles and capability domains, then make persistence and human handoff first-class boundaries.
- Proof: WebHarness routes one HTTPS/OAuth edge into Dev, Code, Terminal, and Local domains; Local brokers compact browser interaction and full DevTools diagnostics without flattening them into the same permission surface.
Connectivity was the easy problem
The first useful prototype only needed a path:
ChatGPT -> HTTPS -> MCP -> WSL
That proves connectivity. It says almost nothing about authority.
Can the session read one workspace or the whole Linux account? Can it run arbitrary Bash? Can it control authenticated browser state? Can it start a process that outlives the current MCP request? Can it hand an interactive terminal to a human without opening a second shell? Can a constrained client reach the same tools without creating a second permission model?
Those questions became the architecture.
I stopped treating every tool as equivalent
WebHarness now has three direct domains plus one stable Local broker domain:
Dev files, guarded edits, Bash, waits, host actions
Code repository-rooted search, context, symbols
Terminal persistent interactive PTYs + human handoff
Local discovery / schema / call
-> browser-fast
-> browser-devtools
That split sounds simple, but it removes a lot of ambiguity.
A file edit and a terminal session do not have the same lifecycle. A semantic code search and unrestricted Bash do not have the same trust boundary. Browser control can reach authenticated browser state, so it should not be hidden inside a generic shell tool. A readiness wait should not consume terminal output just because both involve a running process.
Local keeps that browser surface small. The model sees three broker actions for discovery, schema lookup, and calls. Behind them, browser-fast owns compact routine interaction and browser-devtools owns deep Chrome diagnostics. Windows uses a dedicated MCP Chrome profile; Linux/WSLg can use managed Chrome or Clearcote. The caller can choose the resource target without changing the outer authority model.
Once those differences were explicit, routing became easier to reason about. The question for an agent changed from “which of these many tools could do this?” to “which boundary owns this kind of work?”
Trust profiles made the dangerous part visible
A public workstation reference should not silently grant the same authority as my personal WSL setup.
The restricted profile keeps ordinary file operations workspace-bounded. trusted-dev deliberately adds native Bash for a dedicated development host. The maintained personal composition can expose broader WSL paths, code intelligence, durable terminals, Local/Browser, and optional Windows host sleep.
The names matter less than the contract: more convenience should require a more explicit grant of authority.
That also changed setup. Preparing the personal runtime and enabling persistent startup are separate actions. A bootstrap can qualify and render the environment without silently deciding that services should come back forever.
Long-running work needed its own lifetime
MCP calls are request-shaped. Development work often is not.
A server, build, REPL, or interactive TUI may need to survive a provider restart or a later human handoff. Keeping those processes inside the lifetime of one connector call made them fragile.
The bridge instead gives Terminal its own durable PTY lifetime under tmux. The model can start work, observe the same process later, and yield the exact terminal when a human needs to type a password or make an interactive decision.
That removes a bad escape hatch: opening some second, hidden shell because the original one became inconvenient.
A large browser catalog did not belong in every session
Browser automation was the clearest example of why the Local broker exists. Routine clicking and form work need a compact interaction contract; debugging a browser failure may need network logs, console output, performance traces, Lighthouse, heap inspection, or screenshots.
Publishing both surfaces directly would make every client carry a large, changing catalog. WebHarness instead keeps three Local metatools stable and discovers the selected downstream schema only when it is needed.
That is also a trust boundary: everything admitted behind one Local broker legitimately shares that authorization domain. A capability that needs different authority should get a different broker or scope rather than hiding behind another tool name.
A public reference needs a qualified environment
WebHarness is intentionally specific about what is reproduced: the maintained reference targets WSL2 + Ubuntu + x86_64 + Node.js 24+ + systemd user services, with WSLg for headed Linux browser capability.
That specificity is useful. It separates “this architecture works and is qualified here” from “this project supports every Linux, macOS, and Windows environment.” The public repository can then document the actual constraints instead of turning my personal machine into an implicit universal default.
The useful metric is clarity of authority
I could describe WebHarness as Cloudflare + OAuth + MCP + WSL. That stack list is accurate and not very informative.
The project became more useful when I could describe the failure modes instead:
- a bounded profile should not quietly inherit unrestricted shell access;
- a long-running terminal should not disappear with a short request;
- a human handoff should keep the same PTY;
- code intelligence should stay rooted in a real repository;
- browser control should require its own explicit scope and resource-local owner;
- high-cardinality downstream tools should not inflate every outer client session;
- public installation should not silently copy private-machine authority.
The tunnel connects the system. Those boundaries make it understandable.