Repair SearXNG system and certifi stores, validate native amd64/arm64 runtime images through Compose, and publish the exact verified artifacts through a trusted CI job. Adopt released Python, certifi, grpcio, uv and Docker CLI trust fixes, with strict controls for all fourteen public incident chains.
Part of #6784 and #6790.
Co-authored-by: Léo <leograndcontact@gmail.com>
#6683 made an exact approval seal a launch boundary (and a filesystem root)
in the request's workspace, but only when a workspace is bound. Agent mode
with no folder picked is the default, and there the approval still sealed
nothing, so "Allow" always ended in "Approved action has no sealed
process/job identity" (bash/python) or "Approved filesystem action has no
sealed resource identity" (write_file). In Docker every agent turn carries
the backend runtime context, so bash and write_file always ask for approval
and never ran.
With no workspace, seal the root in the agent workspace, which is where
agent_cwd() already runs bash/python in that case. Inherited (child)
authorities still cannot widen.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The manage_endpoints agent tool accepts arbitrary user-supplied base_urls
and writes them to the database without calling check_outbound_url(),
while the HTTP route (POST /api/model-endpoints) enforces full SSRF
hardening. A prompt-injection payload smuggled into a skill, note, fetched
page, or email could instruct the model to register a cloud-metadata or
internal-network endpoint via this tool path.
Add the same check_outbound_url() guard used by the HTTP route to the
agent tool's 'add' action. Also sanitize the error message in the except
block to avoid leaking raw exception text.
Add 8 regression tests covering: cloud metadata, non-HTTP schemes,
loopback (local-first), strict mode, safe URLs, empty URLs, gopher
scheme, and error message sanitization.
Co-authored-by: isharak7m <192635824+isharak7m@users.noreply.github.com>
* fix: block SSRF via user-supplied model endpoint URLs
* fix(test): stub DNS in loopback SSRF tests for deterministic results
Replace environment-dependent test_loopback_always_rejected with two
deterministic tests using a stub resolver:
- test_loopback_accepted_by_default (local-first, block_private=False)
- test_loopback_rejected_in_strict_mode (block_private=True)
The old test only passed when localhost resolved to ::1 (hit is_reserved),
not because loopback was actually always rejected.
Co-Authored-By: Santoshkumarpuppala
* fix: guard PATCH base_url against SSRF and add ipv6 loopback coverage
---------
Co-authored-by: isharak7m <192635824+isharak7m@users.noreply.github.com>
Reject the published compromised intermediates and cross-signed G3 issuer when supplied as trust anchors. Add explicit CA-override inspection and offline chain regressions with a fixed verification time and affected positive control.
- Propagate preflight HTTPException(status_code=502) regardless of whether other mailbox groups exported messages into the temporary ZIP
- Guarantee temporary file cleanup via SpooledTemporaryFile.close() on preflight failure
- Preserve partial success (HTTP 200) for genuinely missing mail
- Add executable tests for multi-group preflight failure orderings, missing mail, and zero body fetches
- Require valid RFC822.SIZE preflight metadata for every message before body FETCH
- Fail safely with HTTP 502 without body fetching on NO, malformed, or inconsistent sizes
- Distinguish missing mail (HTTP 404) from bad size preflight (HTTP 502)
- Discard duplicate IMAP response records for already-processed UIDs in batch loop
- Correct print notification toast to "Prepared N emails for printing; M unavailable"
- Add dedicated Playwright browser regression test script to repository test suite
- Add request-level tests for preflight NO, empty, partial, malformed, and duplicates
- Use SpooledTemporaryFile with 32 MiB spool threshold for export ZIP archives
- Add IMAP size preflight via (UID RFC822.SIZE) and cap batch payload at 10 MiB
- Enforce 64 MiB per-message ceiling and fail closed with 500 on corrupted ZIP
- Stream spooled archive chunks with generator cleanup on disconnect/cancellation
- Bypass ZIP creation for single raw EML message downloads
- Memoize account ownership checks per request to eliminate N+1 database queries
- Replace interactive image placeholders in print view with clean text notices
- Strip interactive buttons, icons, and image actions from print document
- Implement partial print success policy printing loaded items and reporting omitted
- Harden print iframe lifecycle with window afterprint listener and fallback timer
- Correct mid-batch exception accounting so exported plus skipped equals requested
- Add executable behavioral tests for limits, mid-batch accounting, and memoization
- Validate account ownership with _assert_owns_account for every account_id
- Deduplicate items in selection to prevent duplicate files and skewed accounting
- Fix exception handling in IMAP batching to avoid double-counting on partial failures
- Re-raise HTTPException so 413 memory cap errors are not swallowed
- Remove unused addField helper in emailLibrary print exporter
- Add behavioral test suite covering empty/oversized validation, rfc822 export,
zip export, partial failure/dedup, and cross-tenant account isolation
SAN-157 removed the html2pdf client library. The PDF export code added
in the original PR commit referenced the deleted library and would fail
at runtime. Replace the html2pdf-based _downloadSelectedEmailPdf with a
browser Print / save PDF workflow using a sandboxed iframe and
window.print(), matching the established pattern in document.js and
compare/index.js.
Changes:
- Replace _loadEmailHtml2Pdf and _downloadSelectedEmailPdf with
_printSelectedEmails using a sandboxed iframe + contentWindow.print()
- Update menu label from 'PDF' to 'Print / save PDF'
- Add 512 MiB total size cap to /messages-download-bulk to prevent
unbounded memory growth when exporting many large messages
- Update test assertions to verify the browser print pattern and
confirm html2pdf is not referenced
- Add regression tests for size cap, selection count validation,
non-numeric UID rejection, and partial batch failure accounting
Catch OverflowError in addition to TypeError and ValueError when converting
research_max_tokens, research_extraction_timeout_seconds, and
research_extraction_concurrency to int. Non-finite numbers parsed from JSON
(such as 1e309 or -1e309 representing float infinity) raise OverflowError on
int() conversion, which previously escaped the handler and crashed scheduled
research execution. Also add unit tests covering exponent overflow values.
POST /api/tasks and the scheduler both refuse action tasks whose action
is in ADMIN_ONLY_TASK_ACTIONS (run_local, run_script, ssh_command,
cookbook_serve) for owners without admin task privileges: those actions
reach subprocess.run(shell=True) or SSH with no sandbox. The model-facing
manage_tasks tool applied neither check, so an agent could create or edit
such a task, resume it, or trigger it, and only the scheduler's
execution-time guard stood in the way (pausing the task after the fact).
Route the tool through the same src.task_action_policy helpers the HTTP
route uses: create checks the requested type/action, edit checks the task
as it would be after the edit (so an llm task cannot be flipped into a
shell action), and resume/run check the stored task. Admins, non-
privileged actions and llm/research tasks are unaffected; pause still
works for everyone since it only narrows.
Fixes#6021
Claude-Session: https://claude.ai/code/session_01UAB5sz5srMKR7rbeDFtdMH
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
Setup, configuration and deployment docs live in website/, which is
published as the docs site, but the map sent them to /docs and did not
list website/ at all. Add it, and drop the "when those paths are used"
hedge on /specs, which exists.
CODEOWNERS has no rules until #593 settles the per-area map; say so
instead of describing reviewers it does not list.
CONTRIBUTING.md still had a single maintainer curating main, which the
Release Maintainer section replaces. Name the Product Owner's GitHub
account and say where a community proposal starts, matching the
existing issue forms.
Adapt the agreed maintainer responsibilities and risk-based review policy for the public repository. Add evidence-driven security review, CodeQL triage, release guidance and a discoverable contributing link.
* fix(upload): honor no-login mode after account setup
* ci: refresh checks against the current dev base
* test(upload): add regression tests for unconfigured auth and no-login POST
* fix(upload): fail closed on missing auth_manager and preserve cross-user isolation
Enforce strict cross-user isolation so that authenticated users cannot access
files owned by other accounts when app.state.auth_manager is absent or None.
Fail closed with HTTP 503 for non-loopback requests when AUTH_ENABLED=true and
authentication infrastructure is unexpectedly unavailable, while preserving
intended single-user mode (AUTH_ENABLED=false) and intentional first-run setup
mode (auth_manager.is_configured=False).
* fix(upload): eliminate fail-open paths for unconfigured manager and loopback transport
- Eliminate early return on unconfigured auth_manager (Finding A): fail closed with HTTP 401 Setup required
- Eliminate loopback transport bypass when auth_manager is missing (Finding B): fail closed with HTTP 503
- Enforce strict ownership isolation: non-owners and legacy ownerless uploads require verified administrator access
- Add regression suites in tests/test_upload_no_login.py covering Finding A, Finding B, bearer tokens, and ownerless files
- Explicitly set AUTH_ENABLED=false for unauthenticated tests in tests/test_upload_routes_owner_scope.py
* fix(upload): prevent delegated API tokens from inheriting admin privileges and enforce upload scopes
- Prevent delegated bearer API credentials from inheriting interactive administrator privileges over other users' files or legacy ownerless files.
- Enforce granular upload scopes for delegated credentials: chat and documents:write for upload creation and vision OCR editing; chat, documents:read, and documents:write for downloads, thumbnails, and vision OCR reads.
- Reject requests with missing token owners or reserved sentinel identities (api, demo, system, internal-tool) with 403 Forbidden.
- Add comprehensive regression tests in tests/test_upload_no_login.py verifying delegated token scope isolation, sentinel rejection, owner isolation, and untouched OCR state on rejected edits.
---------
Co-authored-by: PR #6029 Reviewer <reviewer@odysseus-review.local>
* refactor(notes): decompose Notes into feature-owned modules
* fix(notes): avoid HTML interpolation in reminder picker
* docs(notes): drop checkpoint archives, session logs and publication drafts
The N0-N6 tarballs and raw pytest/node logs are not read by any script or
test, and the logs carry absolute home-directory paths. Once squashed into
dev they would stay in history for good. notes-pr.md and notes-issue.md
were the drafts used to open #6712 and #6713, not documentation.
notes-validation.md keeps the counts, the checkpoint hashes and
implementation.sha256 as the record; local interpreter paths and the local
Docker image name are replaced with portable placeholders.
* fix(notes): keep the state store raw and stop masonry re-observing after close
- state: writes built from views (filter, spread, splice) stored nested
read-only views in the store, so structuredClone on a stored note threw
DataCloneError. Store writes now unwrap nested views.
- layout: a save that resolves after closePanel re-renders the leaving pane
and re-created the masonry ResizeObserver that teardown had disposed.
Skip creating it once Notes is closed.
- index.js: drop 30 imported or declared names that were never used and
group the imports at the top. Import order is unchanged.
- Revert unrelated punctuation edits in .gitattributes and keep the header
comment and docstring first in compare/vote.js and
test_notes_dom_xss_helpers.py.
Both fixes have a node test that fails on the previous head.
* test(notes): make script rejection assertion case-insensitive
Address CodeQL js/bad-tag-filter alert #1150 in test-only assertion and refresh Notes evidence manifest checksum.
---------
Co-authored-by: Léo <leograndcontact@gmail.com>
* fix(docker): mount the ChromaDB volume where the image actually persists
chromadb/chroma runs `chroma run /config.yaml`, and that config is one line:
persist_path: "/data". The image declares no VOLUME, so everything the store
writes lives in the container's writable layer. The chromadb-data volume was
mounted at /chroma/chroma — where pre-1.0 images kept the store — so it caught
nothing and the entire vector store was destroyed on every container recreate.
Nothing errors when that happens. The collection survives with a count of 0, so
retrieval simply returns nothing from then on, which reads as "no match".
docs/backup-restore.md tells users to archive the chromadb-data volume, which
means the documented backup was empty too.
Fixed in all three compose files. The volume was never written to, so there is
nothing to migrate.
* fix(tests,docs): harden volume-path test helper and document upgrade migration
tests/test_chromadb_volume_path.py
- _chromadb_volumes() now normalises long-form dict volume entries
(source/target/type map) to 'source:target' strings, preventing
AttributeError crashes if a future Compose change uses that syntax.
- test_chromadb_does_not_use_the_pre_1_0_path now uses split(':')[1]
comparison instead of endswith(), so mount-option suffixes (:z, :ro)
cannot mask a wrong legacy target path.
- Three new unit tests validate the helper against both YAML syntaxes
and the mount-option edge case explicitly.
docs/chromadb-volume-migration.md (new)
- Step-by-step guide for users upgrading from pre-#6166 Docker installs
whose ChromaDB data accumulated in the container's writable layer.
- Covers: detecting the situation, backing up via 'docker cp', seeding
the named volume before recreating the container, verification, and
rollback. Validated with disposable Docker volumes.
- Distinguishes fresh installs (no action needed) from existing
installs with live writable-layer data (action needed before recreate).
docs/backup-restore.md
- Extended ChromaDB caveat block to cross-reference the new migration
guide for users upgrading from pre-#6166.
Closes the P1 documentation gap and P2/P3 test-robustness findings
from the PR #6166 adversarial review.
* fix(docs,tests): eliminate ChromaDB migration data-loss risks and enforce safe cutover
- Require service quiescence before cold database copy to prevent torn SQLite pages
- Mandate container preservation via immutable Docker image commit before recreation
- Enforce positive empty check on destination volume prior to seeding
- Add staging container validation on an isolated loopback port before Compose cutover
- Provide verified rollback procedures prohibiting return to legacy /chroma/chroma mount
- Add regression test coverage for all migration safety invariants
---------
Co-authored-by: PR #6166 Reviewer <reviewer@odysseus-review.local>
* fix(security): deny generated-image access when ownership verification fails
The except Exception block in serve_generated_image catches all
exceptions during ownership verification and falls through to serve
the file anyway. If the database is down or corrupted, the ownership
check is bypassed entirely — any authenticated user can download any
generated image by guessing the content-hash filename.
Raise HTTP 503 (Service Unavailable) instead of falling through,
so the endpoint fails closed when verification is impossible. This
matches the defense-in-depth principle: deny access on verification
errors rather than granting it.
* fix(security): enforce fail-closed ownership and secure caching for generated images
- Fail closed on missing gallery records, null/empty owners, and DB errors
- Support both cookie session and bearer API token owners via effective_user
- Add in-memory registry for freshly generated images to preserve access
- Eliminate file existence oracle by verifying ownership before disk probing
- Update Cache-Control to private, no-cache, must-revalidate
- Pass owner attribution through tool execution and image MCP server
- Add comprehensive hermetic regression tests for ownership gates and caching
* fix(security): sanitize tool owner arguments and drop unneeded MCP env fallback
- Remove redundant and unsafe ODYSSEUS_MCP_IMAGE_OWNER fallback in image_gen_server.py to fix 4 tests in test_env_reference.py.
- Sanitize caller-supplied tool arguments across all MCP and email tool dispatch paths in src/tool_execution.py: strip untrusted _odysseus_* keys from arguments.
- Only inject _odysseus_owner when verified trusted server context is present, preventing untrusted/anonymous users from spoofing ownership claims.
- Sanitize empty/whitespace owner strings in image_gen_server.py to None.
- Add comprehensive regression tests in test_generated_image_ownership.py for argument sanitization, owner injection, fallback propagation, and server sanitization.
---------
Co-authored-by: isharak7m <192635824+isharak7m@users.noreply.github.com>
Co-authored-by: PR #6166 Reviewer <reviewer@odysseus-review.local>
* fix(auth): honor AUTH_ENABLED=false in admin-gated app-config routes
With auth explicitly disabled (reverse-proxy deployments) there is no
session user, so the inline admin checks in auth_routes.py returned 403
unconditionally and settings changes were silently lost. The central
require_admin in core/middleware.py already allows this case; the inline
checks did not.
- add _require_admin_user helper mirroring require_admin semantics and
use it in the app-config routes (settings, features, integrations)
- return unscrubbed settings on GET /settings when auth is disabled
(the caller is the operator)
- settings.js: check res.ok in saveDefault so a failed save no longer
reports 'Saved'
- add regression tests
User-management routes keep requiring a real admin identity; they act on
the user store, which is not meaningful with auth disabled.
Fixes#6028
* fix(settings): verify response.ok on save and expand auth regression tests
- Ensure all settings save handlers in settings.js check response.ok before displaying success notifications, preventing false 'Saved' messages on server rejection.
- Add DOM and JS unit tests verifying response.ok checks and error handling across settings handlers.
- Expand tests/test_admin_routes_auth_disabled.py with comprehensive authorization test coverage for AUTH_ENABLED=false and AUTH_ENABLED=true across settings, features, integrations, secret scrubbing, and user management.
---------
Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
Co-authored-by: PR #6029 Reviewer <reviewer@odysseus-review.local>
* fix(history): reject implicit and coerced truncation targets
* fix(history): reject falsy non-string message IDs; drop redundant keep_count guard
The or-chain used to resolve before_msg_id / message_id evaluated
falsy non-strings (False, 0) to '' before the isinstance(…, str) check
was reached, so they silently bypassed type validation. Replace the
chain with explicit presence-aware logic that falls through on None or
empty string (preserving alias semantics) but rejects any other
non-string value with HTTP 400.
Also remove the now-tautological 'if keep_count >= 0:' guard, which
is always True after the negativity check introduced in the parent
commit. The DB block is unconditional and indented accordingly.
Tests: add {'before_msg_id': False}, {'before_msg_id': 0},
{'message_id': 0} to the parametrised rejection suite.
36 passed, 0 failed (Python 3.14.4 / pytest 9.1.1).
---------
Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
Skills tagged remote-only in frontmatter are excluded from the injected
skill index when the active model's endpoint is local (loopback/private/
tailscale per is_local_endpoint). Big on-demand reference lookups
overwhelm small local contexts; local models never see the skill, while
remote models and API/UI listings are unaffected.
- SkillsManager.index_for gains optional keyword-only local_model flag
(None = don't gate, same contract as active_toolsets=None)
- _build_system_prompt/_build_base_prompt thread endpoint_url from
_build_route_request_state; unknown endpoint fails open
- tests: cover hidden-on-local, visible-on-remote/unknown, untagged
skills never gated
Refs: discussed for the local-model overwhelm problem
Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
With Auto-approve OFF the agent loop asked get_relevant_skills for
min_confidence=2.0, meant as 'published only'. The gate applies the
confidence check to published skills too, and confidence is 0-1, so no
user skill was ever injected, published or not. The explicit-activation
path already treats OFF as status == published.
Add a published_only flag to get_relevant_skills and pass it from both
callers that used the sentinel (relevance injection and the skill
preflight). Auto-approve ON is unchanged: a skill still needs a passing
audit and the confidence threshold.
Fixes#6634