Commit Graph

211 Commits

Author SHA1 Message Date
pewdiepie-archdaemon de64bda6bc Fix endpoint model preservation for tasks 2026-06-02 09:44:24 +09:00
PewDiePie 5a04c78e56 Merge pull request #797 from ErnestHysa/fix/research-path-traversal
fix(research): validate session_id to block path traversal
2026-06-02 09:42:23 +09:00
PewDiePie 73d2e25d1e Merge pull request #782 from tanmayraut45/fix/active-streams-toctou
Fix TOCTOU race in chat stream status endpoint
2026-06-02 09:42:07 +09:00
PewDiePie 29052ea60b Merge pull request #776 from tanmayraut45/fix/searxng-container-caps
Fix searxng container permission errors during setup
2026-06-02 09:41:46 +09:00
PewDiePie 0769bb8559 Merge pull request #809 from BSG-Walter/main
fix: resolve DuckDuckGo redirect URLs in HTML fallback search
2026-06-02 09:41:34 +09:00
PewDiePie f857255f14 Merge pull request #824 from ooovenenoso/fix/odysseus-issue-802-windows-js-mime
fix: normalize JS static MIME types on Windows
2026-06-02 09:41:18 +09:00
PewDiePie 72575775f6 Merge pull request #837 from jamesarslan/fix/agent-toolcall-null-content
Fix tool-calling HTTP 400 on Gemini and Ollama (empty assistant content with tool_calls)
2026-06-02 09:41:01 +09:00
pewdiepie-archdaemon 45bdb1a666 Polish task UI slash commands and Ollama serving 2026-06-02 09:36:03 +09:00
James Arslan 583efe9846 Fix tool-calling HTTP 400 on Gemini and Ollama: send null, not empty, assistant content
When an agent turn uses native (OpenAI-style) function calling and the model
returns only tool calls with no prose, _append_tool_results built the follow-up
assistant message with content "" (empty string).

Google Gemini's OpenAI-compatible endpoint and Ollama both reject an assistant
message that carries tool_calls alongside an empty-string content with HTTP 400.
Because that message feeds the tool results back to the model, every tool-using
turn on these providers dies at the second round: the tool runs, but the agent
never produces a result.

Use None (JSON null) instead, which is the spec-correct form the OpenAI SDK
itself emits and which OpenAI and Anthropic accept too. Adds tests covering the
native tool-call content shaping.
2026-06-02 00:34:51 +00:00
Kevin a4010d85a0 fix: normalize JS static MIME types on Windows
Refs #802
2026-06-02 01:32:00 +02:00
BSG-Walter a9ce8d5439 fix: resolve DuckDuckGo redirect URLs in HTML fallback search
The DuckDuckGo HTML fallback returns redirect URLs (//duckduckgo.com/l/?uddg=...)
instead of actual page URLs. This caused fetch_webpage_content() to reject them
instantly because _public_http_url() requires an http/https scheme, making search
results unfetchable in deep research mode.
Added _resolve_url() to:
- Convert protocol-relative URLs to absolute (https:)
- Convert path-relative URLs to absolute
- Extract the real URL from DuckDuckGo's /l/?uddg= redirect parameters
2026-06-01 19:42:01 -03:00
pewdiepie-archdaemon f991e01df3 Show Ollama models in Cookbook Serve 2026-06-02 07:38:45 +09:00
Ernest Hysa 9176c4d63a fix(research): validate session_id to block path traversal
Every research endpoint interpolates session_id into filesystem paths
(Path('data/deep_research') / f'{session_id}.json') without checking
for traversal sequences. A crafted ID like '../../data/auth' reaches
arbitrary JSON files — readable via research_detail (which also leaks
file paths in error messages), writable via research_archive, and
deletable via research_delete.

Add _validate_session_id() which rejects anything outside
[a-zA-Z0-9-]{1,128}. Called before filesystem access in all 12
endpoints that accept a session_id path parameter.
2026-06-01 23:25:38 +01:00
pewdiepie-archdaemon 24ebb44073 Clarify AI tasks and skipped activity rows 2026-06-02 07:11:40 +09:00
pewdiepie-archdaemon 452eae2bfd Fix Brain tab panel visibility 2026-06-02 07:07:51 +09:00
pewdiepie-archdaemon b194604557 Match mobile task state button height 2026-06-02 07:06:17 +09:00
pewdiepie-archdaemon f5fb1f3995 Polish task activity icons 2026-06-02 07:04:52 +09:00
pewdiepie-archdaemon f12dfaf025 Compact mobile task controls 2026-06-02 07:02:26 +09:00
pewdiepie-archdaemon fd59d2d7d5 Remove mobile notes close button 2026-06-02 07:00:40 +09:00
pewdiepie-archdaemon 0df806259d Clarify task status controls on mobile 2026-06-02 06:57:53 +09:00
pewdiepie-archdaemon 91af23e508 Make favorite dot feedback transient 2026-06-02 06:52:03 +09:00
pewdiepie-archdaemon fae1a75fbe Add model favorite dot feedback 2026-06-02 06:50:22 +09:00
pewdiepie-archdaemon ba8de14b4d Remove broken remind slash command 2026-06-02 06:48:41 +09:00
pewdiepie-archdaemon fdb25198d7 Nudge model picker favorite dots 2026-06-02 06:46:05 +09:00
pewdiepie-archdaemon f9133748db Add direct tool slash commands 2026-06-02 06:44:29 +09:00
pewdiepie-archdaemon fb1d399764 Refresh slash command hints 2026-06-02 06:40:23 +09:00
pewdiepie-archdaemon c7cc6f025a Adjust model picker favorite dot alignment 2026-06-02 06:36:10 +09:00
pewdiepie-archdaemon ccb9600d71 Polish model picker favorites 2026-06-02 06:33:53 +09:00
tanmayraut45 12bc2c3025 Fix TOCTOU race in chat stream status endpoint
The /api/chat/stream_status handler did a membership test against
_active_streams followed by an indexed read of the same key. Between
those two ops, a sibling stream's finally block (or a stop / cleanup
path) can pop the entry, turning the indexed read into a KeyError that
bubbles up as a 500. The race is the exact one _stream_set was already
written to avoid; the comment on the helper at the top of the module
spells out why a single .get() is the right pattern here too.

Collapse the two-step into a single .get() call so the lookup either
returns the live record or None, and report 'detached' / 404 based on
that single read. No behavior change on the happy path; the failure
mode under concurrent stream cleanup is now handled deterministically.

Closes #658.
2026-06-02 03:02:30 +05:30
pewdiepie-archdaemon 4ebf370669 Merge branch 'pr-673' into visual-pr-playground 2026-06-02 06:26:32 +09:00
pewdiepie-archdaemon 721ccf85a5 Merge branch 'pr-644' into visual-pr-playground 2026-06-02 06:26:32 +09:00
pewdiepie-archdaemon 8f6bd52b86 Merge branch 'pr-738' into visual-pr-playground 2026-06-02 06:26:32 +09:00
pewdiepie-archdaemon f2f631cd9c Merge branch 'pr-480' into visual-pr-playground 2026-06-02 06:26:32 +09:00
pewdiepie-archdaemon 77a3c86cba Merge branch 'pr-611' into visual-pr-playground 2026-06-02 06:26:32 +09:00
pewdiepie-archdaemon 64d9c14841 Merge branch 'pr-506' into visual-pr-playground 2026-06-02 06:26:32 +09:00
pewdiepie-archdaemon e556b87caf Merge branch 'pr-550' into visual-pr-playground 2026-06-02 06:26:32 +09:00
pewdiepie-archdaemon 97705007ee Merge branch 'pr-594' into visual-pr-playground 2026-06-02 06:26:31 +09:00
pewdiepie-archdaemon a7acd335f3 Merge branch 'pr-575' into visual-pr-playground 2026-06-02 06:26:31 +09:00
pewdiepie-archdaemon 693c6983ea Merge branch 'pr-469' into visual-pr-playground 2026-06-02 06:26:31 +09:00
pewdiepie-archdaemon 2ef0121ac0 Merge branch 'pr-684' into visual-pr-playground 2026-06-02 06:26:31 +09:00
pewdiepie-archdaemon 30b4e544b8 Merge branch 'pr-668' into visual-pr-playground 2026-06-02 06:26:31 +09:00
pewdiepie-archdaemon d889fe8769 Merge branch 'pr-696' into visual-pr-playground 2026-06-02 06:26:31 +09:00
tanmayraut45 a113ee4f6f Fix searxng container permission errors during setup
A fresh `docker compose up -d` shows the searxng container failing its
healthcheck with permission errors at setup (reported in #721 — the
service comes up under names like `odysseus_searxng_1` and never goes
ready, which then blocks the main odysseus container because of the
`depends_on: searxng: condition: service_healthy` gate).

Root cause: the official `searxng/searxng:latest` image runs as the
non-root `searxng` user but its entrypoint still needs to

1. chown /etc/searxng on first boot so the persisted named volume is
   owned by the searxng user inside the container,
2. su-exec to drop / re-assert privileges before launching uwsgi, and
3. let our wrapper entrypoint (which seeds settings.yml into the named
   volume on first boot) write the file through the volume mount.

Without explicit `cap_add`, the container has neither CHOWN nor
DAC_OVERRIDE nor SETUID/SETGID, so the entrypoint aborts at the first
chown / su-exec / redirection with EACCES. The upstream searxng-docker
compose file solves this with the standard "drop everything, grant only
what's needed" capability pattern.

Fix: mirror the upstream cap_drop ALL / cap_add CHOWN, SETGID, SETUID,
DAC_OVERRIDE on the searxng service. This grants only the four caps the
entrypoint actually needs, matches what searxng-docker ships with, and
leaves ports, volumes, env, healthcheck, and the wrapper entrypoint
unchanged.

Closes #721.
2026-06-02 02:47:30 +05:30
pewdiepie-archdaemon 90b64f32c9 Stabilize auth session revocation tests 2026-06-02 06:02:49 +09:00
Lohinth ae37a3103e Scope document tools to caller owner
Co-authored-by: Lohinth <lohinth25@proton.me>
2026-06-02 06:00:02 +09:00
Ernest Hysa 3969e059fa Scope skill mutations to caller owner
SkillsManager.update_skill walks every SKILL.md on disk and matches by
slug only; the 'owner' key in its scalar_keys whitelist meant a caller
could pass updates={'owner': 'attacker', 'description': 'pwned'} and the
first matching file on disk got silently re-owned. Two users with the
same slug under different category directories (which is supported by
the on-disk layout <category>/<name>/SKILL.md) could each stomp the
other's skill via the manage_skills tool or the in-process callers in
tool_implementations.py (edit, patch, publish, delete).

update_skill and delete_skill now require the caller's owner and only
match a file whose parsed owner field matches. The default of None
means 'no scope' and only matches ownerless skills, so an unsafe call
without an explicit owner is now a no-op. 'owner' is also removed from
scalar_keys so the updates dict cannot be used to reassign ownership
even when the manager is called from an in-process path that didn't
supply the owner argument.

The in-process callers in tool_implementations.py are updated to pass
owner=owner (which was already in scope at every call site) so the
HTTP and agent paths both go through the scoped check. The HTTP route
at routes/skills_routes.py:1499 was already owner-scoped via
sm.load(owner=user); the fix brings the in-process path up to the
same standard.
2026-06-02 05:59:43 +09:00
Alexandre Teixeira 6023c85559 Revoke stale sessions after password change
After a successful password change, revoke all browser sessions for the
same user except the one that submitted the request. This prevents stale
sessions on other devices from remaining valid after credentials are
updated.

Keep API-token behavior unchanged. The current browser session is
preserved so the user can continue from the tab that changed the
password.

Add focused regression tests for preserving the current session, revoking
other sessions, persisting revocation, and avoiding revocation when the
current password is incorrect.
2026-06-02 05:59:22 +09:00
SurprisedDuck 470712fdcf Reserve internal sentinel usernames
`core.middleware.require_admin` grants admin to any request whose
`request.state.current_user == "internal-tool"` — the sentinel meant only
for the in-process tool-loopback path. But the normal cookie auth path
(app.py) sets `current_user` to the raw username, and neither `create_user`
nor the signup route reserved that name. As a result an account literally
named "internal-tool" was silently treated as admin by every
`require_admin`-gated route. With self-service signup enabled this is an
anonymous -> admin privilege escalation.

Reserve the full synthetic-owner set the codebase already special-cases —
"internal-tool", "api", "demo", "system" (see `_SYNTHETIC_OWNERS` in
routes/assistant_routes.py and the matching guards in src/task_scheduler.py
and routes/research_routes.py). "api" collides with the bearer-token owner
sentinel; "demo"/"system" would leave a real account denied an assistant
and inconsistently owner-scoped.

Refuse to create or rename into any reserved name (case/space-normalized),
and reject empty usernames while we're here. Adds a regression test.

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-02 05:58:58 +09:00
SurprisedDuck 55bc00a69a Sanitize preserved markdown HTML
`mdToHtml` deliberately stashes literal <details> blocks and <a> tags from
the source text *before* the global HTML-escape pass and restores them
verbatim into the string callers assign to `innerHTML` (e.g. chatRenderer's
`b.innerHTML = ...processWithThinking(text)`). Nothing scrubbed those
fragments, so message/agent content containing
`<details><img src=x onerror=...></details>` or
`<a href="javascript:..." onmouseover=...>` executed arbitrary script in
the authenticated page.

Route both stashed fragments through `sanitizeAllowedHtml()`, which parses
them in an inert <template> (no resource loads, no script execution),
removes script-capable elements, and strips event-handler attributes plus
javascript:/vbscript:/data: URL schemes. Hardening details:

- Compare tag names case-insensitively and drop the SVG/MathML foreign-
  content roots. An SVG-namespaced <script> has the lower-case tagName
  'script', so an HTML-only upper-case check would miss it — a real bypass.
- Sanitize to a fixpoint (re-parse + re-clean until stable) to blunt
  mutation-XSS, where re-serializing/re-parsing reshapes the tree.

Benign anchors and <details> blocks are preserved unchanged.

Verified under jsdom against the obvious vectors plus mutation-XSS probes
(svg/math-namespaced <script>, foreignObject, ns-confusion, comment
breakout, template smuggling): no script/iframe element, event handler, or
javascript:/data: URL survives, and benign markup is kept.

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-02 05:58:38 +09:00
ghreprimand 5d01559717 Harden backup restore tar extraction
Co-authored-by: ghreprimand <203024559+ghreprimand@users.noreply.github.com>
2026-06-02 05:55:03 +09:00