security: auditoria completa 0.8 — vulnerabilitats, hardening i tests#2
Merged
Conversation
aa487bd to
65c88c2
Compare
…cepts
- memory/rag/routers/endpoints.py: ALLOWED_UPLOAD_EXTENSIONS whitelist,
filename sanitization (Path.name) to prevent path traversal,
generic 500 error messages (no internal details exposed)
- core/endpoints/root.py: /health/ready returns only {status, timestamp}
to avoid exposing internal module list without auth
- plugins/web_ui_module/session_manager.py: implement cleanup_inactive()
with timedelta TTL to prevent session memory leak
- memory/memory/engines/persistence.py: QDRANT_API_KEY env var support
for authenticated Qdrant deployments
- core/endpoints/chat.py: log JSONDecodeError in Ollama stream,
handle asyncio.CancelledError on client disconnect
- plugins/web_ui_module/memory_helper.py: replace bare except with
except Exception + debug logging
- .env.example: document all env vars including QDRANT_API_KEY
- .dockerignore: exclude .env, storage/, venv/, .git/ from Docker image
- .github/workflows/ci.yml: pip-audit CVE scan + unit tests
- core/endpoints/tests/test_security.py: 14 security regression tests
Assisted by AI
65c88c2 to
d80343a
Compare
jgoy-labs
added a commit
that referenced
this pull request
Mar 7, 2026
Issue 8 - {{NEXE_*}} templates substituïts per IDs literals:
- memory/embeddings: {{NEXE_EMBEDDINGS_MODULE}} -> 'embeddings'
- memory/memory: {{NEXE_MEMORY_MODULE}} -> 'memory', dependencies corregides
- memory/rag: {{NEXE_RAG_MODULE}} -> 'rag'
- plugins/security: {{NEXE_SECURITY_MODULE}} -> 'security'
- core/cli/manifest.toml: comentari corregit (cli)
Issues 9+10 - URLs hardcoded restants (lifespan.py + ollama_module):
- lifespan.py:96 unificat NEXE_OLLAMA_HOST > OLLAMA_HOST (fallback)
- lifespan.py:247,585 usen NEXE_OLLAMA_HOST
- lifespan.py:482,564,565 usen NEXE_API_BASE_URL / config
- ollama_module/module.py:420 usa NEXE_API_BASE_URL
Issue 11 - datetime.now() -> datetime.now(timezone.utc):
- core/endpoints/bootstrap.py, root.py
- plugins/security/core/logger.py
- plugins/web_ui_module/session_manager.py, memory_helper.py
- memory_helper.py: handle naive datetimes antics (replace tzinfo)
- test_security.py actualitzat per usar datetime.now(timezone.utc)
Issue 12 - Variables d'entorn sense prefix NEXE_ unificades:
- BOOTSTRAP_TTL -> NEXE_BOOTSTRAP_TTL (amb fallback a nom antic)
- AUTO_CLEAN_ENABLED -> NEXE_AUTO_CLEAN_ENABLED (idem)
- AUTO_CLEAN_DRY_RUN -> NEXE_AUTO_CLEAN_DRY_RUN (idem)
- OLLAMA_HOST mantingut com a fallback de NEXE_OLLAMA_HOST
Issue 13 - .env.example: variables que faltaven documentades:
- NEXE_BOOTSTRAP_DISPLAY, NEXE_BOOTSTRAP_TTL
- NEXE_AUTO_CLEAN_ENABLED, NEXE_AUTO_CLEAN_DRY_RUN
- NEXE_QDRANT_HEALTH_TIMEOUT
- OLLAMA_HOST (com a fallback comentat)
Tests: 954 passed, 0 failures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jgoy-labs
added a commit
that referenced
this pull request
Mar 9, 2026
#3 loader: fallback només considera classes definides al mòdul (afegit attr.__module__ == module.__name__ per evitar classes importades) #2 singletons: afegits reset_config() i reset_loader() per testing #1 core/app.py: import os mogut al bloc d'imports del principi #4 loader.py: docstrings traduïts de català a anglès (unificació open-source) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jgoy-labs
added a commit
that referenced
this pull request
Apr 12, 2026
Fix-All BUS sobre 3 tracks paral·lels per resoldre tots els bugs del QA post-BUS de normalització abans del DMG v0.9.0. 8 commits dev consolidats en aquest sync. TRACK A — Memory/RAG/Sessions - Bug #1 (PID file canònic) — single source of truth a storage/run/server.pid - F5 — 3 col·leccions canòniques (nexe_web_ui, user_knowledge, nexe_documentation) creades a get_memory_api() en lloc de només la primera - F7 — ingest_knowledge defaulteja a nexe_documentation (era user_knowledge) i és idempotent (eliminada la sequence delete_collection + create_collection destructiva que esborrava docs ad-hoc dels usuaris a cada install) - F8 — root cause empíric Bug #4: MemoryModule obria un SEGON QdrantClient real a storage/vectors/qdrant_local/, divergent del singleton del pool. MEM_SAVE escrivia a una col·lecció, MEM_RECALL llegia d'una altra. Ara tots dos comparteixen storage/vectors/. - F1 — _check_duplicate retorna contracte honest (success=False, duplicate=True) enlloc de fingir success=True amb document_id=None. Era el segon root cause de Bug #4: el dedup bloquejava SAVEs amb fals positius silenciats. - F2 — typo cols list (nexe_web_ui duplicat) - F3 — list_memories scroll-based (sense semantic search amb query anglesa) - Bug #10 — collections= filter a list/save/delete (sidebar checks reals) - Bug #6 — frontend hydration document attached. Eren 2 bugs encadenats: l'endpoint /history no retornava attached_document, i removeFilePreview() feia POST /clear-document destructiu cada switch de sessió. - Bug #3 — MEM_SAVE-only response fallback. Quan el model emet només [MEM_SAVE: ...] sense text envoltant, ara genera 'Memòria desada: <fact>' perquè el bloc save s'executi i el frontend mostri confirmació. - auto_save crida eliminada per HOMAD memoria v1 (2026-04-01) — manual MEM_SAVE only fins a Part 2. TRACK B — Tray / Multi-instance / Packaging - Bug #1 (PID file) compartit amb Track A - Bug #2 — setproctitle a server i tray (server-nexe / nexe-tray a ps/Activity Monitor). Force Quit encara mostra Python perquè requereix CFBundleName via .app bundle real (deute v0.9.1). - Bug #9 — menu polish: server-nexe.com duplicat substituït per '📖 Documentació' al main level (3 idiomes), website_item es manté al submenú Configuració. TRACK C — UX cosmètic - Bug #5 — slow_request middleware exclou /ui/upload (uploads naturalment triguen >1s i el log apareixia duplicat amb l'access log d'uvicorn). - Bug #8 — 3 ⓘ visibles als checkboxes del sidebar de col·leccions amb tooltips als 3 idiomes (la infraestructura CSS/i18n ja existia). Pytest D-1 final: 4424 passed, 0 failed, 35 skipped, 1 xfailed, 86% coverage en 76.11s. Baseline pre-BUS era 4396. +28 tests nous, ZERO regressions. Tests nous: - tests/test_pid_file.py: 7 tests Bug #1 - tests/test_ingest_knowledge_idempotent.py: 8 tests F7 (3 classes) - plugins/web_ui_module/tests/test_memory_helper_async.py: 1 test F1 - plugins/web_ui_module/tests/test_memory_delete.py: 7 tests F3+Bug#10 - plugins/web_ui_module/tests/test_mem_save_injection.py: 5 tests Bug #3 Out of scope (deute v0.9.1+): - routes_chat.py 54KB decapitació general (deute formal P0) - Bundle .app real amb py2app per CFBundleName (deute v0.9.1) - Resums per capítol (Part 2 redisseny memory) - RDBMS font de veritat + vector store reconstruïble (HOMAD memoria v1, Part 2)
jgoy-labs
added a commit
that referenced
this pull request
Apr 12, 2026
Fix-All BUS sobre 3 tracks paral·lels per resoldre tots els bugs del QA post-BUS de normalització abans del DMG v0.9.0. 8 commits dev consolidats en aquest sync. TRACK A — Memory/RAG/Sessions - Bug #1 (PID file canònic) — single source of truth a storage/run/server.pid - F5 — 3 col·leccions canòniques (nexe_web_ui, user_knowledge, nexe_documentation) creades a get_memory_api() en lloc de només la primera - F7 — ingest_knowledge defaulteja a nexe_documentation (era user_knowledge) i és idempotent (eliminada la sequence delete_collection + create_collection destructiva que esborrava docs ad-hoc dels usuaris a cada install) - F8 — root cause empíric Bug #4: MemoryModule obria un SEGON QdrantClient real a storage/vectors/qdrant_local/, divergent del singleton del pool. MEM_SAVE escrivia a una col·lecció, MEM_RECALL llegia d'una altra. Ara tots dos comparteixen storage/vectors/. - F1 — _check_duplicate retorna contracte honest (success=False, duplicate=True) enlloc de fingir success=True amb document_id=None. Era el segon root cause de Bug #4: el dedup bloquejava SAVEs amb fals positius silenciats. - F2 — typo cols list (nexe_web_ui duplicat) - F3 — list_memories scroll-based (sense semantic search amb query anglesa) - Bug #10 — collections= filter a list/save/delete (sidebar checks reals) - Bug #6 — frontend hydration document attached. Eren 2 bugs encadenats: l'endpoint /history no retornava attached_document, i removeFilePreview() feia POST /clear-document destructiu cada switch de sessió. - Bug #3 — MEM_SAVE-only response fallback. Quan el model emet només [MEM_SAVE: ...] sense text envoltant, ara genera 'Memòria desada: <fact>' perquè el bloc save s'executi i el frontend mostri confirmació. - auto_save crida eliminada per HOMAD memoria v1 (2026-04-01) — manual MEM_SAVE only fins a Part 2. TRACK B — Tray / Multi-instance / Packaging - Bug #1 (PID file) compartit amb Track A - Bug #2 — setproctitle a server i tray (server-nexe / nexe-tray a ps/Activity Monitor). Force Quit encara mostra Python perquè requereix CFBundleName via .app bundle real (deute v0.9.1). - Bug #9 — menu polish: server-nexe.com duplicat substituït per '📖 Documentació' al main level (3 idiomes), website_item es manté al submenú Configuració. TRACK C — UX cosmètic - Bug #5 — slow_request middleware exclou /ui/upload (uploads naturalment triguen >1s i el log apareixia duplicat amb l'access log d'uvicorn). - Bug #8 — 3 ⓘ visibles als checkboxes del sidebar de col·leccions amb tooltips als 3 idiomes (la infraestructura CSS/i18n ja existia). Pytest D-1 final: 4424 passed, 0 failed, 35 skipped, 1 xfailed, 86% coverage en 76.11s. Baseline pre-BUS era 4396. +28 tests nous, ZERO regressions. Tests nous: - tests/test_pid_file.py: 7 tests Bug #1 - tests/test_ingest_knowledge_idempotent.py: 8 tests F7 (3 classes) - plugins/web_ui_module/tests/test_memory_helper_async.py: 1 test F1 - plugins/web_ui_module/tests/test_memory_delete.py: 7 tests F3+Bug#10 - plugins/web_ui_module/tests/test_mem_save_injection.py: 5 tests Bug #3 Out of scope (deute v0.9.1+): - routes_chat.py 54KB decapitació general (deute formal P0) - Bundle .app real amb py2app per CFBundleName (deute v0.9.1) - Resums per capítol (Part 2 redisseny memory) - RDBMS font de veritat + vector store reconstruïble (HOMAD memoria v1, Part 2)
jgoy-labs
added a commit
that referenced
this pull request
Apr 21, 2026
…rence Symptom: after a restart the sidebar only listed .json sessions (.enc were invisible), new sessions were persisted unencrypted even with encryption enabled, and a reboot's .json->.enc migration could overwrite an existing .enc belonging to a different conversation with the same id (collision observed in the wild: "Hola Diana" overwritten by "Hola Anna"). Root cause is a three-bug chain: 1. Loader early-init (core/loader/manifest_base._get_module): calls `instance._init_router()` immediately after `__init__`, *before* `initialize()` runs. The plugin must have its routable resources ready at construction time. 2. Plugin double-create (plugins/web_ui_module/module.py): `__init__` created a SessionManager() without crypto, then `initialize()` replaced self.session_manager with a new SessionManager(crypto_provider=crypto). Two instances, divergent state: #1 loaded only .json, #2 loaded .enc + migrated .json->.enc. 3. Router local reference (plugins/web_ui_module/api/routes.py): `create_router()` captured session_mgr = module_instance.session_manager into a local at creation time. Because of bug #1, that snapshot was the crypto-less #1. When `initialize()` later replaced the attribute, the router closures kept pointing at the stale #1. Net effect: the UI saw #1 (only .json sessions), new sessions went through #1 and were written unencrypted, and every reboot migrated those .json files into .enc, overwriting whatever .enc already lived under the same id. Fix: - module.py: remove the premature SessionManager() from __init__; build the one real SessionManager(crypto_provider=crypto) in initialize() using whatever crypto_provider is available (may be None). One instance, one truth, one load-from-disk. - routes.py: introduce _SessionManagerProxy. Instead of capturing session_mgr = module_instance.session_manager, route closures hold the proxy and `__getattr__` re-reads module_instance.session_manager on every request. Late-binding, so the real SessionManager built in initialize() is always what the routes hit. Raises a clear RuntimeError if a route fires before initialize() has completed (rather than the opaque 'NoneType has no attribute list_sessions'). Verified: after restart, a single "Loaded N sessions from disk" per reboot (was 2 before), all .enc sessions visible in the sidebar, new sessions persisted as .enc. Data note: no encrypted sessions were lost in the wild. All existing .enc files decrypt cleanly with the current MEK; they were only made invisible by the stale reference. The only silent loss was the conversation overwritten by the colliding-id migration, which predates this fix.
jgoy-labs
added a commit
that referenced
this pull request
May 14, 2026
…clause SQL for nosec coverage Onada 1 bandit audit — findings #2-#23 (22/23). Each remaining HIGH+MEDIUM bandit finding from MUTHUR baseline 701b4e5 was reviewed empirically by reading the actual call site, then either silenced as a false positive (19) or marked ACCEPT with explicit justification (3). Format: `# nosec B<rule>: <reason>` for FP, `# nosec B<rule>: ACCEPT — <reason>` for accepted residual risk (so bandit silences the finding while the ACCEPT prefix flags it as a conscious architectural decision rather than a tooling false positive). By rule: B104 ×2 (FP): host string comparison for allow-list construction in TrustedHostMiddleware / web_ui URL rewriting — not a network bind. Files: core/middleware.py, plugins/web_ui_module/module.py B108 ×2 FP + 1 ACCEPT: /tmp paths inside read-only allow-lists or detection arrays don't create files. The single ACCEPT is rag_logger.py last-resort fallback when both ~/Nexe-Logs and storage/logs are unwritable; mono-user local install threat model accepts the predictable-path race vs. silently disabling RAG telemetry. Files: installer/install.py, memory/memory/cli/rag_viewer.py, memory/memory/rag_logger.py B310 ×8 (FP): all are urlopen() against literal hardcoded http://localhost:{constant}/... URLs (Ollama daemon ps/tags, own server health, dev smoke-test). One additional case (core/cli/client.py) already validates the scheme against ALLOWED_URL_SCHEMES before urlopen. Files: core/cli/client.py, dev-tools/smoke_test.py (×2), installer/install.py, installer/tray.py (×2), installer/tray_monitor.py, plugins/web_ui_module/api/routes_auth.py B314 ×1 (FP): CI tooling parsing coverage.xml emitted by pytest-cov in the same workflow run. File: .github/scripts/generate_coverage_badge.py B608 ×6 (FP): the canonical IN-clause-with-dynamic-placeholders pattern, where placeholders = ",".join("?" for _ in n) and every value is bound as a parameter. The sqlite_store case additionally interpolates a table name validated by _validate_table() against a frozenset whitelist. Files: memory/memory/api/text_store.py, memory/memory/engines/persistence_sqlite.py, memory/memory/storage/sqlite_store.py, memory/memory/workers/{dreaming_cycle,gc_daemon,sync_worker}.py Note: 5 of the 6 SQL strings were also refactored from multi-line f"""...""" to single-line `sql = f"..."` + `conn.execute(sql, ...)` so the # nosec annotation lands on the line bandit reports (bandit doesn't follow # nosec across multi-line statements when the report points at an inner line). Functionally identical. B615 ×2 (ACCEPT): snapshot_download() without revision= pinning in `nexe pull` CLI path. Pinning infrastructure already exists at installer/installer_catalog_data.py:360 (MODEL_WEIGHT_SHA256 map, backlog item C19) but all entries are still None pending hash population. Realignment of CLI pull path scheduled for v1.0.5. File: core/cli/cli.py (×2) Verified empirically: - 0 HIGH+MEDIUM bandit findings remain (was 23) - Total bandit count 195 → 172 (-23 exact) - Suite: 5127 passed, 0 regressions - 22 new # nosec annotations in production (1 preexisting B110 in plugins/mlx_module/core/chat.py:617 unrelated to this audit) No behavioural changes.
jgoy-labs
added a commit
that referenced
this pull request
May 14, 2026
…nada 4.1 Cluster 12) 13 cosmetic mypy findings closed via documented type-ignores or minimal annotations, no behavioural change: import-untyped (toml, yaml — D3 director category) - core/config.py:17 — toml lacks stubs; kept for write path (#3) - core/cli/config.py:21 — yaml stubs deferred to v1.1 (#8) - installer/install_headless.py:447 — toml lacks stubs (#15) - installer/install.py:378 — toml lacks stubs (#16) - core/cli/cli.py:325 — toml lacks stubs (#63) no-redef (tomli fallback for Python <3.11) - core/version.py:9 — # type: ignore[no-redef] (#11) - core/cli/router.py:25 — # type: ignore[no-redef] (#12) annotation/var-annotated/has-type - core/cli/i18n.py:43 — FP or-narrowing of Optional[str] or str (Director Onada 4.1 D2: # type: ignore[return-value]) (#2) - core/bootstrap_tokens.py:35 — declare _db_path / _initialized at class body and add -> 'BootstrapTokenManager' to __new__ (#6, #7) - core/paths/detection.py:44 — _detection_history: list[dict[str, Any]] (#10) - core/ingest/ingest_knowledge.py:243 — files: list[Path] = [] (#32) method-assign - scripts/bench_ingest_bug16.py:102 — benchmark monkey-patch documented with # type: ignore[method-assign] (#39)
jgoy-labs
added a commit
that referenced
this pull request
May 14, 2026
main() had 90 NLOC (over MUTHUR 80 limit) due to orgànic creixement during the v0.9 release work. Extract 4 single-responsibility helpers preserving exact initialization order: - _set_process_title() — setproctitle call (Bug #2) - _setup_file_logging() — TimedRotatingFileHandler setup - _log_quick_commands_banner(host, port) — CLI banner - _run_uvicorn_server(...) — uvicorn.run + KeyboardInterrupt/Exception handling main() now reads as a linear sequence of well-named steps. 40/40 runner-related tests pass (server, sidecar_port_guard, pid_file, tray_helpers).
jgoy-labs
added a commit
that referenced
this pull request
May 16, 2026
Fix-All BUS sobre 3 tracks paral·lels per resoldre tots els bugs del QA post-BUS de normalització abans del DMG v0.9.0. 8 commits dev consolidats en aquest sync. TRACK A — Memory/RAG/Sessions - Bug #1 (PID file canònic) — single source of truth a storage/run/server.pid - F5 — 3 col·leccions canòniques (nexe_web_ui, user_knowledge, nexe_documentation) creades a get_memory_api() en lloc de només la primera - F7 — ingest_knowledge defaulteja a nexe_documentation (era user_knowledge) i és idempotent (eliminada la sequence delete_collection + create_collection destructiva que esborrava docs ad-hoc dels usuaris a cada install) - F8 — root cause empíric Bug #4: MemoryModule obria un SEGON QdrantClient real a storage/vectors/qdrant_local/, divergent del singleton del pool. MEM_SAVE escrivia a una col·lecció, MEM_RECALL llegia d'una altra. Ara tots dos comparteixen storage/vectors/. - F1 — _check_duplicate retorna contracte honest (success=False, duplicate=True) enlloc de fingir success=True amb document_id=None. Era el segon root cause de Bug #4: el dedup bloquejava SAVEs amb fals positius silenciats. - F2 — typo cols list (nexe_web_ui duplicat) - F3 — list_memories scroll-based (sense semantic search amb query anglesa) - Bug #10 — collections= filter a list/save/delete (sidebar checks reals) - Bug #6 — frontend hydration document attached. Eren 2 bugs encadenats: l'endpoint /history no retornava attached_document, i removeFilePreview() feia POST /clear-document destructiu cada switch de sessió. - Bug #3 — MEM_SAVE-only response fallback. Quan el model emet només [MEM_SAVE: ...] sense text envoltant, ara genera 'Memòria desada: <fact>' perquè el bloc save s'executi i el frontend mostri confirmació. - auto_save crida eliminada per HOMAD memoria v1 (2026-04-01) — manual MEM_SAVE only fins a Part 2. TRACK B — Tray / Multi-instance / Packaging - Bug #1 (PID file) compartit amb Track A - Bug #2 — setproctitle a server i tray (server-nexe / nexe-tray a ps/Activity Monitor). Force Quit encara mostra Python perquè requereix CFBundleName via .app bundle real (deute v0.9.1). - Bug #9 — menu polish: server-nexe.com duplicat substituït per '📖 Documentació' al main level (3 idiomes), website_item es manté al submenú Configuració. TRACK C — UX cosmètic - Bug #5 — slow_request middleware exclou /ui/upload (uploads naturalment triguen >1s i el log apareixia duplicat amb l'access log d'uvicorn). - Bug #8 — 3 ⓘ visibles als checkboxes del sidebar de col·leccions amb tooltips als 3 idiomes (la infraestructura CSS/i18n ja existia). Pytest D-1 final: 4424 passed, 0 failed, 35 skipped, 1 xfailed, 86% coverage en 76.11s. Baseline pre-BUS era 4396. +28 tests nous, ZERO regressions. Tests nous: - tests/test_pid_file.py: 7 tests Bug #1 - tests/test_ingest_knowledge_idempotent.py: 8 tests F7 (3 classes) - plugins/web_ui_module/tests/test_memory_helper_async.py: 1 test F1 - plugins/web_ui_module/tests/test_memory_delete.py: 7 tests F3+Bug#10 - plugins/web_ui_module/tests/test_mem_save_injection.py: 5 tests Bug #3 Out of scope (deute v0.9.1+): - routes_chat.py 54KB decapitació general (deute formal P0) - Bundle .app real amb py2app per CFBundleName (deute v0.9.1) - Resums per capítol (Part 2 redisseny memory) - RDBMS font de veritat + vector store reconstruïble (HOMAD memoria v1, Part 2)
jgoy-labs
added a commit
that referenced
this pull request
May 16, 2026
…rence Symptom: after a restart the sidebar only listed .json sessions (.enc were invisible), new sessions were persisted unencrypted even with encryption enabled, and a reboot's .json->.enc migration could overwrite an existing .enc belonging to a different conversation with the same id (collision observed in the wild: "Hola Diana" overwritten by "Hola Anna"). Root cause is a three-bug chain: 1. Loader early-init (core/loader/manifest_base._get_module): calls `instance._init_router()` immediately after `__init__`, *before* `initialize()` runs. The plugin must have its routable resources ready at construction time. 2. Plugin double-create (plugins/web_ui_module/module.py): `__init__` created a SessionManager() without crypto, then `initialize()` replaced self.session_manager with a new SessionManager(crypto_provider=crypto). Two instances, divergent state: #1 loaded only .json, #2 loaded .enc + migrated .json->.enc. 3. Router local reference (plugins/web_ui_module/api/routes.py): `create_router()` captured session_mgr = module_instance.session_manager into a local at creation time. Because of bug #1, that snapshot was the crypto-less #1. When `initialize()` later replaced the attribute, the router closures kept pointing at the stale #1. Net effect: the UI saw #1 (only .json sessions), new sessions went through #1 and were written unencrypted, and every reboot migrated those .json files into .enc, overwriting whatever .enc already lived under the same id. Fix: - module.py: remove the premature SessionManager() from __init__; build the one real SessionManager(crypto_provider=crypto) in initialize() using whatever crypto_provider is available (may be None). One instance, one truth, one load-from-disk. - routes.py: introduce _SessionManagerProxy. Instead of capturing session_mgr = module_instance.session_manager, route closures hold the proxy and `__getattr__` re-reads module_instance.session_manager on every request. Late-binding, so the real SessionManager built in initialize() is always what the routes hit. Raises a clear RuntimeError if a route fires before initialize() has completed (rather than the opaque 'NoneType has no attribute list_sessions'). Verified: after restart, a single "Loaded N sessions from disk" per reboot (was 2 before), all .enc sessions visible in the sidebar, new sessions persisted as .enc. Data note: no encrypted sessions were lost in the wild. All existing .enc files decrypt cleanly with the current MEK; they were only made invisible by the stale reference. The only silent loss was the conversation overwritten by the colliding-id migration, which predates this fix.
jgoy-labs
added a commit
that referenced
this pull request
May 16, 2026
…clause SQL for nosec coverage Onada 1 bandit audit — findings #2-#23 (22/23). Each remaining HIGH+MEDIUM bandit finding from quality checks baseline 701b4e5 was reviewed empirically by reading the actual call site, then either silenced as a false positive (19) or marked ACCEPT with explicit justification (3). Format: `# nosec B<rule>: <reason>` for FP, `# nosec B<rule>: ACCEPT — <reason>` for accepted residual risk (so bandit silences the finding while the ACCEPT prefix flags it as a conscious architectural decision rather than a tooling false positive). By rule: B104 ×2 (FP): host string comparison for allow-list construction in TrustedHostMiddleware / web_ui URL rewriting — not a network bind. Files: core/middleware.py, plugins/web_ui_module/module.py B108 ×2 FP + 1 ACCEPT: /tmp paths inside read-only allow-lists or detection arrays don't create files. The single ACCEPT is rag_logger.py last-resort fallback when both ~/Nexe-Logs and storage/logs are unwritable; mono-user local install threat model accepts the predictable-path race vs. silently disabling RAG telemetry. Files: installer/install.py, memory/memory/cli/rag_viewer.py, memory/memory/rag_logger.py B310 ×8 (FP): all are urlopen() against literal hardcoded http://localhost:{constant}/... URLs (Ollama daemon ps/tags, own server health, dev smoke-test). One additional case (core/cli/client.py) already validates the scheme against ALLOWED_URL_SCHEMES before urlopen. Files: core/cli/client.py, dev-tools/smoke_test.py (×2), installer/install.py, installer/tray.py (×2), installer/tray_monitor.py, plugins/web_ui_module/api/routes_auth.py B314 ×1 (FP): CI tooling parsing coverage.xml emitted by pytest-cov in the same workflow run. File: .github/scripts/generate_coverage_badge.py B608 ×6 (FP): the canonical IN-clause-with-dynamic-placeholders pattern, where placeholders = ",".join("?" for _ in n) and every value is bound as a parameter. The sqlite_store case additionally interpolates a table name validated by _validate_table() against a frozenset whitelist. Files: memory/memory/api/text_store.py, memory/memory/engines/persistence_sqlite.py, memory/memory/storage/sqlite_store.py, memory/memory/workers/{dreaming_cycle,gc_daemon,sync_worker}.py Note: 5 of the 6 SQL strings were also refactored from multi-line f"""...""" to single-line `sql = f"..."` + `conn.execute(sql, ...)` so the # nosec annotation lands on the line bandit reports (bandit doesn't follow # nosec across multi-line statements when the report points at an inner line). Functionally identical. B615 ×2 (ACCEPT): snapshot_download() without revision= pinning in `nexe pull` CLI path. Pinning infrastructure already exists at installer/installer_catalog_data.py:360 (MODEL_WEIGHT_SHA256 map, backlog item C19) but all entries are still None pending hash population. Realignment of CLI pull path scheduled for v1.0.5. File: core/cli/cli.py (×2) Verified empirically: - 0 HIGH+MEDIUM bandit findings remain (was 23) - Total bandit count 195 → 172 (-23 exact) - Suite: 5127 passed, 0 regressions - 22 new # nosec annotations in production (1 preexisting B110 in plugins/mlx_module/core/chat.py:617 unrelated to this audit) No behavioural changes.
jgoy-labs
added a commit
that referenced
this pull request
May 16, 2026
…nada 4.1 Cluster 12) 13 cosmetic mypy findings closed via documented type-ignores or minimal annotations, no behavioural change: import-untyped (toml, yaml — D3 director category) - core/config.py:17 — toml lacks stubs; kept for write path (#3) - core/cli/config.py:21 — yaml stubs deferred to v1.1 (#8) - installer/install_headless.py:447 — toml lacks stubs (#15) - installer/install.py:378 — toml lacks stubs (#16) - core/cli/cli.py:325 — toml lacks stubs (#63) no-redef (tomli fallback for Python <3.11) - core/version.py:9 — # type: ignore[no-redef] (#11) - core/cli/router.py:25 — # type: ignore[no-redef] (#12) annotation/var-annotated/has-type - core/cli/i18n.py:43 — FP or-narrowing of Optional[str] or str (Director Onada 4.1 D2: # type: ignore[return-value]) (#2) - core/bootstrap_tokens.py:35 — declare _db_path / _initialized at class body and add -> 'BootstrapTokenManager' to __new__ (#6, #7) - core/paths/detection.py:44 — _detection_history: list[dict[str, Any]] (#10) - core/ingest/ingest_knowledge.py:243 — files: list[Path] = [] (#32) method-assign - scripts/bench_ingest_bug16.py:102 — benchmark monkey-patch documented with # type: ignore[method-assign] (#39)
jgoy-labs
added a commit
that referenced
this pull request
May 16, 2026
main() had 90 NLOC (over quality checks 80 limit) due to orgànic creixement during the v0.9 release work. Extract 4 single-responsibility helpers preserving exact initialization order: - _set_process_title() — setproctitle call (Bug #2) - _setup_file_logging() — TimedRotatingFileHandler setup - _log_quick_commands_banner(host, port) — CLI banner - _run_uvicorn_server(...) — uvicorn.run + KeyboardInterrupt/Exception handling main() now reads as a linear sequence of well-named steps. 40/40 runner-related tests pass (server, sidecar_port_guard, pid_file, tray_helpers).
jgoy-labs
added a commit
that referenced
this pull request
May 16, 2026
…rence Symptom: after a restart the sidebar only listed .json sessions (.enc were invisible), new sessions were persisted unencrypted even with encryption enabled, and a reboot's .json->.enc migration could overwrite an existing .enc belonging to a different conversation with the same id (collision observed in the wild: "Hola Diana" overwritten by "Hola Anna"). Root cause is a three-bug chain: 1. Loader early-init (core/loader/manifest_base._get_module): calls `instance._init_router()` immediately after `__init__`, *before* `initialize()` runs. The plugin must have its routable resources ready at construction time. 2. Plugin double-create (plugins/web_ui_module/module.py): `__init__` created a SessionManager() without crypto, then `initialize()` replaced self.session_manager with a new SessionManager(crypto_provider=crypto). Two instances, divergent state: #1 loaded only .json, #2 loaded .enc + migrated .json->.enc. 3. Router local reference (plugins/web_ui_module/api/routes.py): `create_router()` captured session_mgr = module_instance.session_manager into a local at creation time. Because of bug #1, that snapshot was the crypto-less #1. When `initialize()` later replaced the attribute, the router closures kept pointing at the stale #1. Net effect: the UI saw #1 (only .json sessions), new sessions went through #1 and were written unencrypted, and every reboot migrated those .json files into .enc, overwriting whatever .enc already lived under the same id. Fix: - module.py: remove the premature SessionManager() from __init__; build the one real SessionManager(crypto_provider=crypto) in initialize() using whatever crypto_provider is available (may be None). One instance, one truth, one load-from-disk. - routes.py: introduce _SessionManagerProxy. Instead of capturing session_mgr = module_instance.session_manager, route closures hold the proxy and `__getattr__` re-reads module_instance.session_manager on every request. Late-binding, so the real SessionManager built in initialize() is always what the routes hit. Raises a clear RuntimeError if a route fires before initialize() has completed (rather than the opaque 'NoneType has no attribute list_sessions'). Verified: after restart, a single "Loaded N sessions from disk" per reboot (was 2 before), all .enc sessions visible in the sidebar, new sessions persisted as .enc. Data note: no encrypted sessions were lost in the wild. All existing .enc files decrypt cleanly with the current MEK; they were only made invisible by the stale reference. The only silent loss was the conversation overwritten by the colliding-id migration, which predates this fix.
jgoy-labs
added a commit
that referenced
this pull request
May 16, 2026
…clause SQL for nosec coverage Onada 1 bandit audit — findings #2-#23 (22/23). Each remaining HIGH+MEDIUM bandit finding from quality checks baseline 701b4e5 was reviewed empirically by reading the actual call site, then either silenced as a false positive (19) or marked ACCEPT with explicit justification (3). Format: `# nosec B<rule>: <reason>` for FP, `# nosec B<rule>: ACCEPT — <reason>` for accepted residual risk (so bandit silences the finding while the ACCEPT prefix flags it as a conscious architectural decision rather than a tooling false positive). By rule: B104 ×2 (FP): host string comparison for allow-list construction in TrustedHostMiddleware / web_ui URL rewriting — not a network bind. Files: core/middleware.py, plugins/web_ui_module/module.py B108 ×2 FP + 1 ACCEPT: /tmp paths inside read-only allow-lists or detection arrays don't create files. The single ACCEPT is rag_logger.py last-resort fallback when both ~/Nexe-Logs and storage/logs are unwritable; mono-user local install threat model accepts the predictable-path race vs. silently disabling RAG telemetry. Files: installer/install.py, memory/memory/cli/rag_viewer.py, memory/memory/rag_logger.py B310 ×8 (FP): all are urlopen() against literal hardcoded http://localhost:{constant}/... URLs (Ollama daemon ps/tags, own server health, dev smoke-test). One additional case (core/cli/client.py) already validates the scheme against ALLOWED_URL_SCHEMES before urlopen. Files: core/cli/client.py, dev-tools/smoke_test.py (×2), installer/install.py, installer/tray.py (×2), installer/tray_monitor.py, plugins/web_ui_module/api/routes_auth.py B314 ×1 (FP): CI tooling parsing coverage.xml emitted by pytest-cov in the same workflow run. File: .github/scripts/generate_coverage_badge.py B608 ×6 (FP): the canonical IN-clause-with-dynamic-placeholders pattern, where placeholders = ",".join("?" for _ in n) and every value is bound as a parameter. The sqlite_store case additionally interpolates a table name validated by _validate_table() against a frozenset whitelist. Files: memory/memory/api/text_store.py, memory/memory/engines/persistence_sqlite.py, memory/memory/storage/sqlite_store.py, memory/memory/workers/{dreaming_cycle,gc_daemon,sync_worker}.py Note: 5 of the 6 SQL strings were also refactored from multi-line f"""...""" to single-line `sql = f"..."` + `conn.execute(sql, ...)` so the # nosec annotation lands on the line bandit reports (bandit doesn't follow # nosec across multi-line statements when the report points at an inner line). Functionally identical. B615 ×2 (ACCEPT): snapshot_download() without revision= pinning in `nexe pull` CLI path. Pinning infrastructure already exists at installer/installer_catalog_data.py:360 (MODEL_WEIGHT_SHA256 map, backlog item C19) but all entries are still None pending hash population. Realignment of CLI pull path scheduled for v1.0.5. File: core/cli/cli.py (×2) Verified empirically: - 0 HIGH+MEDIUM bandit findings remain (was 23) - Total bandit count 195 → 172 (-23 exact) - Suite: 5127 passed, 0 regressions - 22 new # nosec annotations in production (1 preexisting B110 in plugins/mlx_module/core/chat.py:617 unrelated to this audit) No behavioural changes.
jgoy-labs
added a commit
that referenced
this pull request
May 16, 2026
…nada 4.1 Cluster 12) 13 cosmetic mypy findings closed via documented type-ignores or minimal annotations, no behavioural change: import-untyped (toml, yaml — D3 director category) - core/config.py:17 — toml lacks stubs; kept for write path (#3) - core/cli/config.py:21 — yaml stubs deferred to v1.1 (#8) - installer/install_headless.py:447 — toml lacks stubs (#15) - installer/install.py:378 — toml lacks stubs (#16) - core/cli/cli.py:325 — toml lacks stubs (#63) no-redef (tomli fallback for Python <3.11) - core/version.py:9 — # type: ignore[no-redef] (#11) - core/cli/router.py:25 — # type: ignore[no-redef] (#12) annotation/var-annotated/has-type - core/cli/i18n.py:43 — FP or-narrowing of Optional[str] or str (Director Onada 4.1 D2: # type: ignore[return-value]) (#2) - core/bootstrap_tokens.py:35 — declare _db_path / _initialized at class body and add -> 'BootstrapTokenManager' to __new__ (#6, #7) - core/paths/detection.py:44 — _detection_history: list[dict[str, Any]] (#10) - core/ingest/ingest_knowledge.py:243 — files: list[Path] = [] (#32) method-assign - scripts/bench_ingest_bug16.py:102 — benchmark monkey-patch documented with # type: ignore[method-assign] (#39)
jgoy-labs
added a commit
that referenced
this pull request
May 16, 2026
main() had 90 NLOC (over quality checks 80 limit) due to orgànic creixement during the v0.9 release work. Extract 4 single-responsibility helpers preserving exact initialization order: - _set_process_title() — setproctitle call (Bug #2) - _setup_file_logging() — TimedRotatingFileHandler setup - _log_quick_commands_banner(host, port) — CLI banner - _run_uvicorn_server(...) — uvicorn.run + KeyboardInterrupt/Exception handling main() now reads as a linear sequence of well-named steps. 40/40 runner-related tests pass (server, sidecar_port_guard, pid_file, tray_helpers).
jgoy-labs
added a commit
that referenced
this pull request
May 26, 2026
Synced from dev since sync-20260519. Highlights: Security (Tier S — security audit 96/100 PUSH OK): - S2 XSS api_key UI sanitization (web_ui_module/ui/app.js) - S3 /installer/finalize legacy gate via idempotency marker - S9 path traversal reject in _resolve_model_path - S10 SQLiteStore thread-safe via check_same_thread + RLock - S1 health URL boot fix - S6 fsync before close on atomic writes - S7 CancelledError catch + mid-startup signal logging - TOCTOU atomic on /installer/finalize idempotency marker - basename guard applied to all model_id download pipelines Bug fixes vespre (2026-05-21 sessions): - Bug #5 fix(qdrant): retry-with-backoff lock acquisition at startup - Bug #4 test(live): order slow LLM tests after fast ones + 10s cooldown - Bug #2+#3 test(security): replace fragile inspect-based assertions - Bug #1 chore(installer): document accepted CVEs + portable OSV checker - Bug A fix(chat): remove data:[DONE] sentinel from text/plain stream - Bug B iter-2 fix(chat): natural-language date phrase replaces "Now:" tech UI productiva (F5.5 revert + post-revert): - web_ui_module serves full UI in sidecar mode again - footer i18n + version persistence - frontend reads nexe_api_key from query string on first launch F5.3 + F5.4 onboarding wizard: - HTTP endpoints for wizard (installer/finalize, preflight, progress) - NEXE_VERSION UI injection - onboarding_state.py + optional HF token via macOS Keychain (never on disk) - installer_constants.py + installer_progress.py + check_cves_osv.py Hygiene: - 113 hardcoded "Nexe 0.9" → version centralized (UI) - /v1/system/* blocklist on sidecar URL guard - ruff/mypy/pyright/typos cleanup (multiple commits) 54 files changed total. Nous tests: 17 (security regression sentinels + onboarding wizard + qdrant + installer + sqlite concurrent + sqlite store singleton).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resum
Implementació completa del pla d'auditoria de seguretat per Nexe Server 0.8. S'han analitzat ~11.000 fitxers Python i s'han corregit 21 problemes classificats per severitat.
🔴 Crítiques resoltes
NEXE_PRIMARY_API_KEYiNEXE_CSRF_SECRETrotatsexcept:substituïts perexcept Exception as e:amb logging a 4 fitxers🟠 Altes resoltes
{.txt, .md, .pdf, .csv, .rst, .html})QDRANT_API_KEYper entorns remots/health/ready: retorna{status, timestamp}sense exposar llista de mòduls sense authdetail=str(e)(no filtrar internals als clients)flush()+= Noneal shutdownSessionManager.cleanup_inactive()implementat amb TTL realasyncio.CancelledError(client desconnectat)🟡 Mitges resoltes
nexeadocker-compose.ymlqdrant/qdrant:latest→qdrant/qdrant:v1.12.0.dockerignorecreat (exclou.env,storage/,.git/,tests/)httpxeliminat derequirements-dev.txt(duplicat)pip-auditafegit al CI pipeline🟢 Tests de seguretat (14/14 passing)
Nou fitxer
tests/integration/test_security.py:_sanitize_rag_context(truncació + filtratge injeccions)/health/readyno exposa informació de mòduls sense authAltres millores incloses a la branca
nexe:nexe, permisos750runner.py:validate_production_config()per detectar secrets buits en produccióscripts/generate_secrets.sh: script per generar secrets segursknowledge/SECURITY.md,DEPLOYMENT.md: documentació de seguretatPla de tests
pytest tests/integration/test_security.py -v→ 14/14 passingpip-audit -r requirements.txt→ sense CVEs críticsdocker compose up --build -d→ healthcheck OK per ambdós serveis/health/readyretorna{status, timestamp}sense detalls de mòduls.exeretorna HTTP 400../../etc/passwdretorna HTTP 400Assisted by AI