fix(harness): ship coherent vendor harness cluster

Rebuild PR C to ship the vendor/photocraft harness as a coherent unit
(vendor implementation + vendor tests) instead of a per-file ahead/behind
mix that produced broken imports and 6+ test failures.

- Flip 36 harness impl/route/test files back to vendor (the per-file
  line-set policy misclassified user-rewritten files as 'behind').
- Flip core/models.py + src/tool_approval_scopes.py to vendor: vendor's
  harness (authority.py, turn_contract.py, clean_agent_preview.py) depends
  on vendor's older approval-scope API; upstream added
  CHAT_SESSION_APPROVAL_SIGNATURE_FIELD that vendor's core/models.py lacks.
- Add src/shutdown.py (new vendor infra) required by routes/chat_routes.py.
- Keep src/agent_tools/__init__.py at upstream (editor_tools registration
  lives in PR B).

Upstream-only security divergence preserved in C_upstream_delta.patch and
flagged for integration-time reconciliation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
pewdiepie-archdaemon
2026-10-13 13:59:04 +00:00
co-authored by Claude Opus 5.5
parent 364229f2dc
commit 9bdea753ae
39 changed files with 804 additions and 3076 deletions
+39 -308
View File
@@ -15,7 +15,6 @@ import logging
import os
import pathlib
import re
import stat
import secrets
import sys
import time
@@ -37,7 +36,6 @@ from src.tool_policy import ToolPolicy
from src.client_tool_contract import TUI_ROUTED_BRIDGE_TOOL_NAMES
from src.constants import (
DATA_DIR,
AGENT_WORKSPACE_DIR,
MAX_DIFF_LINES,
MAX_OUTPUT_CHARS,
MAX_READ_CHARS,
@@ -71,11 +69,11 @@ _MISSING_TOOL_SECURITY_CONTEXT = _MissingToolSecurityContext()
NO_TOOL_SECURITY_CONTEXT = _NoToolSecurityContext()
# Persistent working directory for agent subprocesses.
# Resolves to <repo_root>/data/agent_workspace, inside the bind-mounted volume
# in Docker (/app/data), so files survive a rebuild as before. The subdirectory
# rather than data/ itself keeps agent scratch files and dotfiles out of the
# directory holding the session store and the auth database.
_AGENT_WORKDIR = AGENT_WORKSPACE_DIR
# Resolves to <repo_root>/data, which is the bind-mounted volume in Docker
# (/app/data) and the local data directory for manual installs.
# Using this as cwd and HOME prevents the agent from silently creating files
# in ephemeral container layers that are lost on the next rebuild.
_AGENT_WORKDIR = DATA_DIR
ExecutionBridgeHandler = Callable[
@@ -135,12 +133,6 @@ def get_active_execution_bridge() -> AgentExecutionBridge | None:
return _active_execution_bridge.get()
def _tui_host_bridge_endpoint_url(url: str, path: str) -> str:
"""Replace a validated bridge URL's path without changing its authority."""
from src.agent_tools.subprocess_tools import host_shell_bridge_endpoint_url
return host_shell_bridge_endpoint_url(url, path)
def _tui_host_bridge_patch_url(
client_runtime_context: Optional[Dict[str, Any]],
) -> tuple[str, str] | None:
@@ -161,7 +153,11 @@ def _tui_host_bridge_patch_url(
from src.agent_tools.subprocess_tools import is_host_shell_bridge_url_allowed
if not is_host_shell_bridge_url_allowed(url):
return None
return _tui_host_bridge_endpoint_url(url, "/patch"), token
if url.endswith("/run"):
url = url[:-4] + "/patch"
elif not url.endswith("/patch"):
url += "/patch"
return url, token
async def _apply_patch_via_tui_host_bridge(
@@ -177,7 +173,7 @@ async def _apply_patch_via_tui_host_bridge(
import httpx
timeout = httpx.Timeout(125.0, connect=5.0, write=10.0, pool=5.0)
async with httpx.AsyncClient(timeout=timeout, trust_env=False) as client:
async with httpx.AsyncClient(timeout=timeout) as client:
response = await client.post(
url,
headers={"x-odysseus-tui-bridge-token": token},
@@ -237,7 +233,7 @@ async def _bridge_post(bridge: Dict, path: str, payload: Dict, *, timeout_s: flo
"error": f"{err_prefix}: invalid TUI host bridge",
"exit_code": 1,
}
target = _tui_host_bridge_endpoint_url(url, path)
base = url.rsplit("/", 1)[0] if url.endswith(("/run", "/read", "/write")) else url.rstrip("/")
try:
import httpx
timeout = httpx.Timeout(
@@ -246,9 +242,9 @@ async def _bridge_post(bridge: Dict, path: str, payload: Dict, *, timeout_s: flo
write=10.0,
pool=5.0,
)
async with httpx.AsyncClient(timeout=timeout, trust_env=False) as client:
async with httpx.AsyncClient(timeout=timeout) as client:
response = await client.post(
target,
f"{base}{path}",
headers={"x-odysseus-tui-bridge-token": token},
json=payload,
)
@@ -304,13 +300,13 @@ async def _cancel_bridge_request(bridge: Dict, request_id: str) -> None:
from src.agent_tools.subprocess_tools import is_host_shell_bridge_url_allowed
if not token or not is_host_shell_bridge_url_allowed(url):
return
target = _tui_host_bridge_endpoint_url(url, "/cancel")
base = url.rsplit("/", 1)[0]
try:
import httpx
timeout = httpx.Timeout(5.0, connect=2.0, write=2.0, pool=2.0)
async with httpx.AsyncClient(timeout=timeout, trust_env=False) as client:
async with httpx.AsyncClient(timeout=timeout) as client:
await client.post(
target,
f"{base}/cancel",
headers={"x-odysseus-tui-bridge-token": token},
json={"request_id": request_id},
)
@@ -733,7 +729,7 @@ async def _route_tool_via_bridge(tool: str, content: str, session_id: Optional[s
path = path_value.strip() if isinstance(path_value, str) else ""
if "\n" in path or "\r" in path:
path = ""
body_value = args.get("content")
body_value = args.get("content", "")
if not isinstance(body_value, str):
return "write_file: invalid arguments", {
"error": "write_file: content must be a string",
@@ -743,7 +739,7 @@ async def _route_tool_via_bridge(tool: str, content: str, session_id: Optional[s
else:
path, _, body = content.partition("\n")
path = path.strip()
if not path or path.endswith(("/", "\\")):
if not path:
return "write_file: invalid arguments", {
"error": "write_file: path is required",
"exit_code": 1,
@@ -751,17 +747,6 @@ async def _route_tool_via_bridge(tool: str, content: str, session_id: Optional[s
rejected = _text_write_to_binary_artifact_result(content)
if rejected is not None:
return rejected
from src.agent_tools.filesystem_tools import _parse_write_intent, _unwrap_fenced_source_body
try:
_, original_body, section, explicit_clear = _parse_write_intent(content)
except ValueError as error:
return "write_file: invalid arguments", {"error": str(error), "exit_code": 1}
body = _unwrap_fenced_source_body(body, path)
if not section or (not body.strip() and not explicit_clear):
return "write_file: ambiguous content", {
"error": "write_file: bridge cannot safely enforce implicit empty content; use explicit JSON string content",
"exit_code": 1,
}
return f"write_file: {path[:80]}", await _bridge_post(
bridge,
"/write",
@@ -788,15 +773,10 @@ async def _route_tool_via_bridge(tool: str, content: str, session_id: Optional[s
# 1. Sensitive-subpath deny list — checked FIRST. Blocks .ssh,
# .gnupg, shell rc files, token/env files even if the root above
# them is on the allowlist.
# 2. Application-state deny (_is_app_state_path) - DATA_DIR holds the
# session store, auth database, app key and settings, so only
# _agent_readable_data_subdirs() is readable inside it.
# 3. Allowlist - only the directories the agent legitimately needs
# (its data/ workspace, user content, system tmp). $HOME is NOT on
# the default list.
# 4. Opt-in extra roots - admin can add broader roots via the
# "tool_path_extra_roots" setting. These cannot re-open DATA_DIR;
# rule 2 is independent of which root a path arrived through.
# 2. Allowlist — only the directories the agent legitimately needs
# (project data/, system tmp). $HOME is NOT on the default list.
# 3. Opt-in extra roots — admin can add broader roots via the
# "tool_path_extra_roots" setting (list of path strings).
# ---------------------------------------------------------------------------
# Compatibility exports: the same deny predicate protects tool access and
@@ -807,191 +787,6 @@ from src.agent_runtime.path_policy import (
)
def _path_within(resolved: str, root: str) -> bool:
"""True when *resolved* is *root* itself or sits underneath it.
Use the platform's path-case rules. This helper participates in allow
decisions, so unconditional case-folding would let a distinct ``/DATA``
tree masquerade as a descendant of ``/data`` on case-sensitive systems.
"""
resolved, root = os.path.normcase(resolved), os.path.normcase(root)
if resolved == root:
return True
try:
if os.path.commonpath([resolved, root]) == root:
return True
except ValueError:
return False
# normcase is intentionally conservative about assumptions (notably on
# POSIX), so consult the filesystem when paths exist. This recognizes a
# case alias on a case-insensitive volume without treating distinct
# case-sensitive paths as the same allow root.
if os.path.exists(root):
candidate = resolved
while True:
try:
if os.path.exists(candidate) and os.path.samefile(candidate, root):
return True
except OSError:
pass
parent = os.path.dirname(candidate)
if parent == candidate:
break
candidate = parent
return False
def _path_within_conservative(resolved: str, root: str) -> bool:
"""Containment for deny decisions, folding case to fail closed."""
resolved, root = resolved.casefold(), root.casefold()
if resolved == root:
return True
try:
return os.path.commonpath([resolved, root]) == root
except ValueError:
return False
def _agent_readable_data_subdirs() -> tuple[str, ...]:
"""The only parts of DATA_DIR the agent's file tools may reach.
The agent's own scratch folder, plus the directories of user content whose
paths the application itself gives to the model, which it would then be
unable to open. These normally live under DATA_DIR; the documented mail
attachment override may instead name a disjoint external directory:
UPLOAD_DIR the chat upload manifest renders "path=<p>" and
says to read it with read_file (agent_loop.py)
MAIL_ATTACHMENTS_DIR download_attachment returns the path and its own
description tells the model to read it
PERSONAL_DIR GET /api/personal returns a path per file and is
reachable through the app_api tool; RUNBOOK_DIR
nests under it
PERSONAL_UPLOADS_DIR indexed as a personal-docs directory, which
manage_rag lists as an absolute path
Order matters: the first entry is roots[0], which _resolve_search_root uses
when grep/glob/ls are called with no path.
"""
from src.constants import (
DATA_DIR,
MAIL_ATTACHMENTS_DIR,
PERSONAL_DIR,
PERSONAL_UPLOADS_DIR,
UPLOAD_DIR,
)
configured = (
(AGENT_WORKSPACE_DIR, "agent_workspace", False),
(UPLOAD_DIR, "uploads", False),
# This has a documented environment override and may legitimately
# live outside DATA_DIR, but it must never equal/contain DATA_DIR.
(MAIL_ATTACHMENTS_DIR, "mail-attachments", True),
(PERSONAL_DIR, "personal_docs", False),
(PERSONAL_UPLOADS_DIR, "personal_uploads", False),
)
configured_data_dir = os.path.abspath(os.path.expanduser(str(DATA_DIR)))
data_dir = os.path.realpath(configured_data_dir)
safe: list[str] = []
for raw, internal_name, external_ok in configured:
value = str(raw or "").strip()
# These paths are security-policy roots, not ordinary allowlist
# entries. Internal roles may inherit a relative DATA_DIR, but must
# still resolve to their exact canonical child below. External mail
# overrides require an absolute, disjoint directory.
if not value:
continue
expanded = os.path.abspath(os.path.expanduser(value))
# A policy root must not acquire an exemption by redirecting its final
# path component to protected state or to an unrelated external tree.
if os.path.islink(expanded):
continue
resolved = os.path.realpath(expanded)
if os.path.exists(resolved) and not os.path.isdir(resolved):
continue
expected_internal = os.path.join(data_dir, internal_name)
expected_configured = os.path.join(configured_data_dir, internal_name)
inside_data = (
os.path.normcase(expanded)
in {
os.path.normcase(expected_configured),
os.path.normcase(expected_internal),
}
and resolved == expected_internal
)
external_safe = (
external_ok
and os.path.isabs(os.path.expanduser(value))
and resolved != data_dir
and os.path.dirname(resolved) != resolved
and not _path_within(data_dir, resolved)
and not _path_within(resolved, data_dir)
)
if not (inside_data or external_safe) or _is_sensitive_path(resolved):
continue
safe.append(resolved)
return tuple(safe)
def _is_app_state_path(resolved: str) -> bool:
"""True for anything under DATA_DIR that is not agent-readable.
DATA_DIR holds the session store, the auth database, the app encryption key
and the settings file. A model-supplied path must not reach those through
any root, so this is checked in both resolvers rather than expressed as an
absence from the allowlist: a workspace bound at or above the data
directory, or an opt-in tool_path_extra_roots entry covering it, would
otherwise put them back in reach.
A containment rule rather than a filename deny list, so state files added
later are covered without anyone remembering to list them. The composed
sensitive-name policy also denies settings.json and app.db in workspaces.
"""
from src.constants import DATA_DIR
if not _path_within_conservative(resolved, os.path.realpath(DATA_DIR)):
return False
return not any(
_path_within(resolved, d)
for d in _agent_readable_data_subdirs()
)
def _is_hardlinked_regular_file(resolved: str) -> bool:
"""Reject inode aliases that can smuggle DATA_DIR state into an allow root."""
try:
target = os.stat(resolved, follow_symlinks=False)
except OSError:
return False
return stat.S_ISREG(target.st_mode) and getattr(target, "st_nlink", 1) > 1
def _is_denied_tool_path(resolved: str, *, snapshot=None) -> bool:
"""Apply every path deny to a canonical traversal result.
Directory scans pass one ``_control_plane_snapshot()`` for the whole walk;
rebuilding it per entry made grep/ls/glob linear in protected-state stats.
"""
from src.agent_runtime.resources import _control_plane_path
return (
_is_sensitive_path(resolved)
or _is_app_state_path(resolved)
or _is_hardlinked_regular_file(resolved)
or _control_plane_path(resolved, snapshot=snapshot)
)
def _can_traverse_tool_path(resolved: str, *, snapshot=None) -> bool:
"""Allow walking a denied state parent only to reach safe carve-outs."""
if _is_sensitive_path(resolved):
return False
if not _is_app_state_path(resolved):
from src.agent_runtime.resources import _control_plane_path
return not _control_plane_path(resolved, snapshot=snapshot)
return any(
_path_within(readable, resolved)
for readable in _agent_readable_data_subdirs()
)
def _tool_path_roots() -> list[str]:
"""Return the list of directory roots that read_file / write_file
may touch. Default: project data/ + system temp dirs. Extra roots
@@ -999,9 +794,9 @@ def _tool_path_roots() -> list[str]:
"""
roots: list[str] = []
# The agent's workspace plus the user-content directories inside data/.
# The rest of DATA_DIR is denied by _is_app_state_path.
roots.extend(_agent_readable_data_subdirs())
# Project data directory — the agent's primary workspace.
from src.constants import DATA_DIR
roots.append(DATA_DIR)
# /tmp (and its macOS realpath /private/tmp).
roots.append("/tmp")
@@ -1058,10 +853,7 @@ def _resolve_tool_path(raw_path: str) -> str:
"""
resource_operation = active_resource_operation()
if resource_operation is not None:
resolved = resource_operation.resolve_path(raw_path)
if _is_denied_tool_path(resolved):
raise ValueError("bound path is sensitive, application state or a hard-linked file")
return resolved
return resource_operation.resolve_path(raw_path)
ws = get_active_workspace()
if ws:
return _resolve_tool_path_in_workspace(ws, raw_path)
@@ -1070,20 +862,11 @@ def _resolve_tool_path(raw_path: str) -> str:
expanded = os.path.expanduser(str(raw_path).strip())
resolved = os.path.realpath(expanded)
if _is_app_state_path(resolved):
raise ValueError(
f"path '{raw_path}' is inside the application state directory"
)
if _is_sensitive_path(resolved):
raise ValueError(
f"path '{raw_path}' is inside a sensitive directory "
f"(e.g. .ssh, .gnupg) or matches a sensitive filename"
)
if _is_hardlinked_regular_file(resolved):
raise ValueError(f"path '{raw_path}' is a hard-linked file")
if _is_denied_tool_path(resolved):
raise ValueError("path addresses sensitive execution control-plane state")
for root in _tool_path_roots():
if is_inside(root, resolved):
@@ -1120,19 +903,11 @@ def _resolve_tool_path_in_workspace(workspace: str, raw_path: str) -> str:
# Confinement says "inside the root"; the deny list says "allowed". They
# are separate questions and this one stays here, with the policy that
# owns it.
if _is_app_state_path(resolved):
raise ValueError(
f"path '{raw_path}' is inside the application state directory"
)
if _is_sensitive_path(resolved):
raise ValueError(
f"path '{raw_path}' is inside a sensitive directory "
f"(e.g. .ssh, .gnupg) or matches a sensitive filename"
)
if _is_hardlinked_regular_file(resolved):
raise ValueError(f"path '{raw_path}' is a hard-linked file")
if _is_denied_tool_path(resolved):
raise ValueError("path addresses sensitive execution control-plane state")
return resolved
@@ -1191,10 +966,6 @@ def vet_workspace(raw: str) -> Optional[str]:
resolved = os.path.realpath(os.path.expanduser(raw))
if not os.path.isdir(resolved) or _is_sensitive_path(resolved):
return None
# Refuse the bind rather than binding a workspace where every subsequent
# tool call would fail on the same deny list.
if _is_denied_tool_path(resolved):
return None
# Reject filesystem roots: binding / (or a Windows drive/UNC root) as the
# workspace would make every absolute path "inside" it, collapsing the
# confinement into host-wide file access. A root is its own dirname, which
@@ -1206,25 +977,11 @@ def vet_workspace(raw: str) -> Optional[str]:
def agent_cwd() -> str:
"""Working directory for agent subprocesses (bash/python/background jobs):
a validated sealed launch root, active workspace, or agent workspace."""
the active workspace when set, else the persistent data dir."""
from src.agent_runtime.process_resources import active_process_operation
bound = active_process_operation()
if bound is not None and bound.launch is not None:
bound.launch.scope.validate()
root = bound.launch.scope.root.path
if vet_workspace(root) != root:
raise RuntimeError("sealed process root is not a safe real directory")
return root
workspace = get_active_workspace()
if workspace:
vetted = vet_workspace(workspace)
if vetted is None:
raise RuntimeError("active workspace is not a safe real directory")
return vetted
resolved = os.path.realpath(_AGENT_WORKDIR)
if resolved not in _agent_readable_data_subdirs() or vet_workspace(resolved) != resolved:
raise RuntimeError("agent workspace is not a safe real directory")
return resolved
return (bound.launch.scope.root.path if bound is not None and bound.launch is not None
else get_active_workspace() or _AGENT_WORKDIR)
def get_mcp_manager():
@@ -1239,28 +996,19 @@ def _resolve_search_root(raw_path: str) -> str:
With a workspace active, the workspace folder is the root and a supplied
path is confined inside it. Otherwise an empty path defaults to the agent's
primary root (its workspace under the project data dir) and a supplied path
is confined by the global allowlist + sensitive-file policy.
primary root (project data dir) and a supplied path is confined by the
global allowlist + sensitive-file policy.
"""
resource_operation = active_resource_operation()
if resource_operation is not None:
resolved = resource_operation.resolve_path(raw_path, search=True)
if _is_denied_tool_path(resolved):
raise ValueError("bound search root is sensitive, application state or a hard-linked file")
return resolved
return resource_operation.resolve_path(raw_path, search=True)
raw = (raw_path or "").strip()
ws = get_active_workspace()
if ws:
# Resolve the empty case as the workspace path rather than returning
# it directly: returned unchecked it skipped both deny lists, so a
# bare ls listed whatever the workspace was bound to.
return _resolve_tool_path_in_workspace(ws, raw or ws)
return os.path.realpath(ws) if not raw else _resolve_tool_path_in_workspace(ws, raw)
if not raw:
roots = _tool_path_roots()
default_root = os.path.realpath(AGENT_WORKSPACE_DIR)
if default_root in roots and not _is_denied_tool_path(default_root):
return default_root
raise ValueError("default agent workspace is not a safe readable data subdirectory")
return roots[0] if roots else os.path.realpath(".")
return _resolve_tool_path(raw)
logger = logging.getLogger(__name__)
@@ -1651,14 +1399,6 @@ async def execute_tool_block(
"NO_TOOL_SECURITY_CONTEXT"
)
# Credential denial precedes authority, exact approval claim and bridges.
if (isinstance(security_context, ToolRunSecurityContext)
and security_context.delegated_credential):
from src.tool_capabilities import delegated_tool_is_blocked
if delegated_tool_is_blocked(getattr(block, "tool_type", None), getattr(block, "content", None)):
decision = security_context.decision_for(block.tool_type, block.content)
return blocked_tool_result(block.tool_type, decision.reason)
authority = active_request_authority() if request_authority is MISSING_AUTHORITY else request_authority
parent = active_request_authority()
if isinstance(authority, RequestAuthority) and parent is not None and authority is not parent:
@@ -2082,17 +1822,6 @@ async def _execute_tool_block_impl(
mark_authorized()
if bridge_owns_tool:
if tool == "write_file":
from src.agent_tools.filesystem_tools import _parse_write_intent, _unwrap_fenced_source_body
try:
path, body, section, explicit_clear = _parse_write_intent(content)
if not path or path.endswith(("/", "\\")) or "\n" in path or "\r" in path:
raise ValueError("write_file: valid file path required")
body = _unwrap_fenced_source_body(body, path)
if not section or (not body.strip() and not explicit_clear):
raise ValueError("write_file: bridge cannot safely enforce implicit empty content; use explicit JSON string content")
except ValueError as error:
return "write_file: invalid arguments", {"error": str(error), "exit_code": 1}
try:
return await dispatched(execution_bridge.route_tool(
tool,
@@ -2164,7 +1893,8 @@ async def _execute_tool_block_impl(
if tool == "generate_image":
from src.ai_interaction import do_generate_image
desc = "generate_image"
result = await dispatched(do_generate_image(content, session_id=session_id, owner=owner))
image_privacy = (client_runtime_context or {}).get('web_ui_state', {}).get('incognito') is True
result = await dispatched(do_generate_image(content, session_id=session_id, owner=owner, **({'incognito': True} if image_privacy else {})))
elif (tool in NATIVE_FILESYSTEM_TOOLS
and (active_resource_operation() is not None or tool != "apply_patch"
or not _tui_host_bridge_patch_url(client_runtime_context))):
@@ -2324,7 +2054,8 @@ async def _execute_tool_block_impl(
result = await dispatched(do_list_cookbook_servers(content, owner=owner))
elif tool == "edit_image":
desc = "edit_image"
result = await dispatched(do_edit_image(content, owner=owner))
image_privacy = (client_runtime_context or {}).get('web_ui_state', {}).get('incognito') is True
result = await dispatched(do_edit_image(content, owner=owner, **({'incognito': True} if image_privacy else {})))
elif tool == "edit_file":
result = await dispatched(_direct_fallback(tool, content)) or {"error": "edit failed", "exit_code": 1}
desc = result.get("output") or result.get("error") or "edit_file"