You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Wave 1 implementation of ADR-012 (Correctness & Safety Fixes) covering thread safety, SSRF hardening, and provider reachability.
Changes
Thread Safety (T1-T6)
CircuitBreakerRegistry: Add threading.RLock around breakers dict; fix falsy-threshold bug (threshold=0 no longer silently replaced)
RoutingMemory: Add threading.RLock around domain_stats; extract magic numbers to SCORE_BASE, RECENCY_DECAY_DAYS, SCORE_SCALE constants
providers_impl: Add _rate_limits_lock around rate limit tracking
utils: Add _session_lock and _cache_lock (RLock) around shared session and cache
semantic_cache: Add double-checked locking for singleton creation; atomic transaction for eviction
resolve: Monkey-patching preserved (needed until ADR-014 state.py); TODO marks the transition
SSRF Hardening (S1-S3)
Mistral browser: Validate URL with is_safe_url() before API call
is_url(): Reject ftp:// and ftps:// schemes (only http/https)
safeFetch(): Validate initial URL before fetch (not just redirects)
Provider Reachability (P1-P2)
Add LLMS_TXT, SERPER, DOCLING, OCR to resolve_direct() dispatch
Fix Profile.max_hops() returning None for unknown profiles (now returns 4)
Package.json Fixes (ADR-013 I6-I8)
typescript: ^6.0.3 → ^5.7.0
next: ^16.2.6 → ^15.3.0
Remove duplicate overrides key
Testing
162 Python unit tests pass
52 Rust CLI tests pass
Web typecheck clean, lint clean (0 errors)
ruff, black, cargo fmt, cargo clippy all pass
Quality Gate
RLock (not Lock) used for reentrant-safe cache access — _get_from_cache() calls _get_cache() which both hold the same lock; threading.Lock would deadlock
Conftest updated to use lock-safe .clear() methods
Tests updated for ftp/ftps rejection behavior
Follow-up
See plans/GOAP_FOLLOWUP.md for remaining Waves 2-6.
We reviewed changes in 6c2f4ce...dd9e92f on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.
Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.
AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.
The reason will be displayed to describe this comment to others. Learn more.
`resolve_with_mistral_browser` has a cyclomatic complexity of 17 with "high" risk
A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.
The reason will be displayed to describe this comment to others. Learn more.
Use lazy % formatting in logging functions
Formatting the message manually before passing it to a logging call does unnecessary work if logging is disabled. Consider using the logging module's built-in formatting features to avoid that.
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
While the PR successfully introduces core security and reachability features in line with ADR-012, there are several implementation flaws that should prevent merging in its current state. Most critically, the shared SQLite connection in the Semantic Cache is not thread-safe and will likely cause runtime errors under the concurrent load of the resolver. Additionally, the SSRF validation implemented for Mistral was not applied to Jina or Firecrawl providers, leaving those vectors unprotected.\n\nThere is a consistent design mismatch where the code uses threading.Lock instead of the threading.RLock specified in the design documents (AUDIT.md and GOAP_FOLLOWUP.md) for the Circuit Breaker and Routing Memory components. This could lead to deadlocks if recursive calls occur. Finally, while Codacy reports the PR is up to standards, the complexity of routing_memory.py combined with zero test coverage and logic duplication makes it a high-risk area for regression.
About this PR
Inconsistent security posture: SSRF validation via is_safe_url was added to the Mistral provider but is missing from other providers (Jina, Firecrawl) that also fetch external URLs. These should be standardized using the _safe_request wrapper.
Systemic mismatch with ADR-012 requirements: The implementation uses threading.Lock across several registries (CircuitBreaker, RoutingMemory) whereas the design specifically requires threading.RLock. This inconsistency increases the risk of deadlocks during complex resolution cycles.
2 comments outside of the diffscripts/circuit_breaker.py
line 17-20 🔴 HIGH RISK
Potential race condition: if self.open_until is cleared by another thread after the check but before the assignment, this will raise AttributeError on line 21. Capture the reference once at the start.
scripts/providers_impl.py
line 61 🔴 HIGH RISK
The SSRF validation check should be applied consistently across all providers that handle user-provided URLs. While resolve_with_mistral_browser correctly implements this, other functions like resolve_with_jina and resolve_with_firecrawl are missing it.
Test suggestions
Verify is_url() rejects ftp://, ftps://, and file:// schemes.\n- [ ] Verify CircuitBreakerRegistry.record_failure() correctly handles threshold=0.\n- [x] Verify safeFetch() throws an SSRF error if the initial URL provided is unsafe.\n- [ ] Verify concurrent access to RoutingMemory.record() does not cause state corruption.\n- [ ] Verify resolve_direct() correctly routes to the new OCR and DOCLING providers.\n- [ ] Unit tests for success rate and recency calculation in scripts/routing_memory.py
The reason will be displayed to describe this comment to others. Learn more.
🟡 MEDIUM RISK
Suggestion: This method duplicates the logic found in _get_domain_stats_unlocked. You should refactor it to call the internal method while holding the lock to ensure consistency.\n\nThis might be a simple fix:\nsuggestion\n def get_domain_stats(self, provider: str, domain: str) -> dict | None:\n with self._lock:\n return self._get_domain_stats_unlocked(provider, domain)\n
The reason will be displayed to describe this comment to others. Learn more.
Attribute '_conn' defined outside __init__
Defining an instance attribute outside __init__ affects the readability of code. It is expected to find all the attributes an instance may have by reading its __init__ method. If there is a need to initialize attribute via sub-initialization methods, it is recommended to assign attributes to None in the init then call the sub-initialization methods.
Add threading locks to all 6 shared mutable state locations with
threading.RLock for reentrant-safe cache access.
SSRF hardening: reject ftp/ftps in is_url(), validate Mistral browser
URLs, validate initial URL in web safeFetch().
Fix resolve_direct() dispatch for LLMS_TXT, SERPER, DOCLING, OCR.
Fix Profile.max_hops() returning None for unknown profiles.
Fix web/package.json non-existent version specifiers.
Update conftest to use lock-safe clear methods.
Update tests for new is_url() ftp rejection.
All quality gate checks verified: 162 Python tests, 52 Rust tests,
ruff, black, cargo fmt, clippy, web lint pass.
Capture self.open_until once at function entry to avoid a
TOCTOU (time-of-check-time-of-use) race between the None
guard on line 17 and the attribute read on line 20.
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
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
Wave 1 implementation of ADR-012 (Correctness & Safety Fixes) covering thread safety, SSRF hardening, and provider reachability.
Changes
Thread Safety (T1-T6)
threading.RLockaround breakers dict; fix falsy-threshold bug (threshold=0no longer silently replaced)threading.RLockaround domain_stats; extract magic numbers toSCORE_BASE,RECENCY_DECAY_DAYS,SCORE_SCALEconstants_rate_limits_lockaround rate limit tracking_session_lockand_cache_lock(RLock) around shared session and cachestate.py); TODO marks the transitionSSRF Hardening (S1-S3)
is_safe_url()before API callftp://andftps://schemes (onlyhttp/https)Provider Reachability (P1-P2)
LLMS_TXT,SERPER,DOCLING,OCRtoresolve_direct()dispatchProfile.max_hops()returningNonefor unknown profiles (now returns 4)Package.json Fixes (ADR-013 I6-I8)
typescript:^6.0.3→^5.7.0next:^16.2.6→^15.3.0overrideskeyTesting
Quality Gate
RLock(notLock) used for reentrant-safe cache access —_get_from_cache()calls_get_cache()which both hold the same lock;threading.Lockwould deadlock.clear()methodsFollow-up
See
plans/GOAP_FOLLOWUP.mdfor remaining Waves 2-6.