When a command's output exceeds DEFAULT_MAX_BYTES the Bash tool spills the full output to a temp file. The file was opened in text mode without an explicit encoding, so it used the platform locale encoding (cp936/GBK on Chinese Windows). Output containing emoji or other characters not representable in that codepage raised UnicodeEncodeError, which propagated out and turned a successful command (exit code 0) into a tool error, discarding all output.
Open the temp file with encoding='utf-8', matching the sibling temp file written in _rewrite_long_python_c. Adds a regression test.
Co-authored-by: Cursor <cursoragent@cursor.com>
The browser tool navigates to a model-supplied URL via Playwright
page.goto and then auto-snapshots the page back to the model. The
http(s) navigate path performed no filtering, so a model tool call
(including under prompt injection) could point the agent-driven browser
at the cloud-metadata endpoint (169.254.169.254) and read the
credentials back through the snapshot.
Unlike the vision/web_fetch tools, the browser legitimately needs local
pages — a dev server on localhost / 127.0.0.1 / a LAN IP — so a blanket
"block all internal" policy is the wrong default here. The guard is
therefore narrow: it resolves the hostname and rejects only link-local
addresses (169.254.0.0/16, which includes the 169.254.169.254 metadata
endpoint, and IPv6 fe80::/10) plus the AWS IPv6 IMDS address
(fd00:ec2::254), before navigation. Loopback and RFC1918/LAN stay
reachable so local dev works out of the box.
Only http/https targets are validated; the documented non-HTTP scheme
handling (about:/data:/file:) is unchanged. An operator who deliberately
needs the link-local/metadata target can opt out with
tools.browser.allow_private_targets = true.
tests/test_security_ssrf_browser_navigate.py asserts link-local/metadata
targets are blocked while loopback, RFC1918/LAN and public targets
navigate through (browser service and DNS are stubbed; no real
browser/network).
Signed-off-by: christop <825583681@qq.com>
1. Embedding model: support custom provider
- Add "custom" entry to EMBEDDING_VENDORS with supports_dim_param=False
- Parse custom:<id> credentials and model fallback in agent_initializer
- Expand custom_providers as custom:<id> entries in Web UI dropdown
2. Vision model: support custom provider
- Add custom:<id> routing in _route_by_provider_id
- Add _build_custom_provider reading credentials from custom_providers
- Expand custom_providers in Web UI dropdown, add validation in _set_vision
3. Fix memory_get Windows path validation bug!
- str.startswith(path+'/') always False on Windows due to backslashes, So All Users can not use "memory_get" tool in Windows.
- Use os.path.realpath + os.sep, consistent with MemoryService
4. Fix historical needsModel:false bug preventing embedding provider switch
- Change embedding needsModel to true in console.js
- Support custom:<id> resolution in cow_cli /memory status, also for adding custom provider support
Closes#2908, Closes#2880
web_fetch fetched any http/https URL a model emitted, checking only the
scheme. It performed requests.get(..., allow_redirects=True) with no
hostname resolution check and no private/loopback/link-local/cloud-metadata
filtering, and never re-validated redirect targets. A model (including one
under prompt injection) could make CowAgent fetch 127.0.0.1, RFC1918,
169.254.169.254 or other internal endpoints and return their bodies into
the conversation; a public URL could also 302-bounce into a private target.
The repo already shipped an SSRF validator for the vision tool
(Vision._validate_url_safe). Extract that logic into a shared helper
(agent/tools/utils/url_safety.py) and reuse it:
- execute() now validates the URL before dispatching to either fetch path.
- A new _safe_get() helper disables auto-redirect and follows redirects
manually, re-validating every hop so a public URL cannot bounce into an
internal address. Both the webpage and document fetch paths use it.
- Vision._validate_url_safe now delegates to the shared helper (public API
unchanged), so both URL-consuming tools share one guard.
Stdlib only (ipaddress, socket, urllib.parse); no new dependency. Adds
tests/test_security_ssrf_web_fetch.py covering loopback, cloud-metadata,
RFC1918 and a public->loopback redirect.
Sink: agent/tools/web_fetch/web_fetch.py (_fetch_webpage / _fetch_document).
Signed-off-by: christop <825583681@qq.com>
- Add toggle, update and delete APIs for scheduled tasks
- Add task edit modal with schedule/action updates in web console (PS: In edit box, now I Prevent channel type changes during editing (weixin token bound to session) )
- Add enable/disable switch with visual feedback in task cards
- Sort task list by enabled status first, then by next_run_at
Closes#2882
1. Vision SSRF (#2878, #2872):
Add _validate_url_safe() that resolves the target hostname via DNS and
rejects any IP in private (RFC1918), loopback, link-local, or reserved
ranges before requests.get() is called. This blocks attacks that use
attacker-controlled image URLs to probe internal services or cloud
metadata endpoints (169.254.169.254).
2. Skill install path traversal (#2873):
Add _safe_skill_dir() that validates the skill name cannot escape the
skills/ root directory. Rejects names containing '..', absolute paths,
and any resolved path that falls outside the custom_dir boundary.
Applied to _add_url(), _add_package(), and delete().
Both fixes include comprehensive unit tests (19 test cases) covering
blocked patterns, edge cases, and allowed legitimate usage.
Closes#2878Closes#2873
Ref: #2872
Add a self-evolution subsystem that reviews idle conversations in an
isolated agent and durably learns from them — patching/creating skills,
finishing unfinished tasks, and backfilling missed memory.
- Trigger: background idle scan, fires when a session is idle >= N min AND
(>= N turns OR context usage > 80%). In-memory cursor reviews only new
messages so a session never re-learns old content.
- Isolated review agent: same model, restricted toolset, hard write-guard
confining edits to the workspace (built-in skills are protected).
- Safety: file-level backup before edits + evolution_undo tool; notify the
user ONLY when a workspace file actually changed (no-nag rule); capped
concurrency.
- Records to memory/evolution/<date>.md, surfaced in the memory UI's
renamed "Self-Evolution" tab (merged with dream diaries).
- Hide internal [SCHEDULED]/[EVOLUTION]/backup_id markers from chat history
display (also fixes scheduler marker leakage) while keeping them in stored
content for undo.
- Flat config: self_evolution_enabled (default off until release),
self_evolution_idle_minutes (15), self_evolution_min_turns (6).
- Tests: tests/test_evolution.py (stub + real model modes, 7 scenarios).
Replace the broad `~/.cow` directory check with a regex that matches
only the credential file path (`\.cow[/\\]\.env`), so legitimate access
to other `~/.cow/` subdirectories (e.g. skills) is no longer blocked.
Drop the incomplete env/printenv blocking rule per reviewer feedback.
Rewrite test_invariant_bash.py to use the correct Bash().execute()
API and cover both the blocked and allowed cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_stdio_send (single pipe) must remain serialized under _call_lock,
but SSE and streamable-http use independent HTTP requests and can
safely execute concurrently across sessions.
- Scope _call_lock to stdio transport only
- Add _http_lock with double-checked pattern to protect _http_session_id
initialization during concurrent streamable-http requests
- Use reader thread + queue.Queue instead of select.select() which does not
work with pipes on Windows (only sockets)
- Make MCP server timeout configurable via mcp.json (default 120s)
- Validate JSON-RPC response id to skip stale responses from timed-out calls
- Log MCP server stderr at WARNING level instead of DEBUG for visibility
Search tool now supports 4 backends with unified output (bocha,
qianfan, zhipu, linkai) and a routing layer:
- strategy 'auto' (default): pick first configured in canonical order
bocha > qianfan > zhipu > linkai
- strategy 'fixed': pin a specific provider
- agent may pass `provider` to override per-call (only exposed when
≥2 providers configured + auto strategy)
Browser sessions now reuse a Chromium user profile across runs by default
(`~/.cow/browser_profile`), so users only log in to a site once.
Three launch modes are selectable via `tools.browser` in config.json:
- persistent (default): Playwright Chromium with a persistent user_data_dir
- cdp: attach to an externally launched real Chrome via `cdp_endpoint`
(full fingerprints, ideal for sites with strict bot detection)
- fresh: clean context every run, set `persistent: false`
Also:
- Self-heal when the user closes the browser window mid-session: detect
closed page/context/browser via close listeners and exception scanning,
then transparently relaunch on the next request.
- Graceful CDP shutdown: disconnect only, never kill the user's Chrome.
- Friendly errors when the CDP endpoint is unreachable or the persistent
profile is locked, so the LLM can guide the user instead of looping.
- Fix tool config being silently overwritten by workspace config in
AgentInitializer; per-tool user settings (e.g. browser.cdp_endpoint)
are now merged instead of replaced.
- Update zh / en / ja docs with the new login-persistence section,
including the Chrome 137+ requirement to pair --remote-debugging-port
with a dedicated --user-data-dir.
Boot MCP servers (npx/uvx) on a background thread instead of blocking
agent init. Built-in tools serve traffic immediately while MCP comes
online; each new agent reads whatever is ready at creation time.
Idempotent via _mcp_loaded flag — concurrent sessions never re-fork
subprocesses. Per-server failures are isolated and warmup is triggered
in app.py so loading overlaps with channel startup.
Stability fixes in mcp_client.py:
- Fix stderr buffer overflow: start daemon thread to continuously drain
stderr pipe, preventing 64KB buffer fill that blocks child process
- Fix notification interference: loop readline and skip JSON-RPC messages
without 'id' field (notifications) instead of treating them as responses
- Fix concurrent race condition: wrap send+receive in _call_lock so
multiple sessions cannot interleave reads/writes on the same client
- Fix missing timeout: use select.select() with 30s timeout in
_readline_with_timeout() to prevent infinite block on dead MCP server
Config improvements in tool_manager.py:
- Add _normalize_mcp_configs() to support both list format (mcp_servers)
and dict format (mcpServers used by Claude Desktop / Cursor)
- Add _load_mcp_configs() to load from ~/cow/mcp.json first, falling back
to config.json mcp_servers field for backward compatibility
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs found during end-to-end validation with Amap and Chrome DevTools
MCP servers:
1. MCP tools were loaded into ToolManager._mcp_tool_instances but never
added to the agent's tool list. AgentInitializer._load_tools() only
iterated tool_classes (built-in tools). Added a second pass to append
all MCP tool instances.
2. When a MCP server config contains an "env" dict, it was passed directly
to subprocess.Popen, replacing the entire process environment. This
caused npx to fail because PATH and other inherited vars were missing.
Fixed by merging config env on top of os.environ.
Validated with:
- @amap/amap-maps-mcp-server (12 tools, stdio + API key env var)
- chrome-devtools-mcp (29 tools, stdio + remote debugging port)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Allows CowAgent to dynamically load tools from any MCP server at startup,
extending the agent from a fixed toolset to an open, extensible tool ecosystem.
## What's added
- `agent/tools/mcp/mcp_client.py`: lightweight JSON-RPC client supporting both
stdio (subprocess) and SSE (HTTP) transports — zero extra dependencies
- `agent/tools/mcp/mcp_tool.py`: `McpTool` wraps a single MCP tool as a
`BaseTool`, with dynamic name/description/params set at instance level
- `agent/tools/tool_manager.py`: new `_load_mcp_tools()` loads MCP servers at
startup via `McpClientRegistry`; falls back gracefully on any error; no-op
when `mcp_servers` is not configured
- `config.py`: registers `mcp_servers` in `available_setting` with inline docs
## Design
- No new dependencies — JSON-RPC implemented from scratch using stdlib only
- MCP clients are long-lived (initialized once, shared across tool calls)
- `McpClientRegistry` holds all subprocess handles and shuts them down cleanly
- Server init failures are non-fatal: logged as warnings, agent continues normally
- Zero overhead when `mcp_servers` is absent from config
## Config example
```json
"mcp_servers": [
{
"name": "filesystem",
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
}
]
```
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>