odysseus/src
Léo 2a540f2acc fix(runtime): enforce workspace confinement in one place
"Is this path inside that root" is asked in twenty places in this tree and
answered twenty times by a locally written realpath/commonpath pair. Nine test
files exist because nine call sites each needed their own proof. Each one is
defensible alone; together they are the defect, because the boundary has no
single definition and a site that gets a detail wrong is wrong by itself.

src/path_confinement.py is that definition, and it settles the details the
copies disagreed on. Both sides get canonicalized: comparing a realpath-ed
candidate against a root that was only abspath-ed is the macOS /tmp ->
/private/tmp mismatch that has already produced a false failure here, and
canonicalizing one side is worse than canonicalizing neither. commonpath rather
than startswith, because /a/bc begins with /a/b and is not inside it. A relative
candidate joins the root rather than os.getcwd(), which is whatever directory
the server happens to be running in. NUL and newline are refused with a reason
instead of caught by a bare `except Exception` and reported as an ordinary
escape. Eighteen call sites go through it now. It deliberately does not decide
whether a path is sensitive -- that deny list answers "allowed" rather than
"inside", and it stays with src/tool_execution, which owns it. The one
commonpath left in the tree, in src/workspace_paths.py, stays: that function
translates a host path into a container path, so canonicalizing either side
would change the relative path it computes and break the mapping. It is not a
confinement check.

Two of those sites were weaker than the rest and are fixed rather than moved.
The email attachment check used abspath, which folds `..` but does not resolve
symlinks, so a symlink written into the extraction directory passed it and was
then read through. The skill-reference guard compared a realpath-ed target
against a raw dirname, so on a host where the skills tree is reached through a
symlink the two sides never matched and the guard could not fire.

The execution boundary had two separate holes.

The workspace namespace bound /home and /mnt read-write. On the one platform
where that namespace engages at all, a command inside it reaches outside the
workspace and writes to the user's home directory -- measured by running this
argv on a Linux host with working bubblewrap, not inferred from the source.
Binding the user's whole home directory into a workspace-confinement namespace
gives back most of what the namespace was for. Both are read-only now. The
workspace is also bound writable at its real host path, not only at /workspace:
BashTool's own /tmp redirect rewrites `/tmp/` to `<agent_cwd()>/.tmp/` before
the namespace is built, so the command bwrap receives already names the real
path, and those writes previously landed only because the workspace happened to
sit under the writable /home.

`namespaced or _replace_workspace_alias(...)` chose between a mount namespace
and a regex with nothing in the result saying which one ran. The fallback
rewrites the literal token /workspace in the command string, so a command that
never mentions /workspace is untouched by it and runs on the host unrestricted
-- which is every agent shell command on macOS. Both tools now ask
containment.probe() instead of each deciding for itself, and every bash and
python result carries a containment block naming the mechanism and stating
whether the filesystem dimension actually held. Under enforcing mode the
command is not run and the result says so.

That block reports the filesystem dimension only, and says so in a
reported_dimensions field. The probe knows this host could also give a process
group and a real wall clock, but these two tools still assemble their own
create_subprocess_* call and pass neither, so listing those dimensions would be
exactly the false claim src/containment.py calls worse than an honest absence.

probe() is new on src/containment.py: the same mechanism table and the same
arithmetic as acquire(), stopping before the side effects. acquire() is the
wrong shape for a decision -- it writes a durable grant record, and a record
whose pid is never filled in and whose release() never runs is an entry a
restart reaper keeps finding.

CONTAINMENT_MODE stays report_only. Flipping it refuses every agent shell
command on macOS and on any Linux host without bubblewrap, which is a product
decision rather than a code one.

Smaller things in the same area: the /tmp redirect's makedirs was unguarded, so
a read-only workspace turned a command that merely mentioned `/tmp/` into an
OSError traceback instead of a tool error; it degrades now. WORKSPACE_MOUNT
moved to src/constants.py so the namespace and the path resolvers read one
definition of the contract rather than two. The ".tmp" dirname got a constant,
since it appeared in both tool paths.

One generated artifact moved with it: website/configuration-reference.md pins
the source line where each ODYSSEUS_* variable is read, and three of those
shifted. Regenerated with scripts/generate_env_reference.py; the diff is line
numbers only.

Three existing tests changed. test_workspace_artifact_tool_floor asserted that
an unsafe interpreter prefix produces no `--ro-bind <prefix> <prefix>`, which
now fires on /home because /home is legitimately a read-only base mount.
Asserting the absence of a literal flag string cannot distinguish "the prefix
was rejected" from "the argv mounted that root itself", so it compares the argv
against the no-prefix baseline instead: an unsafe prefix must add nothing.

The Windows bash test asserted dict equality on the
whole result, which makes adding a field to every bash result impossible without
touching a test about tmux; it asserts the shape now. The personal-dir symlink
test grepped the resolver's source for the literal "os.path.realpath", which is
gone because the resolution moved into the shared boundary -- it keeps the
negative assertion that the closure must not grow its own abspath check again,
and the behavioural half now runs against the boundary, where it covers every
call site instead of one closure.

Not verified: the bubblewrap argv is asserted, not executed. There is no bwrap
on macOS, and in Docker it needs --privileged to work at all -- default and
seccomp=unconfined both fail with "Creating new namespace failed", and
--cap-add=SYS_ADMIN fails at pivot_root. The Python tool's
needs_virtual_namespace gate means ordinary Python code gets no namespace even
on a Linux host that could provide one; that is reported now but deliberately
not changed, because it alters the Linux Python path on every call and cannot be
checked from here.
2026-10-01 19:45:59 +02:00
..
agent_runtime fix: close Wave 1.1 completion-gate audit findings 2026-10-01 14:49:32 +01:00
agent_tools fix(runtime): enforce workspace confinement in one place 2026-10-01 19:45:59 +02:00
model_capability_readers fix(models): bind provider detection to DNS labels (#5961) 2026-08-16 23:25:46 +01:00
search refactor(search): make src analytics a service shim (#2264) 2026-06-04 18:57:24 +02:00
tools fix(runtime): verify process identity before any teardown signal 2026-10-01 18:55:50 +02:00
action_intents.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
agent_evidence.py merge: reconcile Wave 1.1 with post-PR40 lab 2026-10-01 09:09:55 +01:00
agent_loop.py merge: reconcile Wave 1.1 with post-PR40 lab 2026-10-01 09:09:55 +01:00
agent_runs.py Preserve preview harness, editor, email and task improvements 2026-10-01 01:34:26 +00:00
agent_trace.py Consolidate Odysseus agent harness and tool contracts 2026-09-17 10:07:40 +00:00
ai_interaction.py Preserve preview harness, editor, email and task improvements 2026-10-01 01:34:26 +00:00
api_key_manager.py fix: use atomic write in APIKeyManager.save() to prevent credential data loss (#4591) (#4597) 2026-06-23 23:28:53 +02:00
app_helpers.py fix(runtime): enforce workspace confinement in one place 2026-10-01 19:45:59 +02:00
app_initializer.py fix(stabilization): harden attachment lifecycle and agent guard signals (#5420) 2026-07-11 15:14:14 +01:00
assistant_log.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
attachment_refs.py fix(stabilization): harden attachment lifecycle and agent guard signals (#5420) 2026-07-11 15:14:14 +01:00
auth_helpers.py feat(auth): define Default/Local owner contract (#5795) 2026-08-15 20:27:26 +01:00
background_tool_jobs.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
bg_jobs.py fix(runtime): verify process identity before any teardown signal 2026-10-01 18:55:50 +02:00
bg_monitor.py fix: close Wave 1.1 completion-gate audit findings 2026-10-01 14:49:32 +01:00
browser_observation.py merge: reconcile PR 40 with current lab 2026-10-01 05:03:58 +01:00
builtin_actions.py Consolidate Odysseus agent harness and tool contracts 2026-09-17 10:07:40 +00:00
builtin_mcp.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
caldav_sync.py CalDAV: close the DAVClient on sync and write-back paths (#4793) 2026-07-11 13:03:24 +01:00
caldav_writeback.py CalDAV: close the DAVClient on sync and write-back paths (#4793) 2026-07-11 13:03:24 +01:00
chat_handler.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
chat_helpers.py Harden maintainer-preview harness and review fixes 2026-09-21 06:54:03 +00:00
chat_processor.py Consolidate Odysseus agent harness and tool contracts 2026-09-17 10:07:40 +00:00
chatgpt_subscription.py feat(ui): refine subscription model and usage controls 2026-09-22 13:12:19 +01:00
chroma_client.py fix: ChromaDB unreachable blocks app startup for 30-60s (#326) (#476) 2026-06-01 22:22:41 +09:00
clean_agent_preview.py merge: reconcile PR 40 with current lab 2026-10-01 05:03:58 +01:00
cleanup_service.py Replace cleanup service datetime.utcnow calls (#1494) 2026-06-03 14:14:27 +09:00
client_tool_contract.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
config.py fix(cleanup): update MODULE_SUMMARY and remove dead MEMORY_DOC paths (#4411) (#5160) 2026-07-11 17:06:19 +01:00
constants.py fix(runtime): enforce workspace confinement in one place 2026-10-01 19:45:59 +02:00
containment.py fix(runtime): enforce workspace confinement in one place 2026-10-01 19:45:59 +02:00
context_budget.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
context_compactor.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
cookbook_serve_lifecycle.py Polish mobile UI and editor workflows 2026-06-27 13:05:44 +00:00
copilot.py fix(copilot): guard request_flags against a non-dict last message (#5274) 2026-07-08 23:57:23 +02:00
database.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
deep_research.py Consolidate Odysseus agent harness and tool contracts 2026-09-17 10:07:40 +00:00
document_actions.py Merge remote-tracking branch 'origin/dev' 2026-07-01 10:11:22 +00:00
document_processor.py Consolidate Odysseus agent harness and tool contracts 2026-09-17 10:07:40 +00:00
email_attachment_text.py Preserve preview harness, editor, email and task improvements 2026-10-01 01:34:26 +00:00
email_calendar_import.py Consolidate Odysseus agent harness and tool contracts 2026-09-17 10:07:40 +00:00
email_reply_stream.py Preserve preview harness, editor, email and task improvements 2026-10-01 01:34:26 +00:00
email_task_intent.py Preserve preview harness, editor, email and task improvements 2026-10-01 01:34:26 +00:00
email_thread_parser.py Ignore non-string email thread bodies (#1654) 2026-06-03 14:06:31 +09:00
embedding_lanes.py fix(embeddings): survive numpy embeddings when restoring a reset lane (#3410) 2026-06-09 10:40:17 +02:00
embeddings.py Harden maintainer-preview harness and review fixes 2026-09-21 06:54:03 +00:00
endpoint_resolver.py Consolidate Odysseus agent harness and tool contracts 2026-09-17 10:07:40 +00:00
event_bus.py Consolidate Odysseus agent harness and tool contracts 2026-09-17 10:07:40 +00:00
exceptions.py refactor(exceptions): dedupe src/exceptions via core re-export (#4785) 2026-06-24 16:50:07 +02:00
execution_capabilities.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
fixture_email.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
foreground_model_routing.py feat(provider): support multiple ChatGPT subscriptions with usage 2026-09-22 13:12:19 +01:00
generated_images.py fix(runtime): enforce workspace confinement in one place 2026-10-01 19:45:59 +02:00
generation_budget.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
generation_sampling.py Consolidate Odysseus agent harness and tool contracts 2026-09-17 10:07:40 +00:00
goal_based_extractor.py fix(deep-research): wrap fetched webpage content in untrusted-context sandbox 2026-06-06 03:37:10 -06:00
host_docker_access.py fix(docker): make host Docker socket opt-in (#4902) 2026-06-30 19:54:51 +02:00
image_model_ids.py Preserve preview harness, editor, email and task improvements 2026-10-01 01:34:26 +00:00
index_walk.py fix(rag): skip hidden and junk directories when indexing (#5633) 2026-07-23 14:18:08 +02:00
integrations.py fix(agent): authorize exact actions after untrusted context 2026-08-15 05:37:47 +00:00
interactive_gate.py fix: stop status polling from cancelling running scheduled tasks (#5789) 2026-08-14 10:47:47 +01:00
llm_core.py feat(provider): add lazy Featherless model discovery 2026-09-22 13:12:19 +01:00
markitdown_runtime.py Settings overhaul + UI polish pass 2026-06-10 15:15:13 +09:00
mcp_manager.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
mcp_oauth.py fix(mcp): stop assuming http://localhost:7000 for the OAuth callback (#6032) 2026-08-15 23:09:01 -06:00
media_ingress.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
memory.py fix(memory): don't let an unreadable store get overwritten with an empty one (#5831) 2026-08-06 02:33:50 -06:00
memory_provider.py fix(memory): don't let an unreadable store get overwritten with an empty one (#5831) 2026-08-06 02:33:50 -06:00
memory_vector.py fix: split Chroma embedding lanes (#3046) 2026-06-06 03:17:19 -06:00
model_capabilities.py feat(models): define capability schema and readers (#2739) 2026-07-18 09:40:58 +01:00
model_context.py Consolidate Odysseus agent harness and tool contracts 2026-09-17 10:07:40 +00:00
model_discovery.py fix(discovery): cache a successful but empty Tailscale lookup 2026-09-30 12:14:03 +02:00
model_pricing.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
model_profiles.py Preserve preview harness, editor, email and task improvements 2026-10-01 01:34:26 +00:00
office_doc.py Consolidate Odysseus agent harness and tool contracts 2026-09-17 10:07:40 +00:00
optional_deps.py fix(image): patch realesrgan torchvision compatibility (#4110) 2026-06-15 15:16:41 +09:00
outbound_fetch.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
owner_identity.py feat(auth): define Default/Local owner contract (#5795) 2026-08-15 20:27:26 +01:00
path_confinement.py fix(runtime): enforce workspace confinement in one place 2026-10-01 19:45:59 +02:00
pdf_form_doc.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
pdf_forms.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
pdf_runtime.py Show a clear message when PyMuPDF is missing 2026-06-01 18:27:17 +09:00
personal_docs.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
preset_manager.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
process_ownership.py fix(runtime): verify process identity before any teardown signal 2026-10-01 18:55:50 +02:00
process_reaper.py fix(runtime): verify process identity before any teardown signal 2026-10-01 18:55:50 +02:00
prompt_security.py fix(agent): close untrusted-context gate bypasses 2026-08-15 01:58:32 +00:00
rag_manager.py fix: resolve RAG manager search signature TypeError (#4994) 2026-07-03 15:07:16 +01:00
rag_singleton.py feat(paths): abstract runtime path logic for frozen distribution packages (#969) 2026-06-15 17:44:10 +01:00
rag_vector.py fix(rag): skip hidden and junk directories when indexing (#5633) 2026-07-23 14:18:08 +02:00
rate_limiter.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
readiness.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
reminder_personas.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
request_models.py feat(ui): refine subscription model and usage controls 2026-09-22 13:12:19 +01:00
research_handler.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
research_navigator.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
research_utils.py Consolidate Odysseus agent harness and tool contracts 2026-09-17 10:07:40 +00:00
runtime_paths.py feat(paths): abstract runtime path logic for frozen distribution packages (#969) 2026-06-15 17:44:10 +01:00
search_intent.py Avoid invented publication windows on corrected version lookups 2026-09-17 20:53:01 +00:00
search_passages.py Preserve all fetched search sources before transport truncation 2026-09-17 22:00:44 +00:00
secret_storage.py refactor(constants): single source of truth for data dir (#3368) 2026-06-08 09:58:52 +02:00
service_health.py feat(diagnostics): add consolidated service health endpoint for degraded-state reporting (#964) 2026-06-09 16:00:24 +01:00
session_actions.py fix(sessions): keep fresh chats during auto tidy (#1871) 2026-06-09 01:06:20 +01:00
session_image_cleanup.py fix(runtime): enforce workspace confinement in one place 2026-10-01 19:45:59 +02:00
session_search.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
settings.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
settings_scrub.py fix(settings): scrub camelCase secret keys (#3707) 2026-06-11 12:53:33 +02:00
skill_index.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
task_action_policy.py fix(tasks): gate cookbook serve task execution (#5235) 2026-07-05 13:19:04 +01:00
task_endpoint.py Consolidate Odysseus agent harness and tool contracts 2026-09-17 10:07:40 +00:00
task_scheduler.py fix: close Wave 1.1 completion-gate audit findings 2026-10-01 14:49:32 +01:00
teacher_escalation.py fix(runtime): isolate nested invocation ownership 2026-10-01 02:11:53 +01:00
text_helpers.py fix(security): prevent ReDoS in LLM-output tool/think parsers (#4704) 2026-06-27 10:12:28 -07:00
theme_palette.py Preserve preview harness, editor, email and task improvements 2026-10-01 01:34:26 +00:00
tls_overrides.py Support extra CA bundle for private-CA LLM providers (#769) 2026-06-04 13:18:50 +01:00
tool_approval_scopes.py fix(agent): allow remaining actions for an approved task (#6113) 2026-08-19 08:01:34 -06:00
tool_approvals.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
tool_capabilities.py merge: reconcile PR 40 with current lab 2026-10-01 05:03:58 +01:00
tool_execution.py fix(runtime): enforce workspace confinement in one place 2026-10-01 19:45:59 +02:00
tool_implementations.py Merge dev into main for testing 2026-06-28 14:07:23 +00:00
tool_index.py Preserve preview harness, editor, email and task improvements 2026-10-01 01:34:26 +00:00
tool_parsing.py fix(agent): preserve tool evidence through final synthesis 2026-09-18 13:29:54 +00:00
tool_policy.py Preserve preview harness, editor, email and task improvements 2026-10-01 01:34:26 +00:00
tool_routing_experiment.py Harden maintainer-preview harness and review fixes 2026-09-21 06:54:03 +00:00
tool_schemas.py merge: reconcile PR 40 with current lab 2026-10-01 05:03:58 +01:00
tool_security.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
tool_types.py Preserve preview harness, editor, email and task improvements 2026-10-01 01:34:26 +00:00
tool_utils.py fix(stabilization): harden attachment lifecycle and agent guard signals (#5420) 2026-07-11 15:14:14 +01:00
topic_analyzer.py Topics: hydrate session history before analysis 2026-06-02 20:44:27 +09:00
turn_contract.py merge: reconcile PR 40 with current lab 2026-10-01 05:03:58 +01:00
upload_handler.py fix(runtime): enforce workspace confinement in one place 2026-10-01 19:45:59 +02:00
upload_limits.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
url_safety.py fix(url-safety): resolve NAT64 well-known prefix to IPv4 target 2026-09-23 15:32:04 +01:00
url_security.py Reapply "Merge branch 'main' of github.com:pewdiepie-archdaemon/odysseus" 2026-06-03 22:47:00 +09:00
user_time.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
visual_report.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
web_recovery.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
webhook_manager.py fix(security): pin webhook delivery to the SSRF-validated IP (DNS rebinding) (#5147) 2026-07-04 17:03:38 +01:00
workspace_paths.py Squash Odysseus development history 2026-09-11 06:04:19 +00:00
youtube_handler.py fix(youtube): consolidate duplicate handler 2026-06-15 15:03:41 +09:00