Skip to content

feat(core): add REALITY target scanner#695

Open
parsa222 wants to merge 5 commits into
PasarGuard:devfrom
parsa222:reality-scanner
Open

feat(core): add REALITY target scanner#695
parsa222 wants to merge 5 commits into
PasarGuard:devfrom
parsa222:reality-scanner

Conversation

@parsa222

@parsa222 parsa222 commented Jul 13, 2026

Copy link
Copy Markdown

Summary

Adds a REALITY target scanner: a "Scan target" button in the Xray inbound
editor (next to a REALITY dest/target field) that probes a candidate
handshake target and reports whether it is a good REALITY decoy, plus a
POST /api/core/reality-scan endpoint behind it.

For a given host it checks:

  • TLS 1.3
  • HTTP/2 (ALPN)
  • Key-exchange group (X25519, and post-quantum X25519MLKEM768)
  • HTTP/3 advertisement (Alt-Svc)
  • Certificate validity, issuer/subject, expiry, and the non-wildcard SANs
    (surfaced as suggested valid SNIs)
  • Handshake latency

The verdict (feasible) requires the hard REALITY requirements: TLS 1.3 +
HTTP/2 + a valid certificate, and it is not vetoed unless a non-X25519 group
was definitively observed. Post-quantum and HTTP/3 are reported as extras and
never fail the scan.

How it works

Three isolated probes, so a partial failure degrades one field to "unknown"
instead of failing the whole scan:

  1. Main TLS probe via Python ssl (TLS version, ALPN/H2, certificate parsed
    with cryptography).
  2. A self-contained raw TLS 1.3 ClientHello/ServerHello parser to detect the
    negotiated key group. This is done by hand because the panel's OpenSSL 3.0
    predates X25519MLKEM768 (OpenSSL 3.5), so it cannot rely on the local stack.
  3. Best-effort HTTP/3 detection from the server's Alt-Svc header.

Security

The endpoint could otherwise be an SSRF primitive, so:

  • Every target is resolved and pinned to a public IP. Private, loopback,
    link-local, reserved and cloud-metadata addresses are rejected.
  • Connections are pinned to the resolved IP and never re-resolved, which closes
    the DNS-rebinding hole.
  • DNS resolution is time-bounded and concurrent scans are capped by a semaphore.
  • The endpoint is gated behind the cores:read permission.

API

POST /api/core/reality-scan -> RealityScanResult
Request: { target, sni?, timeout? } (timeout clamped to 1-20s, default 10).

Tests

Backend unit tests in tests/test_reality_scan_unit.py cover target parsing,
the SSRF public-host guard, the ClientHello/ServerHello group probe,
certificate/SAN parsing, and the feasibility logic (no network required; one
opt-in network integration test behind REALITY_SCAN_NETWORK_TEST=1).

Notes

  • i18n: no locale keys are included. All new UI strings use
    t(key, { defaultValue: '...' }), so they render in English and fall back
    cleanly with translations missing. Adding fa/zh/ru entries under
    coreEditor.realityScan.* (and coreConfigModal.scanRealityTarget) is a
    good follow-up.
  • The dashboard uses a small hand-written client (service/reality-scan.ts)
    instead of the generated one, mirroring the generated call shape. It can be
    folded into the generated API client once codegen is re-run.

Summary by CodeRabbit

  • New Features
    • Added REALITY target scanning via a new Core API endpoint (/api/core/reality-scan), returning structured TLS/HTTP/feasibility and certificate metadata.
    • Introduced a “Scan target” action in the Xray editor with multi-target tokenized scanning, bounded parallel execution, per-target status tracking, result summaries, and expandable details.
    • Supports optional SNI override, configurable timeout, and scan cancellation/abort.
  • Tests
    • Added comprehensive unit tests for target parsing, DNS/public-IP resolution, TLS handshake/client-hello parsing, certificate extraction, feasibility logic, concurrency limits, and an optional live network integration test.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: db95018b-65ba-4528-a24c-3798ec4fbe93

📥 Commits

Reviewing files that changed from the base of the PR and between 3ce1f21 and 7cdfa5e.

📒 Files selected for processing (1)
  • dashboard/src/features/core-editor/components/xray/reality-scan-dialog.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • dashboard/src/features/core-editor/components/xray/reality-scan-dialog.tsx

Walkthrough

Adds a REALITY target scanning workflow across backend models, public-network TLS probing, authenticated API handling, frontend service integration, editor controls, result visualization, and unit coverage.

Changes

Reality scan workflow

Layer / File(s) Summary
Request, response, and API contracts
app/models/reality_scan.py, app/operation/core.py, app/routers/core.py, dashboard/src/service/reality-scan.ts
Defines validated request/result shapes and connects the authenticated POST endpoint to the frontend service.
Target parsing and public address resolution
app/utils/reality_scan.py, tests/test_reality_scan_unit.py
Normalizes targets, handles SNI and ports, filters non-public addresses, applies DNS timeouts, and tests resolution behavior.
TLS, certificate, group, and HTTP/3 probes
app/utils/reality_scan.py, tests/test_reality_scan_unit.py
Performs TLS and certificate probing, parses custom handshake responses, detects key exchange groups, checks HTTP/3 advertisements, and validates low-level parsing.
Scan orchestration and concurrency
app/utils/reality_scan.py, tests/test_reality_scan_unit.py
Combines probe results into feasibility status, short-circuits unreachable targets, bounds concurrency, and tests orchestration outcomes.
Editor dialog and REALITY target integration
dashboard/src/features/core-editor/components/xray/reality-scan-dialog.tsx, dashboard/src/features/core-editor/components/xray/xray-inbounds-section.tsx
Adds the scan action, optional initial SNI, cancellable request flow, loading and error states, and detailed result rendering.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant XrayInboundsSection
  participant RealityScanDialog
  participant BackendAPI
  participant scan_reality_target
  participant TLSProbes
  XrayInboundsSection->>RealityScanDialog: Open with target and initial SNI
  RealityScanDialog->>BackendAPI: Submit target, SNI, and timeout
  BackendAPI->>scan_reality_target: Resolve and scan target
  scan_reality_target->>TLSProbes: Run TLS, group, and HTTP/3 probes
  TLSProbes-->>scan_reality_target: Negotiation and certificate metadata
  scan_reality_target-->>BackendAPI: RealityScanResult
  BackendAPI-->>RealityScanDialog: Return scan result
  RealityScanDialog-->>XrayInboundsSection: Render feasibility and probe details
Loading

Poem

I’m a rabbit with packets tucked under my hat,
Scanning TLS where the wild servers sat.
Curves hop, certs glow, and ALPN sings,
HTTP/3 flutters on Alt-Svc wings.
The REALITY field now knows where to leap.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a REALITY target scanner.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread app/utils/reality_scan.py Fixed
Comment thread app/utils/reality_scan.py Fixed
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Note

Unit test generation is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@ImMohammad20000

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 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 `@app/utils/reality_scan.py`:
- Around line 549-554: Move _resolve_public_ip_async inside the
_get_scan_semaphore context in scan_reality_target, keeping DNS_TIMEOUT clamping
and the subsequent _scan_sync call intact. Update
tests/test_reality_scan_unit.py lines 390-406 in test_scan_concurrency_is_capped
to track and assert resolver concurrency is capped by the same semaphore.

In `@dashboard/src/features/core-editor/components/xray/reality-scan-dialog.tsx`:
- Around line 62-69: Update the dialog reset effect and runScan flow to cancel
any in-flight scan when the dialog closes or resets. Reuse the existing signal
parameter in runScan, abort the previous request before clearing state, and
ensure stale scan completions cannot update result, errorMessage, or isScanning
for the new target.

In
`@dashboard/src/features/core-editor/components/xray/xray-inbounds-section.tsx`:
- Around line 4012-4016: The REALITY scanner must preserve the configured SNI
when scanning a target. In
dashboard/src/features/core-editor/components/xray/xray-inbounds-section.tsx
lines 4012-4016, extract the configured server name alongside the target and
pass it when opening the scanner; in
dashboard/src/features/core-editor/components/xray/reality-scan-dialog.tsx lines
71-81, accept that SNI and include it in the scanRealityTarget request.
🪄 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: 71c9437b-f3a3-4b3d-9741-832ce2e49795

📥 Commits

Reviewing files that changed from the base of the PR and between 5b9e799 and 17381e7.

📒 Files selected for processing (8)
  • app/models/reality_scan.py
  • app/operation/core.py
  • app/routers/core.py
  • app/utils/reality_scan.py
  • dashboard/src/features/core-editor/components/xray/reality-scan-dialog.tsx
  • dashboard/src/features/core-editor/components/xray/xray-inbounds-section.tsx
  • dashboard/src/service/reality-scan.ts
  • tests/test_reality_scan_unit.py

Comment thread app/utils/reality_scan.py Outdated
Comment thread dashboard/src/features/core-editor/components/xray/reality-scan-dialog.tsx Outdated
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Request timed out after 900000ms (requestId=715f7044-de8a-4e6d-8556-e73d11375206)

Add a POST /api/core/reality-scan endpoint and a "Scan target" button in
the Xray inbound editor to probe a candidate REALITY dest and report
whether it is a good decoy: TLS 1.3, HTTP/2 (ALPN), X25519 / post-quantum
(X25519MLKEM768) key exchange, HTTP/3 advertisement, certificate validity
and SANs, plus handshake latency.

Security: every target is resolved and pinned to a public IP, so private,
loopback, link-local, reserved and cloud-metadata addresses are rejected
and the pinned connection cannot be bypassed by DNS rebinding. DNS and
concurrent scans are bounded, and each sub-probe is isolated so a partial
failure degrades a single field to "unknown" instead of failing the scan.
The key-group probe is a self-contained raw TLS 1.3 ClientHello/ServerHello
parser, so it detects MLKEM even where the panel's OpenSSL predates it.

Gated behind the cores:read permission. Includes backend unit tests.
@ImMohammad20000

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

parsa added 3 commits July 13, 2026 07:22
- _h3_probe: bound the response read with an absolute deadline (was a per-recv
  timeout only) and cap the whole scan with asyncio.wait_for, so a slowloris
  target cannot pin a scan slot/thread for a long time.
- parse_target: reject control characters (CR/LF/NUL/tab) in target and sni,
  preventing header injection into the h3 probe request.
- dashboard: coerce a non-string FastAPI 422 detail (array/object) to text in
  getErrorMessage so a validation error cannot crash the dialog render.
- dashboard: when every REALITY serverName is a wildcard, scan without an SNI
  instead of sending an invalid "*.host" SNI.
Turn the REALITY scanner into a batch tool: add targets as chips (type and
press space, comma, or enter; paste a whitespace/comma/newline list) and each
is probed against the existing /api/core/reality-scan endpoint, bounded to a
small client-side concurrency pool (the backend semaphore already caps real
concurrency). Results stream in as compact per-target rows (verdict, TLS 1.3 /
HTTP/2 badges, latency) that expand to the full detail card, with a "suitable
only" filter and a running suitable/total count. A single target keeps the
original detailed view. Frontend-only; no new endpoint or backend change, and
reuses the abort/request-owner guard.
- Stop now optimistically clears the scanning state (and detaches the run) so
  control returns immediately even though the shared fetch client drops the
  abort signal; the request-owner guard still ignores any late results.
- The targets chip input is fully inert during a scan: the text input and each
  chip's remove button are disabled, not just pointer-events, so a chip cannot
  be removed by keyboard mid-scan.
- The input stays editable at the target cap (no frozen draft) and a hint shows
  when the maximum is reached.
@parsa222

Copy link
Copy Markdown
Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@dashboard/src/features/core-editor/components/xray/reality-scan-dialog.tsx`:
- Around line 361-374: Update the effect watching open in RealityScanDialog so
transitioning to false aborts the active controller and clears abortRef.current.
Preserve the existing reset behavior when open is true, ensuring scans stop
immediately on close while retaining unmount cleanup as a safeguard.
🪄 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: f5fb7057-21f6-4ace-b549-cf0527a2d174

📥 Commits

Reviewing files that changed from the base of the PR and between 5b9e799 and 3ce1f21.

📒 Files selected for processing (8)
  • app/models/reality_scan.py
  • app/operation/core.py
  • app/routers/core.py
  • app/utils/reality_scan.py
  • dashboard/src/features/core-editor/components/xray/reality-scan-dialog.tsx
  • dashboard/src/features/core-editor/components/xray/xray-inbounds-section.tsx
  • dashboard/src/service/reality-scan.ts
  • tests/test_reality_scan_unit.py

The reset effect only aborted the running scan when the dialog opened; on
close the parent keeps the component mounted, so the unmount cleanup never
ran and probes kept hitting targets and patching state in the background.
Abort and clear the scanning state on open -> false too.

@parsa222 parsa222 left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

next commit

@ImMohammad20000

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants