Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/integrations/ego-source-reader.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ may use the existing browser's session; origin configuration does not prove that
every page on that origin is public. Follow the browser's installed skill for
TaskSpace/Page ownership and user consent.

An origin list is insufficient for a public community host that must not reuse
personal cookies. Its isolated native Chat can explicitly enable the separate
[anonymous static public reader](public-source-reader.md), retaining its shell,
history and private-MCP boundary. Dynamic signed-in pages remain outside that
provider's coverage; do not silently substitute the personal Ego reader.

For example, append a uniquely named server to the **actual execution host's**
MCP configuration, preserving its existing entries and authentication:

Expand Down
66 changes: 66 additions & 0 deletions docs/integrations/public-source-reader.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Anonymous public evidence for isolated Chat

An isolated project Chat can read static public articles and actual image pixels
without inheriting personal MCP servers, browser cookies or account history.
The optional bundled `loopx.extensions.public_source_reader` provider performs
anonymous HTTPS GETs only. It owns external IO, not Session, grant, material,
delegation or publication authority. It is a fallback for static sources;
[Ego's rendered reader](ego-source-reader.md) remains useful on an explicitly
authorized personal host when a page needs browser rendering.

Install the optional dependency in the **existing Chat host's** environment:

```sh
python -m pip install 'loopx[public-source-reader]'
```

On that trusted host, set `LOOPX_CHAT_PUBLIC_SOURCE_READ=on` and restart the idle
service through its existing lifecycle. `off` is the default and rollback.
Only native `workspace_only` Chat processes consume this setting; ordinary
personal Chat keeps its existing MCP configuration. Existing Sessions resume
in their original native store. Workspace grants, shell network restrictions,
model authentication, audience and history do not change.

The host first disables all effective personal/project MCP entries, then admits
one provider with a fixed interpreter and current-release file, minimal environment and no caller
credentials. Isolated Python excludes the writable workspace and `PYTHONPATH`
from provider module lookup. Source-only and bundled releases use their own
provider file for both MCP startup and observation workers; they do not need a
second site-installed LoopX package, and cannot silently use an older one. The
optional dependencies must still be installed in that host interpreter. The
reserved name `loopx_public_source_read` must not appear in a
lower native configuration layer: a collision fails before model dispatch rather
than merging its command, environment or headers. Project files and model input
cannot enable this provider. Missing dependencies are an installation failure,
not authority to enable a personal reader or widen the shell sandbox.

`read_public_url(url)` returns static text, image indices, observed URL, source
digest and explicit truncation/coverage. `read_public_image(url, index)` reads
that article again and returns one PNG image-content block with its source
digest. A changed article may have changed image indices; verify the returned
digest and URL. Text/alt labels do not prove pixels were read, and static HTML
does not prove a dynamic article or a verification wall was fully read. The
Agent must report missing coverage and inspect rendered labels and arrows.

The provider sends no cookies, account headers, proxy settings or caller-defined
headers. Every destination and redirect must resolve entirely to globally
routable addresses; the checked address is pinned to the TLS connection while
the original hostname is used for certificate validation. File URLs, credentials
in URLs, nonstandard ports, local/private/link-local addresses and mixed DNS
answers are rejected. The whole observation, including DNS and rendering, runs
in a minimal-environment worker with a 30-second deadline. Responses are bounded
to 4 MB, text to 100,000 characters, image inventories to 128 and image edges to
4,096 pixels. Errors return no private interpreter paths or resolver output.

PNG/JPEG/WebP and self-contained SVG images are supported. SVG geometry and
aspect ratio are preserved; active elements, external references and stylesheet
blocks are rejected. The bundled [resvg renderer](https://resvg-py.readthedocs.io/en/latest/api.html)
uses only standard OS font directories, skipping the account's font database;
no additional native Cairo installation is required. This provider never reads
a local source file supplied by the caller or navigates an authenticated browser.

Qualification must separately demonstrate native Session tool/image consumption,
an original-topic answer with actual provider readback, outside/private-resource
rejection, unchanged grants/history and source completeness. A successful host
MCP probe or unit suite alone does not qualify the community Bot's golden pack.
Keep admission on hold while mandatory cases remain failed, blocked or untested.
30 changes: 30 additions & 0 deletions loopx/capabilities/native_chat/codex_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"""
from __future__ import annotations

import importlib.util
import os
import sys
from pathlib import Path
from typing import Any, Mapping

Expand Down Expand Up @@ -97,3 +99,31 @@ def disable_mcp_servers(config: Mapping[str, Any], host_config: Mapping[str, Any
return {**host_config, "mcp_servers": {
name: {"enabled": False} for name in effective.keys() | requested.keys()
}}


def public_source_reader(config: Mapping[str, Any], host_config: Mapping[str, Any]) -> dict[str, Any]:
"""Admit one operator-selected anonymous provider after private MCP removal.

This is a host IO adapter, not a project-controlled grant. No inherited
server fields, browser state, credentials or private execution permissions.
"""
setting = os.environ.get("LOOPX_CHAT_PUBLIC_SOURCE_READ", "off")
if setting == "off":
return dict(host_config)
if setting != "on":
raise ValueError("LOOPX_CHAT_PUBLIC_SOURCE_READ must be on or off")
if any(importlib.util.find_spec(module) is None for module in ("mcp", "resvg_py", "PIL", "defusedxml")):
raise ValueError("Install loopx[public-source-reader] in the existing Chat host environment")
name = "loopx_public_source_read"
if name in config.get("mcp_servers", {}) or name in host_config.get("mcp_servers", {}):
raise ValueError("Public source reader conflicts with native MCP configuration")
from ...extensions import public_source_reader as reader
# Managed releases put their source on the host's sys.path, not necessarily
# in site-packages. Bind this release's file; -m could fail or select an
# older site-installed LoopX once the child discards the host's sys.path.
return {**host_config, "mcp_servers": {**host_config.get("mcp_servers", {}), name: {
"enabled": True, "command": str(Path(sys.executable).absolute()),
"args": ["-I", str(Path(reader.__file__).resolve())],
"env": {"PATH": os.defpath}, "env_vars": [],
"startup_timeout_sec": 30, "tool_timeout_sec": 40,
}}}
4 changes: 4 additions & 0 deletions loopx/chat_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,10 @@ def start(
raise session._runtime_error("Codex returned an invalid project configuration.")
if permissions_profile:
host_config = codex_context.disable_mcp_servers(effective, host_config or {})
try:
host_config = codex_context.public_source_reader(effective, host_config)
except ValueError as exc:
raise session._runtime_error(str(exc)) from None
if session._host_model_auth is not None:
# Use public native transport defaults, not the
# account's configuration or environment. Reject a
Expand Down
Loading
Loading