redrun-scan 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. redrun_scan-0.1.0/PKG-INFO +178 -0
  2. redrun_scan-0.1.0/README.md +153 -0
  3. redrun_scan-0.1.0/pyproject.toml +41 -0
  4. redrun_scan-0.1.0/redrun/__init__.py +0 -0
  5. redrun_scan-0.1.0/redrun/__main__.py +5 -0
  6. redrun_scan-0.1.0/redrun/app/__init__.py +1 -0
  7. redrun_scan-0.1.0/redrun/app/auth.py +50 -0
  8. redrun_scan-0.1.0/redrun/app/db.py +57 -0
  9. redrun_scan-0.1.0/redrun/app/events.py +31 -0
  10. redrun_scan-0.1.0/redrun/app/models.py +60 -0
  11. redrun_scan-0.1.0/redrun/app/phases.py +257 -0
  12. redrun_scan-0.1.0/redrun/app/runner.py +101 -0
  13. redrun_scan-0.1.0/redrun/app/server.py +281 -0
  14. redrun_scan-0.1.0/redrun/app/store.py +159 -0
  15. redrun_scan-0.1.0/redrun/cli.py +409 -0
  16. redrun_scan-0.1.0/redrun/cloud.py +112 -0
  17. redrun_scan-0.1.0/redrun/engine/__init__.py +0 -0
  18. redrun_scan-0.1.0/redrun/engine/active_scanner.py +887 -0
  19. redrun_scan-0.1.0/redrun/engine/config.py +42 -0
  20. redrun_scan-0.1.0/redrun/engine/docker/egress-proxy/Dockerfile +23 -0
  21. redrun_scan-0.1.0/redrun/engine/docker/egress-proxy/entrypoint.sh +55 -0
  22. redrun_scan-0.1.0/redrun/engine/docker_sandbox.py +190 -0
  23. redrun_scan-0.1.0/redrun/engine/egress.py +98 -0
  24. redrun_scan-0.1.0/redrun/engine/exploitation.py +231 -0
  25. redrun_scan-0.1.0/redrun/engine/nuclei_scanner.py +170 -0
  26. redrun_scan-0.1.0/redrun/engine/planner.py +211 -0
  27. redrun_scan-0.1.0/redrun/engine/recon.py +612 -0
  28. redrun_scan-0.1.0/redrun/engine/reporter.py +315 -0
  29. redrun_scan-0.1.0/redrun/engine/sandbox.py +187 -0
  30. redrun_scan-0.1.0/redrun/engine/schemas.py +258 -0
  31. redrun_scan-0.1.0/redrun/engine/scope.py +237 -0
  32. redrun_scan-0.1.0/redrun/engine/tools/__init__.py +0 -0
  33. redrun_scan-0.1.0/redrun/engine/tools/base.py +244 -0
  34. redrun_scan-0.1.0/redrun/engine/tools/broken_auth.py +183 -0
  35. redrun_scan-0.1.0/redrun/engine/tools/sqli.py +162 -0
  36. redrun_scan-0.1.0/redrun/engine/tools/ssrf.py +176 -0
  37. redrun_scan-0.1.0/redrun/engine/tools/xss.py +104 -0
  38. redrun_scan-0.1.0/redrun/engine/zone.py +159 -0
  39. redrun_scan-0.1.0/redrun/licensing.py +116 -0
  40. redrun_scan-0.1.0/redrun/output.py +93 -0
  41. redrun_scan-0.1.0/redrun_scan.egg-info/PKG-INFO +178 -0
  42. redrun_scan-0.1.0/redrun_scan.egg-info/SOURCES.txt +60 -0
  43. redrun_scan-0.1.0/redrun_scan.egg-info/dependency_links.txt +1 -0
  44. redrun_scan-0.1.0/redrun_scan.egg-info/entry_points.txt +2 -0
  45. redrun_scan-0.1.0/redrun_scan.egg-info/requires.txt +19 -0
  46. redrun_scan-0.1.0/redrun_scan.egg-info/top_level.txt +1 -0
  47. redrun_scan-0.1.0/setup.cfg +4 -0
  48. redrun_scan-0.1.0/tests/test_active_scanner.py +63 -0
  49. redrun_scan-0.1.0/tests/test_auth.py +76 -0
  50. redrun_scan-0.1.0/tests/test_db.py +29 -0
  51. redrun_scan-0.1.0/tests/test_desktop_handoff.py +59 -0
  52. redrun_scan-0.1.0/tests/test_events.py +42 -0
  53. redrun_scan-0.1.0/tests/test_integration_e2e.py +53 -0
  54. redrun_scan-0.1.0/tests/test_models.py +29 -0
  55. redrun_scan-0.1.0/tests/test_phases.py +51 -0
  56. redrun_scan-0.1.0/tests/test_post_comment.py +88 -0
  57. redrun_scan-0.1.0/tests/test_runner.py +48 -0
  58. redrun_scan-0.1.0/tests/test_scan_fail_on.py +39 -0
  59. redrun_scan-0.1.0/tests/test_serve_cmd.py +65 -0
  60. redrun_scan-0.1.0/tests/test_server.py +312 -0
  61. redrun_scan-0.1.0/tests/test_sslyze_adapter.py +51 -0
  62. redrun_scan-0.1.0/tests/test_store.py +95 -0
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.4
2
+ Name: redrun-scan
3
+ Version: 0.1.0
4
+ Summary: RedRun — continuous, proof-backed security testing you run yourself.
5
+ Author: RedRun
6
+ License: Proprietary
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: pydantic>=2.10.0
10
+ Requires-Dist: httpx>=0.28.0
11
+ Requires-Dist: dnspython>=2.7.0
12
+ Requires-Dist: beautifulsoup4>=4.12.0
13
+ Requires-Dist: lxml>=5.3.0
14
+ Requires-Dist: cryptography>=42.0.0
15
+ Requires-Dist: fastapi>=0.115.0
16
+ Requires-Dist: uvicorn[standard]>=0.30.0
17
+ Provides-Extra: ai
18
+ Requires-Dist: anthropic>=0.40.0; extra == "ai"
19
+ Provides-Extra: tools
20
+ Requires-Dist: sslyze>=6.0.0; extra == "tools"
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
23
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
24
+ Requires-Dist: httpx>=0.28.0; extra == "dev"
25
+
26
+ # RedRun CLI
27
+
28
+ **Proof-backed security testing you run yourself.** A standalone command-line tool
29
+ that runs RedRun's scanning engine **locally on your machine** — passive recon and
30
+ real, evidence-verified exploitation (SQLi, XSS, SSRF, IDOR, broken auth) — with
31
+ no cloud dependency.
32
+
33
+ This is the licensed-software M1 (see `../LICENSED-SOFTWARE-PLAN.md`): the engine
34
+ runs inside your own environment, so authorization is implicit (you point it at
35
+ your own assets) and deeper/internal testing is possible.
36
+
37
+ ## Install (customers)
38
+ ```bash
39
+ # Recommended: isolated install on PATH
40
+ pipx install ./dist/redrun-0.1.0-py3-none-any.whl
41
+ # or
42
+ pip install ./dist/redrun-0.1.0-py3-none-any.whl
43
+ ```
44
+ For AI executive summaries: `pipx install "redrun[ai]"` and set `ANTHROPIC_API_KEY`.
45
+
46
+ ## Licensing
47
+ Passive scans are **free**. Active exploitation requires a license.
48
+ ```bash
49
+ redrun license status # show current license
50
+ redrun license activate your.lic # install a license file (offline-verified)
51
+ ```
52
+ Licenses are Ed25519-signed and verified **offline** with an embedded public key —
53
+ no server call, air-gap friendly. Tampering invalidates the signature.
54
+
55
+ ## Usage
56
+ ```bash
57
+ # Passive scan — recon, headers, TLS, DNS, exposed paths, nuclei CVE templates
58
+ redrun scan example.com
59
+
60
+ # Active exploitation — requires explicit authorization
61
+ redrun scan staging.myapp.com --active --authorized
62
+
63
+ # Production-looking host needs an extra confirmation
64
+ redrun scan myapp.com --active --authorized --confirm-production
65
+
66
+ # Extra in-scope hosts, JSON export, kernel sandbox
67
+ redrun scan myapp.com --active --authorized --scope api.myapp.com --json out.json
68
+ redrun scan myapp.com --active --authorized --sandbox docker
69
+ ```
70
+
71
+ ### Options
72
+ | Flag | Meaning |
73
+ |---|---|
74
+ | `--active` | run real exploitation (not just passive observation) |
75
+ | `--authorized` | confirm you own / may test the target (**required for `--active`**) |
76
+ | `--confirm-production` | authorize an active scan against a production-looking host |
77
+ | `--scope a,b` | additional in-scope hosts |
78
+ | `--sandbox local\|docker\|auto` | containment for active scans (default `local` egress guard; `docker` = kernel iptables allowlist, needs a Docker host) |
79
+ | `--json FILE` | write full results to JSON |
80
+ | `--no-ai` | skip the AI executive summary |
81
+
82
+ ## Local console (web UI)
83
+
84
+ RedRun ships a local web console — a targets dashboard, per-target scan history
85
+ with live progress, an add-target flow, and license/settings — served by
86
+ `redrun serve` from a single loopback process. The UI is a Vite + React SPA in
87
+ `ui/`, built to `redrun/app/static/` and served alongside the token-guarded
88
+ `/v1` API on `127.0.0.1`.
89
+
90
+ ```bash
91
+ # one-time: install UI deps
92
+ npm --prefix ui install
93
+ # build the UI (outputs to redrun/app/static/, served by `redrun serve`)
94
+ npm --prefix ui run build
95
+ # run the console — prints a URL containing the per-launch token
96
+ redrun serve
97
+ ```
98
+
99
+ `redrun serve` binds `127.0.0.1:7800` by default and generates a fresh token each
100
+ launch; open the printed `http://127.0.0.1:7800/?token=…` URL (the desktop shell
101
+ will inject this for you in a later release). Every API request is checked for
102
+ that token plus a same-origin guard, so a malicious web page cannot drive the
103
+ local engine. If the UI hasn't been built yet, the API still runs and `serve`
104
+ says so.
105
+
106
+ For UI development with hot reload, run the API and the Vite dev server side by
107
+ side (the dev server proxies `/v1` HTTP + WebSocket to the API):
108
+
109
+ ```bash
110
+ redrun serve --port 7800 # terminal 1 (API + token)
111
+ npm --prefix ui run dev # terminal 2 (proxies /v1 → 7800)
112
+ ```
113
+
114
+ UI tests: `npm --prefix ui test` (Vitest + React Testing Library).
115
+
116
+ ## Desktop app (macOS)
117
+
118
+ RedRun ships as a native macOS app (Tauri) that bundles the Python control plane
119
+ as a sidecar — no Python install required on the end-user machine. The Rust shell
120
+ generates a per-launch loopback token, starts the sidecar, polls it until ready,
121
+ and opens the console in the OS webview.
122
+
123
+ **Build prerequisites:** Rust (`rustup`), the Tauri CLI (`cargo install tauri-cli
124
+ --version "^2.0.0" --locked`), and PyInstaller in the project venv
125
+ (`.venv/bin/python -m pip install "pyinstaller>=6.0"`).
126
+
127
+ ```bash
128
+ # 1. Freeze the Python control plane into a Tauri resource (also builds the UI)
129
+ packaging/build-sidecar.sh
130
+ # 2. Run the desktop app in dev
131
+ cd desktop/src-tauri && cargo tauri dev
132
+ # 3. Produce a distributable .app / .dmg
133
+ cd desktop/src-tauri && cargo tauri build
134
+ ```
135
+
136
+ The shell is the security boundary: it binds the API to `127.0.0.1`, mints a
137
+ fresh 256-bit token each launch, and passes it to the sidecar via env — the token
138
+ is never on the command line. The shell supervises the sidecar (restarting it
139
+ with a fresh token+port if it crashes) and kills it on quit, so no engine process
140
+ is orphaned. A scan left running by a crash is marked `interrupted` on the next
141
+ start.
142
+
143
+ Rust shell tests: `cd desktop/src-tauri && cargo test`. The end-to-end
144
+ token-handoff is covered headlessly by `tests/test_desktop_handoff.py`
145
+ (`pytest -m network`). Windows/Linux bundles, code-signing, and auto-update are
146
+ not yet wired up.
147
+
148
+ ## Safety
149
+ - **Passive** scans are read-only and legal on any domain.
150
+ - **Active** scans send real attack payloads — only run them against systems you
151
+ own or are authorized to test. The `--authorized` flag is your rules-of-engagement.
152
+ - Active scanning is **detection-only**: it proves a vulnerability exists with
153
+ request/response evidence, then stops — it never exfiltrates data or causes damage.
154
+ - Outbound traffic is scope-enforced (egress guard by default; optional Docker
155
+ kernel sandbox).
156
+
157
+ ## Optional
158
+ - **AI summaries:** set `ANTHROPIC_API_KEY` and install the `[ai]` extra for an
159
+ executive summary. Without it, the CLI runs fully offline.
160
+ - **Nuclei:** if the `nuclei` binary is on PATH, CVE templates run automatically;
161
+ otherwise that step is skipped.
162
+
163
+ ## Build & release (maintainers)
164
+ ```bash
165
+ python -m build --wheel # → dist/redrun-<v>-py3-none-any.whl
166
+ ```
167
+ Issue a license (internal — needs the private signing key in `scripts/.keys/`,
168
+ which is gitignored and must never ship):
169
+ ```bash
170
+ python scripts/issue_license.py --email user@co.com --tier pro --days 365
171
+ ```
172
+
173
+ ## Architecture
174
+ `redrun/engine/` is a vendored copy of the scanning engine (recon, scope, egress
175
+ guard, exploit tools, reporter). The CLI orchestrates it locally. Vendored for
176
+ M1 to keep the tool standalone and zero-risk to the live web backend; a shared
177
+ `redrun_core` package can de-duplicate later. `redrun/licensing.py` holds the
178
+ embedded license-verification public key.
@@ -0,0 +1,153 @@
1
+ # RedRun CLI
2
+
3
+ **Proof-backed security testing you run yourself.** A standalone command-line tool
4
+ that runs RedRun's scanning engine **locally on your machine** — passive recon and
5
+ real, evidence-verified exploitation (SQLi, XSS, SSRF, IDOR, broken auth) — with
6
+ no cloud dependency.
7
+
8
+ This is the licensed-software M1 (see `../LICENSED-SOFTWARE-PLAN.md`): the engine
9
+ runs inside your own environment, so authorization is implicit (you point it at
10
+ your own assets) and deeper/internal testing is possible.
11
+
12
+ ## Install (customers)
13
+ ```bash
14
+ # Recommended: isolated install on PATH
15
+ pipx install ./dist/redrun-0.1.0-py3-none-any.whl
16
+ # or
17
+ pip install ./dist/redrun-0.1.0-py3-none-any.whl
18
+ ```
19
+ For AI executive summaries: `pipx install "redrun[ai]"` and set `ANTHROPIC_API_KEY`.
20
+
21
+ ## Licensing
22
+ Passive scans are **free**. Active exploitation requires a license.
23
+ ```bash
24
+ redrun license status # show current license
25
+ redrun license activate your.lic # install a license file (offline-verified)
26
+ ```
27
+ Licenses are Ed25519-signed and verified **offline** with an embedded public key —
28
+ no server call, air-gap friendly. Tampering invalidates the signature.
29
+
30
+ ## Usage
31
+ ```bash
32
+ # Passive scan — recon, headers, TLS, DNS, exposed paths, nuclei CVE templates
33
+ redrun scan example.com
34
+
35
+ # Active exploitation — requires explicit authorization
36
+ redrun scan staging.myapp.com --active --authorized
37
+
38
+ # Production-looking host needs an extra confirmation
39
+ redrun scan myapp.com --active --authorized --confirm-production
40
+
41
+ # Extra in-scope hosts, JSON export, kernel sandbox
42
+ redrun scan myapp.com --active --authorized --scope api.myapp.com --json out.json
43
+ redrun scan myapp.com --active --authorized --sandbox docker
44
+ ```
45
+
46
+ ### Options
47
+ | Flag | Meaning |
48
+ |---|---|
49
+ | `--active` | run real exploitation (not just passive observation) |
50
+ | `--authorized` | confirm you own / may test the target (**required for `--active`**) |
51
+ | `--confirm-production` | authorize an active scan against a production-looking host |
52
+ | `--scope a,b` | additional in-scope hosts |
53
+ | `--sandbox local\|docker\|auto` | containment for active scans (default `local` egress guard; `docker` = kernel iptables allowlist, needs a Docker host) |
54
+ | `--json FILE` | write full results to JSON |
55
+ | `--no-ai` | skip the AI executive summary |
56
+
57
+ ## Local console (web UI)
58
+
59
+ RedRun ships a local web console — a targets dashboard, per-target scan history
60
+ with live progress, an add-target flow, and license/settings — served by
61
+ `redrun serve` from a single loopback process. The UI is a Vite + React SPA in
62
+ `ui/`, built to `redrun/app/static/` and served alongside the token-guarded
63
+ `/v1` API on `127.0.0.1`.
64
+
65
+ ```bash
66
+ # one-time: install UI deps
67
+ npm --prefix ui install
68
+ # build the UI (outputs to redrun/app/static/, served by `redrun serve`)
69
+ npm --prefix ui run build
70
+ # run the console — prints a URL containing the per-launch token
71
+ redrun serve
72
+ ```
73
+
74
+ `redrun serve` binds `127.0.0.1:7800` by default and generates a fresh token each
75
+ launch; open the printed `http://127.0.0.1:7800/?token=…` URL (the desktop shell
76
+ will inject this for you in a later release). Every API request is checked for
77
+ that token plus a same-origin guard, so a malicious web page cannot drive the
78
+ local engine. If the UI hasn't been built yet, the API still runs and `serve`
79
+ says so.
80
+
81
+ For UI development with hot reload, run the API and the Vite dev server side by
82
+ side (the dev server proxies `/v1` HTTP + WebSocket to the API):
83
+
84
+ ```bash
85
+ redrun serve --port 7800 # terminal 1 (API + token)
86
+ npm --prefix ui run dev # terminal 2 (proxies /v1 → 7800)
87
+ ```
88
+
89
+ UI tests: `npm --prefix ui test` (Vitest + React Testing Library).
90
+
91
+ ## Desktop app (macOS)
92
+
93
+ RedRun ships as a native macOS app (Tauri) that bundles the Python control plane
94
+ as a sidecar — no Python install required on the end-user machine. The Rust shell
95
+ generates a per-launch loopback token, starts the sidecar, polls it until ready,
96
+ and opens the console in the OS webview.
97
+
98
+ **Build prerequisites:** Rust (`rustup`), the Tauri CLI (`cargo install tauri-cli
99
+ --version "^2.0.0" --locked`), and PyInstaller in the project venv
100
+ (`.venv/bin/python -m pip install "pyinstaller>=6.0"`).
101
+
102
+ ```bash
103
+ # 1. Freeze the Python control plane into a Tauri resource (also builds the UI)
104
+ packaging/build-sidecar.sh
105
+ # 2. Run the desktop app in dev
106
+ cd desktop/src-tauri && cargo tauri dev
107
+ # 3. Produce a distributable .app / .dmg
108
+ cd desktop/src-tauri && cargo tauri build
109
+ ```
110
+
111
+ The shell is the security boundary: it binds the API to `127.0.0.1`, mints a
112
+ fresh 256-bit token each launch, and passes it to the sidecar via env — the token
113
+ is never on the command line. The shell supervises the sidecar (restarting it
114
+ with a fresh token+port if it crashes) and kills it on quit, so no engine process
115
+ is orphaned. A scan left running by a crash is marked `interrupted` on the next
116
+ start.
117
+
118
+ Rust shell tests: `cd desktop/src-tauri && cargo test`. The end-to-end
119
+ token-handoff is covered headlessly by `tests/test_desktop_handoff.py`
120
+ (`pytest -m network`). Windows/Linux bundles, code-signing, and auto-update are
121
+ not yet wired up.
122
+
123
+ ## Safety
124
+ - **Passive** scans are read-only and legal on any domain.
125
+ - **Active** scans send real attack payloads — only run them against systems you
126
+ own or are authorized to test. The `--authorized` flag is your rules-of-engagement.
127
+ - Active scanning is **detection-only**: it proves a vulnerability exists with
128
+ request/response evidence, then stops — it never exfiltrates data or causes damage.
129
+ - Outbound traffic is scope-enforced (egress guard by default; optional Docker
130
+ kernel sandbox).
131
+
132
+ ## Optional
133
+ - **AI summaries:** set `ANTHROPIC_API_KEY` and install the `[ai]` extra for an
134
+ executive summary. Without it, the CLI runs fully offline.
135
+ - **Nuclei:** if the `nuclei` binary is on PATH, CVE templates run automatically;
136
+ otherwise that step is skipped.
137
+
138
+ ## Build & release (maintainers)
139
+ ```bash
140
+ python -m build --wheel # → dist/redrun-<v>-py3-none-any.whl
141
+ ```
142
+ Issue a license (internal — needs the private signing key in `scripts/.keys/`,
143
+ which is gitignored and must never ship):
144
+ ```bash
145
+ python scripts/issue_license.py --email user@co.com --tier pro --days 365
146
+ ```
147
+
148
+ ## Architecture
149
+ `redrun/engine/` is a vendored copy of the scanning engine (recon, scope, egress
150
+ guard, exploit tools, reporter). The CLI orchestrates it locally. Vendored for
151
+ M1 to keep the tool standalone and zero-risk to the live web backend; a shared
152
+ `redrun_core` package can de-duplicate later. `redrun/licensing.py` holds the
153
+ embedded license-verification public key.
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ # Distribution name on PyPI ("redrun" is taken). The CLI command stays `redrun`
7
+ # (see [project.scripts]) and the import package stays `redrun/`.
8
+ name = "redrun-scan"
9
+ version = "0.1.0"
10
+ description = "RedRun — continuous, proof-backed security testing you run yourself."
11
+ readme = "README.md"
12
+ requires-python = ">=3.10"
13
+ license = { text = "Proprietary" }
14
+ authors = [{ name = "RedRun" }]
15
+ dependencies = [
16
+ "pydantic>=2.10.0",
17
+ "httpx>=0.28.0",
18
+ "dnspython>=2.7.0",
19
+ "beautifulsoup4>=4.12.0",
20
+ "lxml>=5.3.0",
21
+ "cryptography>=42.0.0",
22
+ "fastapi>=0.115.0",
23
+ "uvicorn[standard]>=0.30.0",
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ # AI executive summaries / attack-chain narrative (set ANTHROPIC_API_KEY to use).
28
+ ai = ["anthropic>=0.40.0"]
29
+ tools = ["sslyze>=6.0.0"]
30
+ dev = ["pytest>=8.0.0", "pytest-asyncio>=0.24.0", "httpx>=0.28.0"]
31
+
32
+ [project.scripts]
33
+ redrun = "redrun.cli:main"
34
+
35
+ # Explicitly list packages so setuptools does NOT treat the hyphenated
36
+ # docker/egress-proxy data dir as a (invalid) namespace package.
37
+ [tool.setuptools]
38
+ packages = ["redrun", "redrun.app", "redrun.engine", "redrun.engine.tools"]
39
+
40
+ [tool.setuptools.package-data]
41
+ "redrun.engine" = ["docker/egress-proxy/*"]
File without changes
@@ -0,0 +1,5 @@
1
+ import sys
2
+ from redrun.cli import main
3
+
4
+ if __name__ == "__main__":
5
+ sys.exit(main())
@@ -0,0 +1 @@
1
+ """RedRun local control plane — the FastAPI app behind `redrun serve`."""
@@ -0,0 +1,50 @@
1
+ """Per-launch local API token + same-origin guard.
2
+
3
+ The control plane runs an exploitation engine, so the only caller allowed is the
4
+ app's own webview. We require a per-launch token header AND reject any cross-site
5
+ Origin (defeats a malicious page reaching 127.0.0.1 via DNS-rebinding/CSRF).
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import hmac
10
+ import secrets
11
+ from urllib.parse import urlparse
12
+
13
+ from fastapi import Header, HTTPException
14
+ from typing import Optional
15
+
16
+
17
+ def is_allowed_origin(origin: Optional[str]) -> bool:
18
+ """True if the Origin is same-origin/loopback or the Tauri shell, or absent
19
+ (non-browser client). A foreign site is rejected (anti DNS-rebind/CSRF).
20
+
21
+ Uses exact hostname matching (not prefix) so spoofed domains like
22
+ http://127.0.0.1.evil.com cannot bypass the guard.
23
+ """
24
+ if origin is None:
25
+ return True
26
+ parsed = urlparse(origin)
27
+ host = parsed.hostname # None for schemes like tauri:// without //host
28
+ return (
29
+ host in ("127.0.0.1", "localhost", "::1")
30
+ or (parsed.scheme == "tauri" and host in ("localhost", None))
31
+ )
32
+
33
+
34
+ class LocalAuth:
35
+ def __init__(self, token: str):
36
+ self.token = token
37
+
38
+ @staticmethod
39
+ def new_token() -> str:
40
+ return secrets.token_urlsafe(32)
41
+
42
+ async def require(
43
+ self,
44
+ x_redrun_token: Optional[str] = Header(default=None),
45
+ origin: Optional[str] = Header(default=None),
46
+ ) -> None:
47
+ if not is_allowed_origin(origin):
48
+ raise HTTPException(status_code=403, detail="Cross-origin blocked")
49
+ if not x_redrun_token or not hmac.compare_digest(x_redrun_token, self.token):
50
+ raise HTTPException(status_code=401, detail="Invalid local token")
@@ -0,0 +1,57 @@
1
+ """SQLite connection + schema for the local control plane."""
2
+ from __future__ import annotations
3
+
4
+ import sqlite3
5
+
6
+ _SCHEMA = """
7
+ CREATE TABLE IF NOT EXISTS targets (
8
+ id TEXT PRIMARY KEY,
9
+ label TEXT NOT NULL,
10
+ host_or_url TEXT NOT NULL,
11
+ scope TEXT NOT NULL DEFAULT '[]', -- JSON list of hosts/CIDRs
12
+ authorized INTEGER NOT NULL DEFAULT 0,
13
+ enabled_phases TEXT NOT NULL DEFAULT '[]', -- JSON list of phase names
14
+ tags TEXT NOT NULL DEFAULT '[]', -- JSON list
15
+ created_at TEXT NOT NULL
16
+ );
17
+ CREATE TABLE IF NOT EXISTS scans (
18
+ id TEXT PRIMARY KEY,
19
+ target_id TEXT NOT NULL REFERENCES targets(id) ON DELETE CASCADE,
20
+ mode TEXT NOT NULL,
21
+ status TEXT NOT NULL,
22
+ phase TEXT,
23
+ progress INTEGER NOT NULL DEFAULT 0,
24
+ started_at TEXT,
25
+ completed_at TEXT,
26
+ duration REAL,
27
+ report TEXT NOT NULL DEFAULT '{}',
28
+ source TEXT NOT NULL DEFAULT 'manual'
29
+ );
30
+ CREATE INDEX IF NOT EXISTS idx_scans_target ON scans(target_id);
31
+ CREATE TABLE IF NOT EXISTS settings (
32
+ key TEXT PRIMARY KEY,
33
+ value TEXT NOT NULL
34
+ );
35
+ """
36
+
37
+
38
+ def connect(path: str) -> sqlite3.Connection:
39
+ conn = sqlite3.connect(path, check_same_thread=False)
40
+ conn.row_factory = sqlite3.Row
41
+ conn.execute("PRAGMA foreign_keys = ON")
42
+ conn.execute("PRAGMA journal_mode = WAL")
43
+ return conn
44
+
45
+
46
+ def _migrate(conn: sqlite3.Connection) -> None:
47
+ """Idempotent column adds for DBs created before a column existed."""
48
+ cols = {r["name"] for r in conn.execute("PRAGMA table_info(scans)")}
49
+ if "source" not in cols:
50
+ conn.execute(
51
+ "ALTER TABLE scans ADD COLUMN source TEXT NOT NULL DEFAULT 'manual'")
52
+
53
+
54
+ def init_schema(conn: sqlite3.Connection) -> None:
55
+ conn.executescript(_SCHEMA)
56
+ _migrate(conn)
57
+ conn.commit()
@@ -0,0 +1,31 @@
1
+ """In-process async pub/sub. One queue per WebSocket subscriber, keyed by scan id.
2
+ A small per-scan backlog lets a client that connects mid-scan replay the events it
3
+ missed; the authoritative final state is always in GET /v1/scans/{id}."""
4
+ from __future__ import annotations
5
+
6
+ import asyncio
7
+ from collections import defaultdict, deque
8
+
9
+
10
+ class EventHub:
11
+ def __init__(self, backlog: int = 64):
12
+ self._subs: dict[str, list[asyncio.Queue]] = defaultdict(list)
13
+ self._backlog: dict[str, deque] = defaultdict(lambda: deque(maxlen=backlog))
14
+
15
+ def subscribe(self, scan_id: str) -> asyncio.Queue:
16
+ q: asyncio.Queue = asyncio.Queue()
17
+ for event in self._backlog.get(scan_id, ()): # replay what was missed
18
+ q.put_nowait(event)
19
+ self._subs[scan_id].append(q)
20
+ return q
21
+
22
+ def unsubscribe(self, scan_id: str, q: asyncio.Queue) -> None:
23
+ if q in self._subs.get(scan_id, []):
24
+ self._subs[scan_id].remove(q)
25
+ if not self._subs.get(scan_id):
26
+ self._subs.pop(scan_id, None)
27
+
28
+ async def publish(self, scan_id: str, event: dict) -> None:
29
+ self._backlog[scan_id].append(event)
30
+ for q in list(self._subs.get(scan_id, [])):
31
+ await q.put(event)
@@ -0,0 +1,60 @@
1
+ """Local control-plane domain types. Finding is reused from the engine schema."""
2
+ from __future__ import annotations
3
+
4
+ from datetime import datetime
5
+ from enum import Enum
6
+ from typing import Optional
7
+
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ class Phase(str, Enum):
12
+ RECON = "recon"
13
+ ENUMERATION = "enumeration"
14
+ VULN_ASSESSMENT = "vuln_assessment"
15
+ EXPLOITATION = "exploitation"
16
+ EXPOSURE = "exposure"
17
+
18
+
19
+ # Phases an ACTIVE scan may run; passive excludes EXPLOITATION.
20
+ PASSIVE_PHASES = [Phase.RECON, Phase.ENUMERATION, Phase.VULN_ASSESSMENT, Phase.EXPOSURE]
21
+ ACTIVE_PHASES = [Phase.RECON, Phase.ENUMERATION, Phase.VULN_ASSESSMENT,
22
+ Phase.EXPLOITATION, Phase.EXPOSURE]
23
+
24
+
25
+ class ScanStatus(str, Enum):
26
+ PENDING = "pending"
27
+ RUNNING = "running"
28
+ COMPLETED = "completed"
29
+ FAILED = "failed"
30
+ INTERRUPTED = "interrupted"
31
+
32
+
33
+ class Target(BaseModel):
34
+ id: str
35
+ label: str
36
+ host_or_url: str
37
+ scope: list[str] = Field(default_factory=list)
38
+ authorized: bool = False
39
+ enabled_phases: list[str] = Field(default_factory=list)
40
+ tags: list[str] = Field(default_factory=list)
41
+ created_at: datetime = Field(default_factory=datetime.utcnow)
42
+ # rolled-up display state (computed from scans, not stored on the row)
43
+ status: str = "idle"
44
+ last_scan_at: Optional[datetime] = None
45
+ latest_risk_score: Optional[int] = None
46
+ open_findings: dict = Field(default_factory=dict)
47
+
48
+
49
+ class ScanRecord(BaseModel):
50
+ id: str
51
+ target_id: str
52
+ mode: str = "passive"
53
+ status: ScanStatus = ScanStatus.PENDING
54
+ phase: Optional[str] = None
55
+ progress: int = 0
56
+ started_at: Optional[datetime] = None
57
+ completed_at: Optional[datetime] = None
58
+ duration: Optional[float] = None
59
+ report: dict = Field(default_factory=dict)
60
+ source: str = "manual"