| 2026-10-09 |

profiles: restricted profiles for ordinary users, and a role gate that holds
...
`is_admin_only` was checked in one place out of nine and was not read from
config.json at all, so all seven profiles were reachable by every account with
role `user`. The flag now lives in config.json — the file is the baseline, a
`profile_overrides` row still wins on top of it — and one predicate,
`admin_only_blocked`, is the single place the rule is expressed. The nine
surfaces that list, switch to, spawn or resolve a profile all consult it:
`POST /sessions`, the WebSocket, switch_profile, list_profiles, the system
prompt's "Available profiles" block, spawn_agent and the Synapse reaction
runner. The prompt cache is now keyed by (profile, role), so a user's prompt
can never be served an admin's profile list.
The seven existing profiles (developer, discuss, dispatcher, modeler_3d,
navi_code, secretary, server_admin) are marked admin-only. Three new ones take
their place for ordinary users: assistant, designer_3d and coder. They share one
native tool set — ssh_exec, peer, reload_tools, create_mcp_server, test_mcp_tool,
image_view and gmail are withheld — and differ only in system prompt, model and
MCP groups. navi-web's raw `request` group, and the whole of gnexus-creds and
tgclient, are withheld too.
MCP per-user keys gain the missing half of the rule: a server that declares a
`user_key` slot is refused to anyone but an admin who has no personal key, and
is left out of their tool list entirely, instead of quietly falling back to the
owner's credential and appearing as a tool that cannot work. The refusal names
the server and points at Settings.
Also closes `GET /agents/prompts`, which served every profile's system prompt to
anyone, with no user dependency at all.
The accepted residual risk is written down in docs/profiles.md: the working
directory is a convention, not a sandbox.
Eugene Sukhodolskiy
committed
3 hours ago
|
| 2026-09-09 |

security: critical batch 1 — RCE/XSS/CORS/webhook hardening
...
Backend:
- auth/deps: fix refresh-lock clock race (cleanup now monotonic like the cache)
- core/registry: add missing structlog logger (NameError on fallback path)
- config: GNAUTH_WEBHOOK_SECRET, NAVI_ALLOWED_ORIGINS (+list property)
- webhooks: HMAC-SHA256 signature verification (503 unconfigured in auth mode,
unsigned+warning in no-auth mode, 403 bad/stale signature, 400 bad JSON)
- main: CORS from NAVI_ALLOWED_ORIGINS in auth mode (fail-fast on empty),
eval router behind require_admin, /debug only registered in no-auth mode
- auth routes: mobile-done sid validation (32 hex) + CSP header + safe JS
escaping (reflected XSS); Secure cookie flag via helper (https base URL)
- websocket: anonymous WS rejected in auth mode (closes legacy-session RCE);
socket registered only after access checks; stop_session auth gate
- messages: REST agent start now takes session lock + busy flag (409 on
active run), contextvars reset in finally
Webclient:
- useMarkdown: DOMPurify.sanitize on all rendered markdown (stored XSS),
image-URL scheme whitelist, delegated error listener (no inline onerror)
- html.html artifact viewer: sandbox without allow-same-origin (opaque origin)
- tests: DOMPurify runs under jsdom (happy-dom Node.prototype.nodeName getter
breaks DOMPurify); useWebSocket tests get localStorage stub + ui-kit alias
Tests: pytest 1049 passed, 1 skipped; vitest 61 passed
Eugene Sukhodolskiy
committed
29 days ago
|
| 2026-06-23 |
Navi Code TUI: fix Phase 4 review critical/medium issues
...
- PermissionEngine: always-deny returns None via check(); is_always_deny() exposed for explicit rejection; default rules for code_exec/ssh_exec/shell; extract_target() made public.
- ShellRunner: set ShellResult.truncated when output is actually truncated.
- File refs: restrict paths to base_dir/home, block sensitive files/dirs, skip binary, support glob brackets, shared guess_language.
- Permission dialog: deny now renders synthetic tool_call before stopping; shell ! commands require permission with always-allow/deny persistence.
- Tests: add permission tests, fix file_refs/shell tests, add __init__.py to fix pytest name collisions, update websocket integration for AgentSessionOrchestrator.
504 passed, 1 skipped. Ruff clean.
Signed-off-by: Eugene Sukhodolskiy <eugene.sukhodolskiy@gmail.com>
Eugene Sukhodolskiy
committed
on 23 Jun
|
| 2026-05-18 |
Extract single shared Database pool, eliminate 4 duplicated pool creations
...
- Create navi/db.py::Database managing one asyncpg pool
- KvStore, PgSessionStore, MemoryStore, RecallScheduler now accept pool in constructor
- AppContainer holds Database, shutdown closes one pool instead of 4
- create_container creates one pool and passes it to all stores
- All tests updated to set _initialized=True on fakes to skip DDL
392 passed, 1 skipped
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 18 May
|
Make Settings immutable (frozen=True) and fix all test mutations
...
- Add frozen=True to SettingsConfigDict in navi/config.py
- Convert model_validator to mode="before" since mode="after" cannot mutate frozen instances
- Replace all field-level monkeypatches in tests with whole-Settings object replacement
- Ensure cross-module settings consistency (content_store, session_files, share_file, content_publish, filesystem)
392 passed, 1 skipped
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 18 May
|

Replace global lazy singletons with explicit AppContainer + lifespan
...
- Create navi/core/container.py with AppContainer dataclass and create_container()
- Rewrite navi/api/deps.py: remove module-level singletons, add _container global
fallback + set_container(), use _resolve_container() for all getters
- Replace @app.on_event with @asynccontextmanager lifespan in main.py
- Update routes to use Depends(get_scheduler) instead of calling get_scheduler()
- Fix FastAPI body parsing bug: remove dataclass parameters from Depends getters
(FastAPI was treating AppContainer sub-dependencies as Body params, forcing
embed=True on all endpoint body params and causing 422 errors)
- Update websocket.py to use _resolve_container() instead of get_registries()
- Update integration test fixtures to build AppContainer and call set_container()
- Remove obsolete tests/unit/test_startup.py (tests removed _on_startup)
- Fix test_scheduler_loop.py fixture (get_registries no longer exists)
All 387 tests pass (excluding websocket hang tests).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 18 May
|
| 2026-05-15 |
Add self-recall (scheduled callback) system
...
Core features:
- schedule_recall tool: once/recurring/immediate callbacks
- manage_recall tool: cancel/skip/list scheduled recalls
- Natural-language time parser (ISO, relative, "tomorrow at 09:00")
- PostgreSQL-backed RecallScheduler with lazy pool init
- Background recall_scheduler_loop with asyncio.Semaphore(3)
- _busy_sessions guard prevents user messages during headless runs
- Agent.run() preserves thinking field for session history visibility
- API endpoints: GET/DELETE/POST for session recall, admin list
- Frontend: recall badge, filter, cancel/skip in sidebar and chat header
- Tests: parser, scheduler CRUD, tools, API, scheduler loop (53 tests)
- Manuals: schedule_recall.md and manage_recall.md
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 15 May
|
| 2026-05-11 |
Fix MCP tool lookup in websocket handler and tests
...
- websocket.py: pass mcp_manager to Agent(), with graceful fallback
if MCP connection fails
- conftest.py: mock get_mcp_manager() in tests to prevent SSE
connection attempts against real servers
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 May
|
| 2026-05-08 |
Add pagination, search, and sorting to admin sessions
...
Backend:
- Add count_all and search_list abstract methods to SessionStore
- Implement count_all and search_list in PgSessionStore (SQL with ILIKE)
- Implement count_all and search_list in InMemorySessionStore
- Update /admin/sessions to accept limit, offset, search, sort_by, sort_order
- Return {total, limit, offset, items} from /admin/sessions
Frontend:
- Add search input for sessions in admin panel
- Add clickable sortable column headers with asc/desc toggle
- Add pagination controls (prev/next, page size selector, item count)
- Debounce search input (300ms)
Tests:
- Add integration tests for pagination, offset, search, and sorting
- All 217 tests pass
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 8 May
|
| 2026-05-04 |
Fix WebSocket 403 — bypass FastAPI Depends for WS auth
...
- websocket.py: resolve user by calling get_current_user_ws directly
inside the handler instead of using Depends(). This avoids FastAPI
returning HTTP 403 on the upgrade request when auth resolution fails.
- websocket.py: accept WebSocket before access check, close with 4003
for auth failures instead of HTTP 403.
- auth/deps.py: remove debug logging from get_current_user_ws.
- tests/conftest.py: monkeypatch get_current_user_ws directly since
Depends() is no longer used on the WebSocket endpoint.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 4 May
|
| 2026-05-03 |
Multi-user auth via gnexus-auth OAuth + hybrid role/permission model
...
- Integrate gnexus-auth-client-py (GAuthClient) for OAuth flow, token refresh,
and webhook parsing
- Add navi/auth/ package: User model, Fernet encryptor, client singleton,
deps (get_current_user, require_admin, require_permission)
- New tables: navi_users, user_auth_sessions (auto-created on startup)
- Session/memory isolation by user_id with legacy NULL support
- Cookie-based auth proxy: /auth/login, /callback, /logout, /me
- Webhook receiver /webhooks/gnexus-auth handling user events, global logout,
session revocation, role/permission changes
- Admin endpoints (/admin/*) gated by role + permissions
- Webclient auth store with isAdmin/hasPermission guards
- Admin-only profile filtering in /agents/profiles
- 200/200 tests passing
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 3 May
|
| 2026-04-29 |
Add 62 tests across all planned phases + fix integration flakiness
...
- Phase 3: 19 API route integration tests (health, agents, sessions, messages)
- Phase 3: 7 WebSocket integration tests (connect, send, replay, invalid, stop)
- Phase 4: 9 agent tests (_check_context_size, _iter_stream_guarded)
- Phase 4: 5 planning tests (_parse_plan_steps)
- Phase 5: 22 tool tests (filesystem 13, code_exec 5, terminal 4)
- Fix flaky integration tests by patching module-level singletons
(_session_store, _registries, _workers) instead of getter functions,
because FastAPI Depends() captures the original function at import time.
- Update docs/testing.md coverage table (150 total tests)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 29 Apr
|