⚡️ refactor: critical fixes + performance optimization - #34
Merged
Merged
Conversation
- Fix SSRF bypass in _validate_git_url: IP-address hostnames with non-standard ports were silently accepted (except ValueError swallowed the ip_address parse). Now hostnames without an IP component are allowed; private IPs are always blocked. - Fix path traversal in delete_local_file: resolve() + relative_to() check prevents deleting files outside the documents directory. - Fix ToolContext injection: tools with a ctx param now receive the ToolContext from ChatService instead of None. Executor only passes ctx when the function signature accepts it. Registry excludes ctx from generated JSON schema. - Fix broken imports in scripts: src.back.utils -> src.shared.utils, src.back.database -> src.infrastructure.database. - Move llama.cpp/Qdrant version checks to asyncio.to_thread() to avoid blocking the event loop. - Add tests/unit/application/tools/test_ctx_injection.py (6 tests). - Update SSRF tests to match corrected behavior.
- Centralize HF_HUB_OFFLINE monkey-patch into force_hf_online() context manager (src/shared/utils/hf_hub.py). Replaced 6 copy-pasted save/set/restore blocks across 3 files. - Deduplicate SHA-256 hashing: replaced 5 inline hashlib.sha256 call sites with compute_sha256_file/bytes/str from src/shared/utils/crypto_utils.py. - Remove 3rd LLM path: replace raw httpx POST in src/api/v1/sigma/explain.py with LlamaClient.chat() call. - Fix fake cancel endpoint: no longer claims 'cancelled' status without a job registry; returns 'acknowledged' with honest note. - Add missing direct dependencies to pyproject.toml: aiosqlite>=0.22, portalocker>=3.2 (were transitive-only). - Add src/shared/utils/hf_hub.py (new module).
- Add .github/workflows/ci.yml with 3 jobs: lint (ruff check + format), typecheck (mypy), test (pytest). - Fix 4 mypy errors so typecheck passes clean: - session.py: use lambda instead of dict.get as min() key - chat/service.py: add cast() for session store return values - Add cast to typing import in service.py.
- logs.py: move SSE log reads (full + tail) to asyncio.to_thread() so the 500ms polling loop no longer stalls concurrent requests. - logs.py: get_logs endpoint now awaits asyncio.to_thread(read_log_file). - files.py: upload write+identify+hash moved to asyncio.to_thread() so large uploads don't freeze the event loop. - repo_router.py: list_repos_handler GitPython loop extracted to sync helper invoked via asyncio.to_thread(). - prompts.py: _ensure_loaded() now guards with 'if not _prompts' to avoid 2-3 redundant locked DuckDB scans per chat message.
… cancel - CI: push triggers on all branches (was main-only) - CI: add Biome check for src/presentation/static/ - CI: include main.py in ruff paths - Cancel endpoint: outer status stays 'success' (non-breaking for clients)
- HF downloads (llm + embedding) now run in asyncio.to_thread instead of blocking the event loop for multi-GB transfers - ChatService creates LlamaClient once and shares it between SearchEngine (router) and RAGPipeline — eliminates per-query client construction and second dead SearchEngine - File upload: replaced read_bytes() + sha256_bytes with streaming compute_sha256_file — eliminates double full-file read - ToolDef: pre-compute has_ctx at @tool registration; executor no longer calls inspect.signature per dispatch
… download, N+1 repos) - Cache VectorStoreIndex per (collection, alpha) in retrievers.py; shared AsyncQdrantClient singleton; invalidation via reset_search_embed_model() - set_config: add persist=False param; config endpoints batch writes and call persist() once per request instead of per key - download_file: stream response in 1MiB chunks to disk instead of buffering full content in memory - list_repos: fetch_remote=False default (no network per repo); explicit sync endpoint can opt in - Update test_http.py mocks for streaming interface
…patcher) - sigma_ref_downloader: compute_sha256_file() replaces read_bytes() + compute_sha256_bytes() to avoid loading multi-GB files into RAM - logs: single read_bytes() + in-memory decode attempts instead of up to 5 full-file re-opens for encoding detection - TaskDispatcher: poll_interval 1.0s → 0.2s, max_workers default 1 → 4 to reduce task pickup latency
- Add get_async_pooled_client() / close_all_async_pooled_clients() to shared/http.py (mirrors the sync pool pattern) - Refactor github/api.py: 4 throwaway AsyncClient per call → 1 shared pooled client with keep-alive connection reuse - Wire close_all_async_pooled_clients() into lifespan shutdown - Update test_api.py to mock the pool factory instead of httpx.AsyncClient directly
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.
Summary
9-commit refactor of the SigmaForge codebase addressing security bugs, blocking I/O, and runtime performance.
Changes
Phase 1 — Critical Bug Fixes
asyncio.to_threadfor blocking version checksPhase 2 — Dedup & Dependency Hygiene
force_hf_online()context manager to prevent accidental offline modePhase 3 — CI Pipeline
Phase 4 — Event Loop Blocking
logs.py,files.py,repo_router.py: file I/O moved to thread poolprompts.py:_ensure_loadedguard to prevent redundant readsTier 1 — Event Loop & I/O Performance
asyncio.to_thread(API stays responsive)LlamaClientinstance shared across SearchEngine + RAGPipelinecompute_sha256_file()replaces double full-readToolDef.has_ctxpre-computed at decoration (no per-callinspect.signature)Tier 2 — Caching, Persistence, Streaming
AsyncQdrantClientsingleton +VectorStoreIndexcache per (collection, alpha)set_config:persist=Falsekwarg; config endpoints batch writes, singledb.persist()per requestdownload_file: streams response in 1 MiB chunks to disk (no full-buffer in RAM)list_repos:fetch_remote=Falsedefault (no N+1 network calls on list)Tier 3 — Quick Wins
sigma_ref_downloader:compute_sha256_file()replacesread_bytes()on multi-GB fileslogs.py: singleread_bytes()+ in-memory decode attempts (was up to 5 full-file re-opens)TaskDispatcher: poll interval 1.0s → 0.2s,max_workersdefault 1 → 4httpxclient pool: 4 throwawayAsyncClientper GitHub API call → 1 pooled client with keep-aliveValidation
The 46 pre-existing failures (sigma_ref_*, qdrant downloader, fusion_retriever, search router) exist on
mainand are unaffected by this branch.Files Changed
~40 files across
src/shared/,src/api/,src/application/,src/core/,src/infrastructure/,src/workers/,tests/,.github/