feat(captcha): add automatic solver cascade (MAT-141)#182
Conversation
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
📝 WalkthroughWalkthroughThis PR adds OCR, AntiCaptcha, and browser CAPTCHA plugins with configurable cascading, Tesseract brokering, persisted solver attempts, credential management, and a human-assisted Tauri WebView flow. It updates domain models, SQLite storage, IPC, frontend settings, CAPTCHA views, plugin metadata, and WASM export validation. ChangesCAPTCHA solver pipeline
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant CaptchaCommandHandler
participant PluginCaptchaSolver
participant ExtismPluginLoader
participant TesseractBroker
participant CaptchaRepository
User->>CaptchaCommandHandler: enqueue CAPTCHA
CaptchaCommandHandler->>PluginCaptchaSolver: run configured solver
PluginCaptchaSolver->>ExtismPluginLoader: solve_captcha
ExtismPluginLoader->>TesseractBroker: invoke typed OCR capability
TesseractBroker-->>ExtismPluginLoader: solver outcome
ExtismPluginLoader-->>PluginCaptchaSolver: CAPTCHA result
PluginCaptchaSolver-->>CaptchaCommandHandler: solver outcome
CaptchaCommandHandler->>CaptchaRepository: persist attempt and state
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
src-tauri/src/domain/model/captcha.rs (1)
366-379: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winNo upper bound on the number of recorded solver attempts.
Every other invariant in this file caps size (
MAX_CAPTCHA_ID_BYTES,MAX_CAPTCHA_URL_BYTES,MAX_CAPTCHA_IMAGE_BYTES,MAX_CAPTCHA_SOLUTION_BYTES, and hereMAX_CAPTCHA_SOLVER_NAME_BYTES), butrecord_solver_attemptonly bounds the solver name — not the count of attempts pushed intosolver_attempts. Sinceretry()(Line 356) lets a pending challenge be retried indefinitely while extending its deadline, and each retry presumably triggers another solver cascade,solver_attemptscan grow unbounded for a long-lived pending challenge. This vector is persisted as JSON incaptcha_log.solver_attempts_json(seesrc-tauri/src/adapters/driven/sqlite/entities/captcha_log.rs), so unbounded growth here becomes unbounded row growth there.Consider capping the vector (e.g., drop-oldest or reject once a
MAX_CAPTCHA_SOLVER_ATTEMPTSthreshold is hit) consistent with the other bounded invariants in this file.♻️ Possible approach
+const MAX_CAPTCHA_SOLVER_ATTEMPTS: usize = 32; + pub fn record_solver_attempt( &mut self, attempt: CaptchaSolverAttempt, ) -> Result<(), DomainError> { self.ensure_pending()?; if attempt.solver().trim().is_empty() || attempt.solver().len() > MAX_CAPTCHA_SOLVER_NAME_BYTES { return Err(validation("CAPTCHA solver name is invalid")); } + if self.solver_attempts.len() >= MAX_CAPTCHA_SOLVER_ATTEMPTS { + return Err(validation("too many CAPTCHA solver attempts recorded")); + } self.solver_attempts.push(attempt); Ok(()) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src-tauri/src/domain/model/captcha.rs` around lines 366 - 379, Bound the number of entries accepted by record_solver_attempt by introducing or reusing a MAX_CAPTCHA_SOLVER_ATTEMPTS limit. When the limit is reached, reject the new attempt or apply the established bounded-history policy, while preserving the existing pending-state and solver-name validation behavior.src-tauri/src/adapters/driven/plugin/tesseract_broker.rs (1)
24-35: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winRedact
Debugon request/response types carrying CAPTCHA image data and solutions.
PluginTesseractRequest.image_dataandTesseractResponse.solutionderive plainDebug, so any future{:?}logging (tracing, error context, panic message) would leak the raw base64 image or — more sensitively — the plaintext solved CAPTCHA answer. This is inconsistent with the redaction pattern already used forCaptchaSolutionelsewhere in the plugin layer, and contradicts the PR's stated goal of redacting solutions and image data.🔒 Suggested redacted Debug impls
-#[derive(Debug, serde::Deserialize)] +#[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] pub(crate) struct PluginTesseractRequest { image_data: String, } -#[derive(Debug, serde::Serialize)] +#[derive(serde::Serialize)] pub(crate) struct TesseractResponse { pub(crate) status: &'static str, #[serde(skip_serializing_if = "Option::is_none")] pub(crate) solution: Option<String>, } + +impl std::fmt::Debug for PluginTesseractRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PluginTesseractRequest") + .field("image_data", &"<redacted>") + .finish() + } +} + +impl std::fmt::Debug for TesseractResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TesseractResponse") + .field("status", &self.status) + .field("solution", &self.solution.as_ref().map(|_| "<redacted>")) + .finish() + } +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src-tauri/src/adapters/driven/plugin/tesseract_broker.rs` around lines 24 - 35, Replace the derived Debug implementations on PluginTesseractRequest and TesseractResponse with manual redacted Debug implementations. Ensure formatting never includes PluginTesseractRequest.image_data or TesseractResponse.solution, while preserving Debug support for the remaining non-sensitive fields and matching the existing CaptchaSolution redaction pattern.src-tauri/src/adapters/driven/plugin/provenance.rs (1)
135-138: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winShare the OCR plugin-name constant here.
grants_forhardcodes"vortex-mod-captcha-ocr"whiletesseract_broker::supports_pluginalready usesOCR_PLUGIN_NAME. Point both checks at the same constant so a rename can’t silently disable OCR.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src-tauri/src/adapters/driven/plugin/provenance.rs` around lines 135 - 138, Update grants_for to replace the hardcoded "vortex-mod-captcha-ocr" comparison with the existing OCR_PLUGIN_NAME constant used by tesseract_broker::supports_plugin, ensuring both OCR checks share the same plugin-name definition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src-tauri/capabilities/default.json`:
- Line 5: Create a dedicated capability for the CAPTCHA browser window instead
of including "captcha-browser-*" in the default capability’s windows list.
Remove that window pattern from default.json, add a separate CAPTCHA capability
scoped to the window, and grant only the CAPTCHA-related commands it requires
rather than core, tray, notification, or dialog permissions.
In `@src-tauri/src/adapters/driven/config/toml_config_store.rs`:
- Line 168: Ensure the captcha_solver_order field in the relevant configuration
DTOs uses an explicit serde default via default_captcha_solver_order, so legacy
config.toml files missing the field retain the default solver cascade. Update or
add coverage around the legacy-file case referenced by the existing tests, while
preserving normalization for explicitly provided values.
In `@src/views/CaptchaSolverSettings.tsx`:
- Around line 149-164: Update the save action in the captcha settings component
to pass a trimmed API key to saveCredential.mutate, matching the existing
validation used by the Save button while leaving the input value unchanged for
editing.
---
Nitpick comments:
In `@src-tauri/src/adapters/driven/plugin/provenance.rs`:
- Around line 135-138: Update grants_for to replace the hardcoded
"vortex-mod-captcha-ocr" comparison with the existing OCR_PLUGIN_NAME constant
used by tesseract_broker::supports_plugin, ensuring both OCR checks share the
same plugin-name definition.
In `@src-tauri/src/adapters/driven/plugin/tesseract_broker.rs`:
- Around line 24-35: Replace the derived Debug implementations on
PluginTesseractRequest and TesseractResponse with manual redacted Debug
implementations. Ensure formatting never includes
PluginTesseractRequest.image_data or TesseractResponse.solution, while
preserving Debug support for the remaining non-sensitive fields and matching the
existing CaptchaSolution redaction pattern.
In `@src-tauri/src/domain/model/captcha.rs`:
- Around line 366-379: Bound the number of entries accepted by
record_solver_attempt by introducing or reusing a MAX_CAPTCHA_SOLVER_ATTEMPTS
limit. When the limit is reached, reject the new attempt or apply the
established bounded-history policy, while preserving the existing pending-state
and solver-name validation behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 52a08574-c9b9-400e-bd8e-a8115a18e100
⛔ Files ignored due to path filters (1)
src-tauri/gen/schemas/capabilities.jsonis excluded by!**/gen/**
📒 Files selected for processing (54)
.github/workflows/plugin-ci.ymlCHANGELOG.mdregistry/registry.tomlsrc-tauri/capabilities/default.jsonsrc-tauri/src/adapters/driven/captcha_interaction.rssrc-tauri/src/adapters/driven/config/toml_config_store.rssrc-tauri/src/adapters/driven/mod.rssrc-tauri/src/adapters/driven/plugin/capabilities.rssrc-tauri/src/adapters/driven/plugin/captcha_solver.rssrc-tauri/src/adapters/driven/plugin/captcha_solver_tests.rssrc-tauri/src/adapters/driven/plugin/extism_loader.rssrc-tauri/src/adapters/driven/plugin/host_functions.rssrc-tauri/src/adapters/driven/plugin/mod.rssrc-tauri/src/adapters/driven/plugin/provenance.rssrc-tauri/src/adapters/driven/plugin/tesseract_broker.rssrc-tauri/src/adapters/driven/plugin/tesseract_broker_tests.rssrc-tauri/src/adapters/driven/plugin/ytdlp_broker.rssrc-tauri/src/adapters/driven/plugin/ytdlp_broker/platform.rssrc-tauri/src/adapters/driven/sqlite/captcha_repo.rssrc-tauri/src/adapters/driven/sqlite/captcha_repo_tests.rssrc-tauri/src/adapters/driven/sqlite/entities/captcha_log.rssrc-tauri/src/adapters/driven/sqlite/migrations/m20260720_000012_add_captcha_solver_attempts.rssrc-tauri/src/adapters/driven/sqlite/migrations/mod.rssrc-tauri/src/adapters/driving/tauri_ipc.rssrc-tauri/src/application/commands/captcha.rssrc-tauri/src/application/commands/captcha_credential.rssrc-tauri/src/application/commands/captcha_tests.rssrc-tauri/src/application/commands/mod.rssrc-tauri/src/application/commands/tests_support.rssrc-tauri/src/application/read_models/captcha_view.rssrc-tauri/src/domain/model/captcha.rssrc-tauri/src/domain/model/config.rssrc-tauri/src/domain/ports/driven/captcha_interaction.rssrc-tauri/src/domain/ports/driven/captcha_solver.rssrc-tauri/src/domain/ports/driven/mod.rssrc-tauri/src/domain/ports/driven/plugin_loader.rssrc-tauri/src/lib.rssrc/App.tsxsrc/components/__tests__/ClipboardIndicator.test.tsxsrc/hooks/__tests__/useAppEffects.test.tssrc/i18n/locales/en.jsonsrc/i18n/locales/fr.jsonsrc/layouts/__tests__/AppLayout.test.tsxsrc/stores/__tests__/settingsStore.test.tssrc/types/captcha.tssrc/types/settings.tssrc/views/CaptchaBrowserWindow.tsxsrc/views/CaptchaChallengePanel.tsxsrc/views/CaptchaSolverSettings.tsxsrc/views/LinkGrabberView/__tests__/LinkGrabberView.test.tsxsrc/views/SettingsView/__tests__/Sections.test.tsxsrc/views/SettingsView/__tests__/SettingsView.test.tsxsrc/views/__tests__/CaptchaBrowserWindow.test.tsxsrc/views/__tests__/CaptchaView.test.tsx
Merging this PR will degrade performance by 18.75%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | from_status_code_500 |
94.7 ns | 154.7 ns | -38.78% |
| ❌ | apply_patch_single_field |
3.1 µs | 4.2 µs | -26.15% |
| ❌ | default_config |
3.3 µs | 4.5 µs | -25.74% |
| ❌ | apply_patch_many_fields |
3.6 µs | 4.7 µs | -22.09% |
| ❌ | create_valid |
252.2 ns | 310.6 ns | -18.78% |
| ❌ | from_status_code_404 |
153.1 ns | 183.9 ns | -16.77% |
| ❌ | full_lifecycle |
192.5 ns | 221.7 ns | -13.16% |
| ⚡ | reject_invalid |
610.3 ns | 493.6 ns | +23.64% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing feat/mat-141-captcha-solvers (39d0e24) with main (4a2d6fe)
There was a problem hiding this comment.
cubic analysis
All reported issues were addressed across 55 files
Linked issue analysis
Linked issue: MAT-141: [Lot 4] Fournir les solvers CAPTCHA automatiques (OCR, service, navigateur)
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | R-01 — OCR solves simple image CAPTCHA when tesseract is installed; absence detected and signaled | PR adds a typed Tesseract broker, detection logic and tests that report missing Tesseract; the UI/tests include user-facing detection messaging. |
| ✅ | R-02 — Failure of a solver falls through to next configured solver and each attempt is logged in captcha_log with solver used | PR records per-solver attempts, adds DB column and migration, updates repo/read-models and command logic to persist solver attempts; cascade/attempt recording is implemented in application code and exposed in views/tests. |
| ✅ | R-03 — AntiCaptcha API key stored in OS keyring, never in SQLite or clear config | PR adds commands to store/remove AntiCaptcha credential via credential store, includes tests/queries for credential status, and explicitly documents/redacts secrets; DB changes avoid storing solutions in cleartext. |
| ✅ | R-04 — No generic subprocess access to plugins; only a typed host function for Tesseract is exposed | PR introduces a dedicated run_tesseract host function, enforces provenance grants so only the verified OCR plugin receives that capability, and warns/ignores unproven declarations; no generic subprocess API is granted to plugins. |
| ✅ | R-05 — Each plugin builds for wasm32-wasip1 + smoke ABI and is referenced in registry.toml with CI checksums | PR adds registry entries with SHA256 checksums for the three plugins, updates plugin CI workflow to include the solvers, and includes release links + verification statements that checksums were validated. |
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
Review feedback is addressed in 6e5b46a. The three non-inline CodeRabbit nitpicks are also covered: solver history is drop-oldest bounded at 64 entries, Tesseract request and response Debug output is redacted with regression coverage, and provenance shares OCR_PLUGIN_NAME. Local and pre-push verification passed clippy, cargo-deny, 1796 Rust unit tests plus 4 integration tests, 742 frontend tests, oxlint, formatting, typecheck, and the production frontend build. |
There was a problem hiding this comment.
All reported issues were addressed across 30 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 20 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src-tauri/src/domain/model/captcha.rs (1)
367-376: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick winBound persisted solver-attempt history. Line 376 only appends, so
captcha_loggrows indefinitely; the 64-entry limit is applied only when building the frontend DTO. This conflicts with the stated bounded, drop-oldest persistence contract.
src-tauri/src/domain/model/captcha.rs#L367-L376: evict the oldest attempt before appending once the retained-history cap is reached.src-tauri/src/domain/model/captcha.rs#L681-L697: assert 64 retained attempts, beginning withsolver-1and ending withsolver-64.Proposed fix
- self.solver_attempts.push(attempt); + if self.solver_attempts.len() >= MAX_EXPOSED_CAPTCHA_SOLVER_ATTEMPTS { + self.solver_attempts.remove(0); + } + self.solver_attempts.push(attempt);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src-tauri/src/domain/model/captcha.rs` around lines 367 - 376, Bound persisted solver-attempt history in record_solver_attempt by evicting the oldest entry before appending when the retention cap is reached, while preserving validation behavior. In src-tauri/src/domain/model/captcha.rs lines 367-376, update the append logic; in lines 681-697, extend the test to assert exactly 64 retained attempts, starting with solver-1 and ending with solver-64.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src-tauri/src/application/read_models/captcha_view.rs`:
- Around line 53-56: Bound solver-attempt history in
CaptchaChallenge::record_solver_attempt before persistence by dropping the
oldest entries once the collection exceeds MAX_EXPOSED_CAPTCHA_SOLVER_ATTEMPTS,
while preserving the newest attempts. Keep the exposed_solver_attempts
projection cap in the read model as defense in depth.
---
Outside diff comments:
In `@src-tauri/src/domain/model/captcha.rs`:
- Around line 367-376: Bound persisted solver-attempt history in
record_solver_attempt by evicting the oldest entry before appending when the
retention cap is reached, while preserving validation behavior. In
src-tauri/src/domain/model/captcha.rs lines 367-376, update the append logic; in
lines 681-697, extend the test to assert exactly 64 retained attempts, starting
with solver-1 and ending with solver-64.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: fd6d73fd-ab12-4738-a92a-ae2fb0f2cc6c
⛔ Files ignored due to path filters (1)
src-tauri/gen/schemas/capabilities.jsonis excluded by!**/gen/**
📒 Files selected for processing (20)
.github/workflows/ci.ymlCHANGELOG.mdsrc-tauri/capabilities/captcha-browser.jsonsrc-tauri/src/adapters/captcha_browser.rssrc-tauri/src/adapters/driven/captcha_interaction.rssrc-tauri/src/adapters/driven/plugin/extism_loader.rssrc-tauri/src/adapters/driven/plugin/registry.rssrc-tauri/src/adapters/driven/plugin/tesseract_broker.rssrc-tauri/src/adapters/driven/plugin/tesseract_broker_tests.rssrc-tauri/src/adapters/driven/plugin/ytdlp_broker/platform.rssrc-tauri/src/adapters/driven/plugin/ytdlp_broker/tests/platform.rssrc-tauri/src/adapters/driving/tauri_ipc.rssrc-tauri/src/adapters/mod.rssrc-tauri/src/application/commands/captcha.rssrc-tauri/src/application/commands/captcha_tests.rssrc-tauri/src/application/read_models/captcha_view.rssrc-tauri/src/domain/model/captcha.rssrc-tauri/src/domain/ports/driven/captcha_interaction.rssrc/views/CaptchaBrowserWindow.tsxsrc/views/__tests__/CaptchaBrowserWindow.test.tsx
💤 Files with no reviewable changes (1)
- src/views/CaptchaBrowserWindow.tsx
🚧 Files skipped from review as they are similar to previous changes (9)
- src-tauri/src/domain/ports/driven/captcha_interaction.rs
- src-tauri/src/adapters/driven/captcha_interaction.rs
- .github/workflows/ci.yml
- CHANGELOG.md
- src-tauri/src/adapters/driven/plugin/registry.rs
- src-tauri/src/adapters/driven/plugin/ytdlp_broker/platform.rs
- src-tauri/src/adapters/driven/plugin/extism_loader.rs
- src-tauri/src/application/commands/captcha.rs
- src-tauri/src/application/commands/captcha_tests.rs
|
Review disposition for the latest CodeRabbit request: intentionally not applying the proposed 64-entry persistence cap. MAT-141 R-02 requires every solver attempt to be logged in |
Dismissed after documented verification: the requested persistence cap conflicts with MAT-141 R-02 audit logging and reverses the prior auditability fix. The IPC/UI projection remains bounded to 64.
Summary
Adds the MAT-141 automatic CAPTCHA solver cascade: local OCR, AntiCaptcha, and an assisted browser window, with configurable ordering and a manual fallback.
Changes
captcha_logvortex-mod-captcha-ocr,vortex-mod-captcha-anticaptcha, andvortex-mod-captcha-browserv1.0.0 using releaseSHA256SUMSSecurity notes
Verification
cargo fmt --checkcargo clippy --workspace --all-targets -- -D warningscargo test --workspace— 1,783 unit tests and 4 integration tests passed; 5 environment/format-dependent tests ignorednpm run lintnpm run typechecknpm test— 741 tests passednpm run buildsha256sum --check SHA256SUMSPlugin releases
Related Issues
Linear: MAT-141
Type of Change
Checklist
cargo fmtandoxlintpasscargo clippy -- -D warningsis cleancargo test+npm test)CHANGELOG.mdunder[Unreleased]Screenshots (if applicable)
Not included: the feature is covered by component tests and the generated Tauri capability schema.
Summary by cubic
Adds the MAT-141 automatic CAPTCHA solver cascade (OCR → AntiCaptcha → assisted browser) with a manual fallback, capability‑scoped windows, and per‑solver attempt history to reduce interruptions while keeping subprocess access locked down.
New Features
vortex-mod-captcha-anticaptcha→vortex-mod-captcha-browsercascade viacaptcha_solver_order; per‑solver attempts (outcome, timing) persisted tocaptcha_logand shown in the CAPTCHA view.vortex-mod-captcha-ocr: trusted absolute paths, fixed args, strict caps/limits; capability only for the verified OCR plugin.captcha-browsercapability and per‑window command ACL; Tauri permissions split intomain-window-commandsandcaptcha-browser-commands.get_balanceforvortex-mod-captcha-anticaptcha), plugins registered with releaseSHA256SUMS, and a Windows Tesseract regression that must run exactly once.Bug Fixes
Written for commit 39d0e24. Summary will update on new commits.
Summary by CodeRabbit