The fuzzy fallback anchored every line with a leading [ \t]* that
greedily consumed the file's indentation into the matched region, so a
no-indent oldText dropped the edited line's indentation. Fold leading
whitespace into the match only when oldText was itself indented on the
first line, mirroring exact-substring semantics. Add a regression test.
The Read tool documents that a negative offset reads from the end (-N = last N lines). Content is split on newline, so a file ending in a newline produces a trailing empty element and total_file_lines is one too high. Every negative offset was therefore off by one: offset=-1 returned the empty string after the final newline instead of the last line, and -N returned N-1 real lines.
Exclude the trailing empty element when computing the start line for negative offsets. Adds regression tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
The Edit tool falls back to a whitespace-tolerant fuzzy match when oldText does not match byte-for-byte. On a fuzzy hit it replaced text inside a whitespace-normalized copy of the whole file and wrote that copy back, so every untouched line lost its original indentation (runs of spaces/tabs collapsed to a single space). For indentation-sensitive files such as Python this silently corrupts the file.
Locate the fuzzy match in the original content with a whitespace-flexible regex and return offsets into that original content, so only the matched region is replaced. Adds regression tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
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>
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
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>
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>
Two independent robustness fixes:
1. ChatChannel.cancel_session / cancel_all_session raised KeyError when a
session existed in self.sessions but no future had been dispatched yet.
self.sessions[sid] is created in produce(), but self.futures[sid] is only
created later in consume() on first dispatch. Cancelling in that window
(e.g. user sends a message then immediately cancels) crashed the cancel
path. Use self.futures.get(sid, []) so an absent entry is a no-op.
2. compress_imgfile decremented JPEG quality by 5 with no lower bound. For an
image that cannot be compressed below max_size, quality went 0, negative,
... — the loop never terminated and passed invalid quality values to PIL.
Add a min_quality floor (10) and return the best effort once reached.
Adds tests/test_robustness_fixes.py covering both paths (5 tests).
1. Creating a provider no longer auto-switches bot_type. Only an
explicit make_active=true changes the active model — prevents
silently hijacking users on Claude/OpenAI/etc.
2. When a provider IS activated, its 'model' is now written into the
global 'model' field. This ensures all three paths (regular chat,
agent_bridge, vision) use the correct model without per-path patches.
3. Removed unused i18n key 'models_custom_name_exists' (no longer
referenced after the id-based rework removed name-collision checks).
4. Updated tests: 39 passing (added model-sync tests, fixed tests that
relied on the removed auto-activation behavior).
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
Rework the multi custom-provider design per maintainer review:
1. Data model: use server-generated uuid4 short id as primary key;
'name' is now a pure display label that can be freely renamed.
2. Routing: drop 'custom_active_provider'; activate a provider by
setting bot_type to 'custom:<id>'. Single source of truth — no
pointer drift between bot_type and a separate active selector.
3. Security: drag_sensitive() now recursively masks api_key/secret in
nested structures (custom_providers list); previously only top-level
string fields were masked.
4. Per-provider model: the provider's 'model' field now takes effect on
the main chat path and agent path (was silently ignored before).
5. XSS fix: replace all inline onclick handlers in custom-provider UI
with data-* attributes + event delegation. Provider names never
appear in executable HTML contexts.
Legacy compatibility: bot_type='custom' (no colon) still reads the flat
custom_api_key/custom_api_base fields byte-for-byte identically.
Closes: consolidates #2876 into this PR as requested.
Ref: #2838
Adds first-class support for configuring more than one custom
OpenAI-compatible provider (e.g. SiliconFlow, DeepSeek, local vLLM)
and switching the active one from the web console, addressing #2838.
Backend:
- config: new `custom_providers` (list) and `custom_active_provider`
fields, fully backward compatible with the legacy single
`open_ai_api_base`/`model` fields (used as fallback).
- models/custom_provider.py: centralized resolver
`resolve_custom_credentials()` returning (api_key, api_base, model),
with active-provider selection and graceful fallback.
- chat_gpt_bot.py wired to use the resolver.
- web_channel.py: `_provider_overview` expands `custom_providers` into
one card per provider (id `custom:<name>`, active flag, masked key);
new POST actions `set_custom_provider`, `delete_custom_provider`,
`set_active_custom_provider` with hermetic persistence + bridge reset.
Frontend:
- console.js: dedicated "Custom providers" section with add / edit /
delete / set-active actions, masked-key keep-existing handling, and
~20 new zh/en i18n strings.
- chat.html: custom provider modal.
Tests:
- tests/test_custom_provider.py (11) - resolver/config behavior.
- tests/test_custom_provider_handlers.py (18) - write-side handlers and
overview expansion, including duplicate-name rejection.
All 29 unit tests pass.
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>
- Add MINIMAX_M3 = "MiniMax-M3" constant and put it first in MODEL_LIST
- Default MinimaxBot model: MiniMax-M2.7 -> MiniMax-M3
- Keep MiniMax-M2.7 and MiniMax-M2.7-highspeed as legacy options
- Drop MINIMAX_M2_5 / MINIMAX_M2_1 / MINIMAX_M2_1_LIGHTNING / MINIMAX_M2
- Update web console recommended/provider model lists
- Update README capability table and docs/models index (en/zh/ja)
- Update docs/models/minimax.mdx and coding-plan.mdx MiniMax section
- Update run.sh / run.ps1 installer default and menu hint
- Update zh CLI status sample output
- Update unit tests to assert new M3 default and constant
TTS (speech-2.*) and API base URL remain unchanged.
The translate module previously only supported Baidu translation, and the
factory raised a bare RuntimeError for any other type. This change adds
Youdao Translation as a second provider and improves the factory's error
message.
Implementation details:
- New YoudaoTranslator class in translate/youdao/youdao_translate.py
- Implements Youdao's v3 SHA-256 signature scheme, including the
truncate-input rule for queries longer than 20 characters
- Maps ISO 639-1 language codes to Youdao-specific codes
(zh -> zh-CHS, zh-TW -> zh-CHT, others pass through)
- Differentiates network errors, API error codes, and empty translations
- factory.create_translator now lists the supported types in its
RuntimeError message instead of failing silently
- Default config exposes youdao_translate_app_key and
youdao_translate_app_secret
Adds 17 unit tests covering signature correctness, language code mapping,
input truncation edge cases, the full request/response flow, and factory
dispatch. All tests pass under Python 3.11.