DOCUMENTATION
v1.0  ·  WINDOWS & MACOS

OVERVIEW

MDRS (Multi-threaded Diagnostic & Recognition Suite) is a desktop application for bulk string classification. It loads a plain-text wordlist, runs each entry through a configurable check module, and classifies results as HIT, FAIL, OTP, or ERROR in real time.

The application is built on PySide6 and ships as a self-contained standalone binary — no Python runtime or installation required on the target machine. It runs identically on Windows and macOS.

KEY FEATURES

FeatureDetails
Check modulesConfig-driven, drop-in Python classes registered via REGISTRY
Threading1–20 concurrent worker threads via ThreadPoolExecutor
Result typesHIT, FAIL, OTP, ERROR with per-type color coding
Proxy supportSingle proxy or rotating list, optional auth
Retry logicPer-entry retries on ERROR + batch retry of all FAILs
ExportHits, OTP, all results, clipboard copy
Pause / ResumeNon-destructive mid-run pause via threading event
Live statsHit count, fail count, errors, checks/sec, progress bar

QUICKSTART

Get up and running in three steps:

1.  Click combolist and load a .txt file of strings (one per line)
2.  Select a check module from the dropdown
3.  Click start

Results stream into the upper table in real time. Hits appear in the lower table. When the run completes, use the export buttons to save results.

TIP
For your first run, use a small wordlist (100–500 lines) to verify your check module is configured correctly before loading a large file.

INSTALLATION

MDRS ships as a standalone folder build — no installer, no Python required on the target machine.

WINDOWS

# Unzip the release archive
MDRS-win64.zip
  └── MDRS/
      ├── reap.exe          # entry point
      ├── checks.py
      ├── worker.py
      └── ... (runtime libs)

# Run directly — no install step
MDRS\reap.exe

MACOS

# Unzip and clear quarantine
unzip MDRS-macos.zip
xattr -cr MDRS/

# Run
./MDRS/reap
GATEKEEPER
On macOS, running xattr -cr MDRS/ removes the quarantine flag set by Gatekeeper. This is required for unsigned builds. The app does not make any network calls beyond what your check modules explicitly implement.

REQUIREMENTS

PlatformMinimum OSArch
WindowsWindows 10 (build 1903+)x86_64
macOSmacOS 12 Montereyx86_64, arm64

YOUR FIRST RUN

# Example wordlist: strings.txt
access-production-ab123456-1234-5678-9012-000000000001
access-production-cd789012-3456-7890-1234-000000000002
access-production-ef345678-5678-9012-3456-000000000003
... (one entry per line)

Load the file via the combolist button. The file label updates with the filename and line count. Select your check module, optionally set a parameter, then click start.

The title bar updates live: MDRS [done/total] THREADS: 20 HITS: n OTP: n

WORDLIST / COMBOLIST

MDRS accepts any UTF-8 plain-text file with one entry per line. Invalid bytes are silently ignored. Blank lines and lines consisting only of whitespace are skipped on load.

LOADING

Click the combolist button to open a file picker. Supported extensions: .txt and all files. After loading, the label below the button shows the filename and total line count.

LIMITS

There is no hard line count limit. Large files (1M+ lines) load fine but keep in mind memory usage scales linearly — each line is stored as a Python string in the self.lines list.

NOTE
Lines are stripped of trailing \n and \r characters but leading/trailing spaces are preserved. If your check module is sensitive to whitespace, pre-process your wordlist accordingly.

RELOADING

The combolist button is disabled during a run and re-enabled on completion. You can load a new file at any time when idle — the previous wordlist is replaced entirely.

CHECK MODULES

Check modules define the validation logic applied to each string in the wordlist. They are Python classes registered in checks.py via the REGISTRY dict. The UI dropdown is populated dynamically from this registry — adding a new module requires no UI changes.

STRUCTURE

# checks.py

class MyCheck:
    name       = "MY_CHECK"        # internal key
    label      = "My Check"        # shown in dropdown
    param_hint = "enter parameter"  # placeholder in param field
                                       # use "No parameter" to disable field

    def __init__(self, param: str):
        # validate param here, raise ValueError on bad input
        self.param = param

    def run(self, value: str) -> "Result":
        # return Result(value, "HIT"|"FAIL"|"OTP"|"ERROR", detail)
        ...

RESULT OBJECT

from dataclasses import dataclass

@dataclass
class Result:
    value:  str   # the original input string
    status: str   # "HIT", "FAIL", "OTP", "ERROR"
    detail: str   # human-readable info shown in table col 3

RESULT TYPES

StatusColorMeaningGoes to hits table?
HIT#00c44fEntry satisfied the check conditionYes
FAIL#8a1a1aEntry did not satisfy the conditionNo
OTP#b87800Secondary match (module-defined)No
ERROR#7a2020Exception during check executionNo

All four result types appear in the upper (all results) table. Only HIT results appear in the lower table. FAIL entries are tracked separately for the retry fails function.

THREADING

MDRS uses a QThread + RunnerWorker architecture. The worker runs in a dedicated thread and dispatches entries to a concurrent.futures.ThreadPoolExecutor with a configurable pool size.

Results are emitted in batches back to the main thread via Qt signals, keeping the UI responsive at all thread counts. Each batch triggers a single UI update cycle rather than per-item redraws.

RECOMMENDED THREAD COUNTS

Check TypeRecommended ThreadsReason
Pure local (regex, format, length)1–4CPU-bound, threads add overhead
DNS lookups8–16I/O-bound, benefits from concurrency
HTTP requests10–20High latency, maximum concurrency helps
WARNING
Setting threads to 20 for local checks that complete in microseconds can actually reduce throughput due to context-switching overhead. Start at 4 and increase if checks involve any I/O.

LEFT PANEL

The left panel contains all run configuration controls. It is 230px wide and fixed.

combolist
button
Opens a file picker to load a .txt wordlist. Disabled during runs.
check dropdown
combobox
Selects the active check module. Populated from REGISTRY in checks.py.
param field
text input
Module-specific parameter. Auto-disabled when the module's param_hint contains "no parameter".
threads
spinbox 1–20
Thread pool size. Default: 20.
start
button (primary)
Begins a run. Requires a loaded wordlist. Clears previous results.
stop
button
Signals the worker to stop after finishing in-flight checks. Not immediate.
pause / resume
button
Pauses dispatch of new entries. In-flight checks complete. Click again to resume.
clear results
button
Clears both tables, all counters, progress bar, and internal result lists.
retry fails
button
Re-queues all FAIL entries from the last run as a new wordlist and starts a new run.
proxy field
text input
Single proxy in host:port or user:pass@host:port format.
proxy toggle
toggle button
Enables/disables proxy. Toggling off clears the active proxy list.
timeout
spinbox 1–60s
Per-request network timeout in seconds. Default: 10s.
delay
spinbox 0–5000ms
Sleep between entry dispatches per thread. Default: 0ms.
retries
spinbox 1–10
Number of retry attempts per entry on ERROR. Default: 1.

RESULTS TABLES

The right panel contains two stacked tables separated by a horizontal divider.

UPPER TABLE — ALL RESULTS

Every entry processed is shown here regardless of status. Rows are color-coded by status. The table holds a rolling window of the last 60 rows — older rows scroll off as new ones arrive.

LOWER TABLE — HITS ONLY

Only HIT results appear here. Holds the last 20 rows. Use the export hits button to save the full hit list — the table is a view window, not the complete dataset.

COLUMNS

ColumnContent
1 (value)The original input string from the wordlist
2 (status)HIT / FAIL / OTP / ERROR — color coded
3 (detail)Module-provided detail string (e.g. "valid format", "length 3 < min")

STATS BAR

Displays five live counters below the tables:

LOADED
integer
Total entries in the loaded wordlist.
HITS
integer
Cumulative HIT count for the current run.
FAILS
integer
Cumulative FAIL count. Also used by retry fails.
ERRORS
integer
Cumulative ERROR count.
CHECKS/SEC
float
Throughput: entries completed divided by elapsed seconds.

PROGRESS & STATUS

The progress bar below the stats bar fills from 0% to 100% as entries are processed. It resets at the start of each run including retry runs.

The status label in the bottom-left of the results panel shows current state: ready, running..., stopping..., done in Xs - N hits, or cleared.

The window title bar mirrors key stats during a run: MDRS [done/total] THREADS: N HITS: N OTP: N

EXPORT CONTROLS

Four export actions sit in the bottom footer row of the results panel. All are disabled until a run completes and the relevant result type has at least one entry.

export hits
button
Saves all HIT detail strings to a .txt file, one per line.
export otp
button
Saves all OTP detail strings to a .txt file, one per line.
export all
button
Saves every result as value|status|detail, one per line.
copy last
button
Copies the detail string of the most recent HIT to the clipboard.

THREAD COUNT

The threads spinbox (1–20) sets the ThreadPoolExecutor max_workers. This value is read at run start and cannot be changed mid-run.

# worker.py — how threads_count is used
with ThreadPoolExecutor(max_workers=threads_count) as pool:
    futures = {pool.submit(self._check_one, line): line
               for line in self.lines}

DELAY

Adds a time.sleep(delay_ms / 1000) call after each entry dispatch. Applied per thread, so with 20 threads and a 100ms delay the effective global rate is roughly 200 entries/sec max.

Range
0 – 5000 ms
Default
0 ms (no delay)
Use case
Rate-limited external endpoints where hammering causes 429s or bans

TIMEOUT

Sets the per-request network timeout passed to check modules. Check modules are responsible for honoring this value — it is passed as a parameter at run start and available via self.timeout.

Range
1 – 60 seconds
Default
10 seconds
NOTE
Timeout applies to each individual request, not the entire run. A timed-out check returns an ERROR result with detail = "timeout".

RETRIES

On ERROR, the worker retries the entry up to retries times before recording the final ERROR result. Each retry respects the configured delay.

Range
1 – 10
Default
1 (no retries beyond first attempt)
WARNING
Setting retries to 10 with a high thread count and slow endpoint can significantly extend total run time. Use 2–3 for most cases.

PROXY

MDRS supports both single-proxy and proxy-list modes.

SINGLE PROXY

Enter a proxy in the left panel field and toggle proxy on:

# formats accepted
127.0.0.1:8080
192.168.1.1:3128
user:pass@10.0.0.1:8080

PROXY LIST

Click load proxies to import a .txt file of proxies, one per line. The proxy field updates to show the count of loaded proxies.

# proxies.txt — one per line
10.0.0.1:8080
10.0.0.2:8080
user:pass@10.0.0.3:3128

PROXY ROTATION

When a proxy list is loaded, the worker selects proxies in round-robin order per request. Each thread picks the next proxy from the shared list atomically.

AUTH FORMAT

user:password@host:port

# example
proxyuser:hunter2@proxy.example.com:8080

TOGGLE

The proxy off / proxy on button is a stateful toggle. Setting it to proxy off during a run takes effect on the next entry dispatch. It also calls set_proxies([]) which clears the loaded proxy list.

MODULE OVERVIEW

All check modules live in checks.py. The file exports a REGISTRY dict mapping module names to classes, and optionally a set_proxies() function used by the proxy toggle.

# checks.py structure

REGISTRY = {
    "FORMAT_CHECK":  FormatCheck,
    "LENGTH_RANGE":  LengthRangeCheck,
    "REGEX_MATCH":   RegexCheck,
    "MY_MODULE":     MyModule,
    # add entries here
}

def set_proxies(proxy_list: list[str]):
    global _proxies
    _proxies = proxy_list

WRITING A MODULE

import re
from dataclasses import dataclass

@dataclass
class Result:
    value:  str
    status: str
    detail: str

class FormatCheck:
    name       = "FORMAT_CHECK"
    label      = "Format Check"
    param_hint = "regex pattern  e.g. ^access-production-"

    def __init__(self, param: str):
        if not param:
            raise ValueError("pattern required")
        self.pattern = re.compile(param)

    def run(self, value: str) -> Result:
        try:
            if self.pattern.search(value):
                return Result(value, "HIT", "pattern matched")
            return Result(value, "FAIL", "no match")
        except Exception as e:
            return Result(value, "ERROR", str(e))

REGISTRY

The REGISTRY dict is iterated at app startup to populate the check dropdown. Keys are internal names, values are uninstantiated classes. The UI calls cls(param_text) on run start.

TIP
Order in the dict is the order modules appear in the dropdown. Put your most-used module first.

PARAMETERS

Each module receives the contents of the param field as a string at instantiation. To disable the param field for a module, set param_hint to any string containing "no parameter" (case-insensitive):

param_hint = "No parameter needed"   # disables the input field
param_hint = "enter min:max length"  # enables input with placeholder text

WORKER OVERVIEW

RunnerWorker in worker.py is a QObject moved to a QThread. It owns the ThreadPoolExecutor and is responsible for dispatching entries, collecting results, and emitting Qt signals back to the main thread.

class RunnerWorker(QObject):
    batch    = Signal(list)         # list[Result]
    progress = Signal(int, int)     # done, total
    stats    = Signal(int, int, int)# hits, fails, errors
    finished = Signal(float)        # elapsed seconds

QT SIGNALS

SignalPayloadFrequency
batchlist[Result]Every N results (batched for performance)
progress(done: int, total: int)With each batch
stats(hits, fails, errors)With each batch
finishedelapsed: floatOnce, when run completes or is stopped

RUN LIFECYCLE

start clicked
  │
  ├─ validate wordlist loaded
  ├─ instantiate check module (raises ValueError on bad param)
  ├─ reset counters + clear tables
  ├─ create QThread + RunnerWorker
  ├─ connect signals
  ├─ thread.start()
  │
  │  [worker thread]
  ├─ ThreadPoolExecutor dispatches all entries
  ├─ results collected → batched → emit batch signal
  ├─ emit progress + stats per batch
  │
  ├─ all futures complete (or stop() called)
  ├─ emit finished(elapsed)
  │
  └─ [main thread]
     ├─ re-enable buttons
     ├─ enable export buttons if results exist
     └─ update status label + window title

PAUSE / RESUME

Pause is implemented via a threading.Event. The worker checks the event before dispatching each entry. In-flight checks (already submitted to the executor) are not interrupted — they complete normally.

# worker.py
self._pause_event = threading.Event()
self._pause_event.set()  # set = running, clear = paused

def pause(self):  self._pause_event.clear()
def resume(self): self._pause_event.set()

# in dispatch loop
self._pause_event.wait()  # blocks until resumed

STOP

Calling stop() sets a _stop flag. The dispatch loop checks this flag before each entry and exits early. Already-dispatched futures are cancelled where possible; the executor is shut down with cancel_futures=True.

NOTE
Stop is not instantaneous. The status label shows stopping... until all in-flight checks finish. For HTTP checks with a 10s timeout, stopping may take up to 10s.

DETAIL FIELD

The detail field of a Result is a free-form string provided by the check module. It appears in column 3 of both tables and is the value written to export files.

For HIT results, detail is typically the most useful extracted data — e.g. a validated token, matched substring, or parsed value. For FAIL and ERROR it should explain why the check failed.

# good detail strings
Result(value, "HIT",   "valid format — prefix matches")
Result(value, "FAIL",  "length 3 < minimum 8")
Result(value, "OTP",   "secondary pattern matched")
Result(value, "ERROR", "timeout after 10s")

EXPORT FILE FORMAT

HITS / OTP FILES

# hits.txt / otp.txt — one detail string per line
valid format — prefix ok
valid format — prefix ok
secondary match

ALL RESULTS FILE

# all.txt — value|status|detail
access-production-ab123456-...|HIT|valid format — prefix ok
access-production-cd000000-...|FAIL|length segment mismatch
access-production-ef999999-...|OTP|secondary pattern matched
access-production-gh111111-...|ERROR|timeout after 10s
NOTE
The all-results file uses | as delimiter. If your detail strings contain |, split on the first two occurrences only when parsing.

RETRY FAILS

After a run completes, retry fails re-queues the internal fails_list as self.lines and calls start_run(). This is useful for:

ScenarioHow to use
Verify FAILs with a different moduleChange the check dropdown, then click retry fails
Re-run with looser parametersChange the param field, click retry fails
Re-run with proxy enabledEnable proxy, click retry fails
Re-run after fixing a module bugEdit checks.py (requires restart), then retry fails
WARNING
retry fails calls start_run() which clears all result lists including fails_list. The retry list is captured before the clear. Make sure you've exported anything you need before retrying.

BUILD & DISTRIBUTION

MDRS is compiled with Nuitka in standalone mode. The entry point is reap.py on both platforms.

WINDOWS

python -m nuitka --standalone --onefile-tempdir-spec="%TEMP%\MDRS" ^
  --windows-console-mode=disable ^
  --output-dir=dist ^
  reap.py

MACOS

python -m nuitka --standalone \
  --macos-create-app-bundle \
  --output-dir=dist \
  reap.py

# fix multimedia dylibs after build
install_name_tool -change @rpath/... @loader_path/... dist/reap.app/...
NOTE
Onefile builds are avoided to prevent AV false positives from self-extracting executables. Standalone folder builds are used instead.

PLATFORM NOTES

MACOS — GATEKEEPER

xattr -cr ./MDRS/    # clear quarantine recursively

MACOS — DEVICE DETECTION

On macOS, pymobiledevice3 is used for iOS device detection via async daemon thread. The CFBundleName AppKit fix is applied at runtime to set the correct app name in the macOS menu bar.

WINDOWS — ANTIVIRUS

Nuitka standalone folder builds (not onefile) are used specifically to avoid AV heuristics that flag self-extracting archives. If your AV flags the binary, add the MDRS folder to your exclusion list.

FONT

The UI uses DejaVu Sans Mono with fallbacks to Consolas (Windows), Menlo (macOS), and Courier New. Font antialiasing is disabled for a sharper monospace rendering consistent across platforms.

PERFORMANCE TUNING

BottleneckSolution
UI freezing during large runsResults are batched — reduce batch emit frequency in worker.py if needed
Low throughput on local checksReduce threads (1–4), CPU context-switching is the bottleneck
High ERROR rate on HTTP checksIncrease timeout, reduce threads, add delay, enable proxy rotation
Memory usage on 1M+ wordlistsSplit wordlist into chunks and run sequentially
Table rendering slowTables use setUpdatesEnabled(False/True) around batch inserts — already optimized

TROUBLESHOOTING

ProblemCauseFix
App won't open on macOS Gatekeeper quarantine Run xattr -cr ./MDRS/
Param field disabled Module's param_hint contains "no parameter" Expected behavior — module takes no input
"Check config error" on start Module __init__ raised ValueError Fix the parameter value per the module's param_hint
All results are ERROR Network issue, bad proxy, or module exception Check proxy config, increase timeout, inspect module code
Stop button unresponsive In-flight checks still completing Wait — status shows "stopping..." until they finish
Export buttons stay disabled No results of that type exist Only enabled when hits/otp/all lists are non-empty
Retry fails does nothing fails_list is empty Only works if previous run had FAIL results
Skull animation not visible Font rendering issue Ensure DejaVu Sans Mono or Courier New is installed