|
1 | 1 | # Changelog |
2 | 2 |
|
| 3 | +## 2.1.3 - 2026-05-09 |
| 4 | + |
| 5 | +**Security hardening + chat-pipe queue + path harmonization for |
| 6 | +`tdpilot_API.tox`.** A fresh deep-debug audit of the chat-pipe found |
| 7 | +a CSRF / drive-by-RCE chain plus a silent-message-drop bug. Both |
| 8 | +closed in this release; storage is also unified under the dpsk4 |
| 9 | +variant root. |
| 10 | + |
| 11 | +### Security fixes |
| 12 | + |
| 13 | +- **Bug #1 — `TDPILOT_API_INSECURE` no longer bypasses origin |
| 14 | + checks.** Pre-2.1.3 the insecure-mode env var bypassed BOTH the |
| 15 | + token AND the origin gate, leaving the chat-pipe wide open to |
| 16 | + cross-origin browser CSRF. Now insecure-mode bypasses **only** the |
| 17 | + token check; the origin allowlist + `Sec-Fetch-Site` checks always |
| 18 | + fire. Legitimate non-browser tooling (curl, Python `requests`) |
| 19 | + sends no `Origin` header so it still passes the same-origin gate. |
| 20 | + See `_check_auth` in `td_component/tdpilot_api_web_callbacks.py`. |
| 21 | +- **Bug #3 — `EXEC_MODE` clamps to `restricted` in insecure mode.** |
| 22 | + Pre-2.1.3 the API tox unconditionally set |
| 23 | + `TD_MCP_EXEC_MODE=full` at COMP load, so an unauthenticated POST |
| 24 | + to `/send` could chain into `td_exec_python` with full Python |
| 25 | + privileges. Now the clamp triggers whenever `TDPILOT_API_INSECURE` |
| 26 | + is truthy. Users who genuinely need full mode for trusted local |
| 27 | + scripting can opt back in by setting |
| 28 | + `TDPILOT_API_ALLOW_INSECURE_FULL_EXEC=1`. See |
| 29 | + `_build_runtime` in `td_component/tdpilot_api_extension.py`. |
| 30 | +- **`/send` now requires `Content-Type: application/json`.** The |
| 31 | + pre-2.1.3 plain-text body was a CORS "simple request" that |
| 32 | + bypassed preflight, letting cross-origin pages POST into the |
| 33 | + chat-pipe without the browser checking `Access-Control-Allow- |
| 34 | + Origin`. JSON triggers a preflight, which the origin gate |
| 35 | + rejects. The chat HTML's `send()` was migrated to the |
| 36 | + `{"message": "<text>"}` envelope. |
| 37 | +- **Loud startup banner when insecure-mode is active** — pre-2.1.3 |
| 38 | + the bypass was a silent env toggle and the audit found it active |
| 39 | + on a real machine with no startup signal. The banner prints to |
| 40 | + textport on every COMP load and lists the secure-mode opt-in |
| 41 | + path. |
| 42 | + |
| 43 | +### Reliability fixes |
| 44 | + |
| 45 | +- **Bug #2 — rapid `/send` no longer drops messages.** Pre-2.1.3 a |
| 46 | + follow-up `/send` while the worker was still busy overwrote |
| 47 | + `comp.par.Chatmessage.val` before the prior message was consumed, |
| 48 | + so the prior message was silently dropped. New behaviour: the |
| 49 | + param is always cleared up front; messages that arrive while the |
| 50 | + worker is alive are appended to a FIFO inbox queue on |
| 51 | + `comp.storage` (`tdpilot_api_chat_inbox` key) and drained one at |
| 52 | + a time on each `EV_DONE`. The `/reset` path clears the queue. |
| 53 | + - **Chat HTML send-button gate.** The send button now stays |
| 54 | + disabled until the runtime emits a non-working agent state |
| 55 | + (`idle`, `ready`, `reset`, `error`, `send failed`) over the |
| 56 | + WebSocket, not just until the `/send` fetch resolves. |
| 57 | + Re-enabling on the fetch (which returns `queued` instantly) |
| 58 | + was the surface symptom of the queue-drop bug. |
| 59 | + |
| 60 | +### Path harmonization |
| 61 | + |
| 62 | +- **Chat-pipe storage moved under `~/.tdpilot-dpsk4/api/<subdir>`** |
| 63 | + with transparent legacy `~/.tdpilot-api/<subdir>` fallback. The |
| 64 | + dpsk4 fork now uses a single variant root for all its state |
| 65 | + (matching the MCP-side layout). New helper |
| 66 | + `tdpilot_api_config.resolve_user_dir(subdir)` returns the new |
| 67 | + location by default, falling back to the legacy path if it has |
| 68 | + content (per-subdir, not bulk-migrated). All 9 chat-pipe modules |
| 69 | + with hardcoded path constants (`memory`, `knowledge`, `recipes`, |
| 70 | + `skills`, `snapshots`, `traces`, `macros`, `tools`, `history`) |
| 71 | + now route through `resolve_user_dir`. Tool descriptions in |
| 72 | + `tdpilot_api_schema_defs.py` were swept to point at the new |
| 73 | + default — the LLM-facing copy and the runtime resolution are now |
| 74 | + consistent. |
| 75 | +- The legacy `~/.tdpilot-api/` is still used by users who have |
| 76 | + data there; no automatic migration runs on import. To move data, |
| 77 | + manually `mv ~/.tdpilot-api/* ~/.tdpilot-dpsk4/api/` after |
| 78 | + closing TD. |
| 79 | + |
| 80 | +### Tone / politeness |
| 81 | + |
| 82 | +- **Memory protocol updated.** The system prompt now instructs the |
| 83 | + agent NOT to save reflections about its own behaviour uninvited — |
| 84 | + only when the user explicitly asks ("remember this", "save a |
| 85 | + memory") or has just stated a clear rule / preference / fact |
| 86 | + worth keeping. Auto-saved meta-feedback memories were noise in |
| 87 | + the user's MEMORY.md index. |
| 88 | + |
| 89 | +### Model routing |
| 90 | + |
| 91 | +- **Bug — explicit "use pro" / "use flash" in user text was |
| 92 | + ignored.** Pre-2.1.3 the auto-tier heuristic was the only way to |
| 93 | + flip pro/flash on a per-turn basis. Users who set the COMP's |
| 94 | + `Modeltier` to `flash` (or left it at `auto` with a short |
| 95 | + prompt) and wrote "use pro model" in their message kept routing |
| 96 | + to flash because the heuristic scored 0 (no build-keyword, no |
| 97 | + code fence, no tool keywords, len < 300). Now `_resolve_model` |
| 98 | + checks `_PRO_OVERRIDE_RE` / `_FLASH_OVERRIDE_RE` against the |
| 99 | + user text BEFORE the tier-pin or auto heuristic. Phrases that |
| 100 | + trigger the override: |
| 101 | + - `use pro` / `use the pro model` / `switch to pro` / `force pro` |
| 102 | + - `with pro` / `via pro` / `run in pro` / `run with pro` |
| 103 | + - `pro model` / `pro tier` / `pro mode` / `in pro mode` |
| 104 | + - `deepseek-v4-pro` / `deepseekv4pro` / `deepseek_v4_pro` |
| 105 | + |
| 106 | + Same pattern set applies to flash. Pro takes precedence on ties |
| 107 | + (since the motivating bug was "I asked for pro and got flash"). |
| 108 | + The override is per-turn only — the next turn falls back to the |
| 109 | + configured tier. |
| 110 | + |
| 111 | + False-positive guard via `\b…\b` word boundaries: the override |
| 112 | + does NOT trigger on `professional`, `professionally`, `prompt`, |
| 113 | + `production`, `process`, `flashlight`, `flashy`, `flashed`, or |
| 114 | + bare `pro` / `flash` mentions without a verb cue. Tests at |
| 115 | + `tests/test_tdpilot_api_agent.py::test_resolve_model_*_override_*`. |
| 116 | + |
| 117 | +### Files touched |
| 118 | + |
| 119 | +Source files (all in API tox source list — `.tox` rebuild required |
| 120 | +in TD; CI will reject this branch until `.tox-source-hash.json` |
| 121 | +is regenerated): |
| 122 | + |
| 123 | +``` |
| 124 | +td_component/tdpilot_api_web_callbacks.py # Bug #1 server side |
| 125 | +td_component/tdpilot_api_chat.html # Bug #1 client + Bug #2 client |
| 126 | +td_component/tdpilot_api_extension.py # Bug #3 + Bug #2 server + banner |
| 127 | +td_component/tdpilot_api_runtime.py # system-prompt politeness + path-string sweep |
| 128 | +td_component/tdpilot_api_config.py # resolve_user_dir |
| 129 | +td_component/tdpilot_api_memory.py # path constant |
| 130 | +td_component/tdpilot_api_knowledge.py # path constant |
| 131 | +td_component/tdpilot_api_recipes.py # path constant |
| 132 | +td_component/tdpilot_api_skills.py # path constant |
| 133 | +td_component/tdpilot_api_patches.py # path constant |
| 134 | +td_component/tdpilot_api_tracing.py # path constant |
| 135 | +td_component/tdpilot_api_user_tools.py # path constant |
| 136 | +td_component/tdpilot_api_macros.py # path constant |
| 137 | +td_component/tdpilot_api_compaction.py # path constant |
| 138 | +td_component/tdpilot_api_introspect.py # firstrun memory probe |
| 139 | +td_component/tdpilot_api_schema_defs.py # tool-description path sweep |
| 140 | +td_component/tdpilot_api_recovery.py # error-message path |
| 141 | +td_component/callbacks/_header.py # API_VERSION bump |
| 142 | +``` |
| 143 | + |
| 144 | +Tests `tests/test_standalone_csrf.py::test_insecure_mode_bypasses_token_and_origin` |
| 145 | +was renamed/updated to assert the new contract (insecure-mode |
| 146 | +bypasses token only, not origin). |
| 147 | + |
3 | 148 | ## 2.1.2 - 2026-05-09 |
4 | 149 |
|
5 | 150 | **Patch: opt-in MCP auth.** Pre-2.1.2 the dpsk4 COMP's |
|
0 commit comments