@ksl101/vulnreaper 0.1.0-beta.0 → 0.1.0-beta.2
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.
- package/README.md +221 -199
- package/package.json +2 -2
- package/vendor/vulnreaper/__init__.py +23 -23
- package/vendor/vulnreaper/cli/__init__.py +1 -1
- package/vendor/vulnreaper/cli/account.py +140 -0
- package/vendor/vulnreaper/cli/app.py +293 -111
- package/vendor/vulnreaper/cli/autocomplete.py +50 -0
- package/vendor/vulnreaper/cli/commands.py +452 -0
- package/vendor/vulnreaper/cli/components/__init__.py +11 -0
- package/vendor/vulnreaper/cli/components/banner.py +82 -0
- package/vendor/vulnreaper/cli/components/error.py +29 -0
- package/vendor/vulnreaper/cli/components/finding.py +106 -0
- package/vendor/vulnreaper/cli/components/palette.py +78 -0
- package/vendor/vulnreaper/cli/components/spinner.py +96 -0
- package/vendor/vulnreaper/cli/components/status.py +55 -0
- package/vendor/vulnreaper/cli/dashboard.py +17 -14
- package/vendor/vulnreaper/cli/intent.py +84 -0
- package/vendor/vulnreaper/cli/language.py +160 -0
- package/vendor/vulnreaper/cli/prompt.py +354 -0
- package/vendor/vulnreaper/cli/repl.py +344 -0
- package/vendor/vulnreaper/cli/terminal.py +107 -0
- package/vendor/vulnreaper/cli/theme.py +170 -0
- package/vendor/vulnreaper/config.py +7 -1
- package/vendor/vulnreaper/i18n/__init__.py +45 -0
- package/vendor/vulnreaper/i18n/locales/de.json +406 -0
- package/vendor/vulnreaper/i18n/locales/en.json +406 -0
- package/vendor/vulnreaper/i18n/locales/es.json +406 -0
- package/vendor/vulnreaper/i18n/locales/fr.json +406 -0
- package/vendor/vulnreaper/i18n/locales/it.json +406 -0
- package/vendor/vulnreaper/i18n/locales/pt-BR.json +406 -0
- package/vendor/vulnreaper/i18n/manager.py +464 -0
- package/vendor/vulnreaper/licensing.py +38 -0
- package/vendor/vulnreaper/selfupdate.py +442 -0
- package/vendor/vulnreaper/targets.py +308 -0
- package/vendor/vulnreaper/tools.py +54 -0
- package/vendor/vulnreaper/ui.py +12 -4
- package/vendor/vulnreaper/cli/menu.py +0 -117
package/README.md
CHANGED
|
@@ -1,199 +1,221 @@
|
|
|
1
|
-
# VulnReaper —
|
|
2
|
-
|
|
3
|
-
**VulnReaper**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- **
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
vulnreaper
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
vulnreaper --
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
---
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
---
|
|
140
|
-
|
|
141
|
-
##
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
1
|
+
# VulnReaper — Security platform for research and bug-bounty
|
|
2
|
+
|
|
3
|
+
**VulnReaper** is an **offline-first** security-analysis platform for bug-bounty
|
|
4
|
+
and security research. It is built around a **target-agnostic core** (projects,
|
|
5
|
+
runs, findings, rule engine, JSON/Markdown/HTML reports, licence/account) and
|
|
6
|
+
around **per-target modules**:
|
|
7
|
+
the **Android** module (APK/APKS/XAPK, Unity and IL2CPP) is available today,
|
|
8
|
+
while web, Windows and game-engine project targets are already detected and
|
|
9
|
+
routed, with their engines still to be integrated.
|
|
10
|
+
|
|
11
|
+
It runs on **pure Python** (standard library only), with no runtime dependency,
|
|
12
|
+
and is **Windows-first**, while remaining POSIX-compatible.
|
|
13
|
+
|
|
14
|
+
On an Android target, it turns an APK into an actionable report: attack surface
|
|
15
|
+
(manifest), strings and secrets, endpoints/hosts, native libraries and IL2CPP
|
|
16
|
+
metadata — all correlated by a JSON rule engine.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Highlights
|
|
21
|
+
|
|
22
|
+
- **No runtime dependency**: standard library only.
|
|
23
|
+
- **Never fabricate results**: a missing external tool is marked `missing` and
|
|
24
|
+
its step is `skipped`, never simulated.
|
|
25
|
+
- **Windows-first**: UTF-8 encoding, ANSI (VT) colours, Windows-aware path and
|
|
26
|
+
subprocess handling.
|
|
27
|
+
- **Native analysis without a decompiler**: built-in DEX, AXML (compiled
|
|
28
|
+
manifest), ELF and `global-metadata.dat` (IL2CPP v24–v31) parsers.
|
|
29
|
+
- **Reports** in JSON, Markdown and HTML, ready to attach to a bug-bounty report.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Core and per-target modules
|
|
34
|
+
|
|
35
|
+
| Element | Scope |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| **Core** (target-agnostic) | Workspaces/projects, runs, findings, JSON rule engine, JSON/Markdown/HTML reports, licence/account management — **always available**. |
|
|
38
|
+
| **Android module** | APK/APKS/XAPK: manifest, DEX, native, Unity/IL2CPP, assets, endpoints — **available**. |
|
|
39
|
+
| **Web module** | http(s) URL: detected and routed; engine coming. |
|
|
40
|
+
| **Windows module** | EXE/DLL/SYS/MSI: detected and routed; engine coming. |
|
|
41
|
+
| **Unity / Unreal module** | Project folders: detected and routed; engines coming. |
|
|
42
|
+
|
|
43
|
+
A target whose module is not yet integrated is **honestly refused**
|
|
44
|
+
("not supported yet" message): VulnReaper never simulates a scan.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
### Public beta (npm)
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
npm install -g @ksl101/vulnreaper@beta
|
|
54
|
+
vr version
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Requirements: Node.js ≥ 18 and Python ≥ 3.10 (the package embeds the complete
|
|
58
|
+
Python core, executed by the `vr` / `vulnreaper` launcher).
|
|
59
|
+
|
|
60
|
+
VulnReaper is nominative-licence software: activate your key with
|
|
61
|
+
`vr login --key VR-...`, then check `vr whoami`. The `scan` and
|
|
62
|
+
`report` commands require a permission (`scan.run`, `report.export`); the
|
|
63
|
+
`recon.run` and `audit.run` permissions are reserved for the future `recon`/`audit`
|
|
64
|
+
commands.
|
|
65
|
+
See **[docs/BETA_TESTERS.md](docs/BETA_TESTERS.md)** for the full tester guide.
|
|
66
|
+
|
|
67
|
+
### From source
|
|
68
|
+
|
|
69
|
+
No installation is required to use VulnReaper from source.
|
|
70
|
+
|
|
71
|
+
```bat
|
|
72
|
+
:: Windows (command prompt)
|
|
73
|
+
vulnreaper.bat doctor
|
|
74
|
+
vulnreaper.bat scan C:\path\to\game.apk --profile apk-static
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
# Git Bash / Linux / macOS
|
|
79
|
+
./vulnreaper.sh doctor
|
|
80
|
+
./vulnreaper.sh scan /path/to/game.apk --profile apk-static
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Or directly through Python (at the repository root):
|
|
84
|
+
|
|
85
|
+
```bat
|
|
86
|
+
python -m vulnreaper doctor
|
|
87
|
+
python -m vulnreaper scan C:\path\to\game.apk
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Optional installation as a global command:
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
pip install -e .
|
|
94
|
+
vulnreaper --version
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Commands
|
|
100
|
+
|
|
101
|
+
| Command | Description |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| `vulnreaper doctor` | Diagnose the environment and external tools |
|
|
104
|
+
| `vulnreaper version` | Print the version |
|
|
105
|
+
| `vulnreaper profiles` | List scan profiles |
|
|
106
|
+
| `vulnreaper rules [--json]` | List loaded rules |
|
|
107
|
+
| `vulnreaper tools [--missing\|--available] [--json]` | Inspect external tools |
|
|
108
|
+
| `vulnreaper project create\|list\|show\|delete <name>` | Manage projects/workspaces |
|
|
109
|
+
| `vulnreaper scan <target> [--profile P] [--project N] [--json]` | Analyze a security target (APK, URL, EXE, project) |
|
|
110
|
+
| `vulnreaper runs [--project N]` | List runs |
|
|
111
|
+
| `vulnreaper findings [--run ID] [--severity S]` | Show findings |
|
|
112
|
+
| `vulnreaper report --run ID` | Path of the generated reports |
|
|
113
|
+
| `vulnreaper config show\|init` | Show / initialise the configuration |
|
|
114
|
+
| `vulnreaper login --key <VR-…> [--base-url URL]` | Activate a licence and open a session |
|
|
115
|
+
| `vulnreaper logout` | Close the local session |
|
|
116
|
+
| `vulnreaper whoami [--json]` | Show licence, role, status, permissions |
|
|
117
|
+
| `vulnreaper admin license\|device\|audit …` | Licence/device administration (`admin` role) |
|
|
118
|
+
| `vulnreaper self-update [--check] [--yes] [--force] [--json]` | Update VulnReaper itself (npm `@beta`) |
|
|
119
|
+
| `vulnreaper ui` | Interactive shell (conversational REPL) |
|
|
120
|
+
|
|
121
|
+
With no command, `vulnreaper` opens the interactive shell (`ui`).
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Scan profiles
|
|
126
|
+
|
|
127
|
+
| Profile | Usage |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `apk-static` *(default)* | Full static analysis: import, extraction, manifest, dex, native, Unity, IL2CPP, assets, endpoints, scanners, report |
|
|
130
|
+
| `quick` | Fast version without extraction or decompilation |
|
|
131
|
+
| `android-full` | Adds apktool, Il2CppDumper and jadx when present |
|
|
132
|
+
| `il2cpp` | Unity/IL2CPP-oriented |
|
|
133
|
+
| `recon` | Endpoint/host reconnaissance only |
|
|
134
|
+
| `device` | Enumerate adb devices |
|
|
135
|
+
| `report-only` | Regenerate a report from existing data |
|
|
136
|
+
|
|
137
|
+
List profiles: `vulnreaper profiles`.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## External tools (optional)
|
|
142
|
+
|
|
143
|
+
VulnReaper works without them; when present, it takes advantage of them:
|
|
144
|
+
|
|
145
|
+
| Tool | Role |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| `adb` | Device enumeration / device interaction |
|
|
148
|
+
| `apktool` | Resource decoding |
|
|
149
|
+
| `jadx` | Java decompilation |
|
|
150
|
+
| `apksigner` | Signature verification |
|
|
151
|
+
| `aapt` | APK inspection |
|
|
152
|
+
| `frida` | Dynamic instrumentation |
|
|
153
|
+
| `il2cppdumper` | IL2CPP dump (reconstructed `.cs`) |
|
|
154
|
+
| `cpp2il` | Alternative IL2CPP reconstruction |
|
|
155
|
+
| `ghidra` | Advanced native analysis |
|
|
156
|
+
|
|
157
|
+
Status: `vulnreaper tools`. Explicit paths and search directories are
|
|
158
|
+
defined in the configuration (`vulnreaper config show`).
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Repository layout
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
vulnreaper/ main package
|
|
166
|
+
cli/ entry point, interactive shell (REPL), theme + components
|
|
167
|
+
formats/ strings, AXML, DEX, ELF, IL2CPP metadata parsers
|
|
168
|
+
android/ APK import, manifest model, dex/native analysis, adb
|
|
169
|
+
unity/ il2cpp/ engine detection + dump adapters
|
|
170
|
+
web/ endpoint discovery and classification
|
|
171
|
+
scanners/ rule engine + scanners (secrets, crypto, network, native)
|
|
172
|
+
findings/ Finding model + JSON/MD/HTML reports
|
|
173
|
+
rules/ JSON rule packs
|
|
174
|
+
config.py workspace.py database.py project.py pipeline.py engine.py tools.py
|
|
175
|
+
tests/ pytest suite + synthetic fixtures + end-to-end smoke
|
|
176
|
+
docs/ architecture, rules, usage
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Tests
|
|
182
|
+
|
|
183
|
+
```bat
|
|
184
|
+
python -m pytest tests/ -q :: Python core + UI
|
|
185
|
+
python tests/smoke.py :: end-to-end scan of a synthetic APK
|
|
186
|
+
cd server && npm test :: licence API (in-memory store)
|
|
187
|
+
cd server && VULNREAPER_E2E=1 npm test :: + CLI ↔ API E2E
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The smoke builds a structurally valid APK (compiled AXML manifest, DEX,
|
|
191
|
+
native `.so`, `global-metadata.dat`) in a temporary folder, runs the `quick`
|
|
192
|
+
profile and checks that the run completes, writes the 3 report formats and
|
|
193
|
+
produces findings and endpoints.
|
|
194
|
+
|
|
195
|
+
The cross-stack E2E test actually drives the CLI against the licence API
|
|
196
|
+
(activation, `whoami`, gating, revocation). Coverage details and
|
|
197
|
+
prerequisites: **[docs/TESTING.md](docs/TESTING.md)**.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Documentation
|
|
202
|
+
|
|
203
|
+
- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — design and data flow
|
|
204
|
+
- [`docs/USAGE.md`](docs/USAGE.md) — getting started and bug-bounty workflow
|
|
205
|
+
- [`docs/UI.md`](docs/UI.md) — CLI/REPL interface (theme, components, shortcuts)
|
|
206
|
+
- [`docs/RULES.md`](docs/RULES.md) — rule packs and writing rules
|
|
207
|
+
- [`docs/BETA_TESTERS.md`](docs/BETA_TESTERS.md) — beta tester guide (npm, licence, gating)
|
|
208
|
+
- [`docs/TESTING.md`](docs/TESTING.md) — test strategy and coverage
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Licence and usage
|
|
213
|
+
|
|
214
|
+
> Distributed under a proprietary nominative licence. Using the software
|
|
215
|
+
> constitutes acceptance of the [end-user licence agreement (EULA)](LICENSE).
|
|
216
|
+
> The activation guide and the details of the permissions are in
|
|
217
|
+
> [docs/BETA_TESTERS.md](docs/BETA_TESTERS.md).
|
|
218
|
+
|
|
219
|
+
Tool intended for authorised professionals. Only scan applications you are
|
|
220
|
+
authorised to test (your own targets, bug-bounty programs, or explicitly
|
|
221
|
+
authorised targets).
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ksl101/vulnreaper",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
4
|
-
"description": "VulnReaper - authorised security
|
|
3
|
+
"version": "0.1.0-beta.2",
|
|
4
|
+
"description": "VulnReaper - authorised security-auditing & bug-bounty CLI with a target-agnostic core and per-target modules (Android today; web, Windows and game-engine projects detected next)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"security",
|
|
7
7
|
"bug-bounty",
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
"""VulnReaper — Android game security research & bug-bounty automation platform.
|
|
2
|
-
|
|
3
|
-
A modular, offline-first toolkit for analysing Android/Unity/IL2CPP game
|
|
4
|
-
packages: APK/DEX/native/IL2CPP parsing, secret and misconfiguration scanning,
|
|
5
|
-
endpoint discovery, and JSON/Markdown/HTML reporting.
|
|
6
|
-
|
|
7
|
-
Design invariants
|
|
8
|
-
-----------------
|
|
9
|
-
* Standard library only (no third-party runtime dependencies).
|
|
10
|
-
* Never fabricate results: a missing external tool is reported as ``missing``
|
|
11
|
-
and its pipeline stage is ``skipped`` — never simulated.
|
|
12
|
-
* Windows-first, POSIX-compatible.
|
|
13
|
-
"""
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
__all__ = ["__version__", "PRODUCT"]
|
|
17
|
-
|
|
18
|
-
__version__ = "0.1.0-beta.
|
|
19
|
-
PRODUCT = "VulnReaper"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def version_string() -> str:
|
|
23
|
-
return f"{PRODUCT} {__version__} — Security Research Platform"
|
|
1
|
+
"""VulnReaper — Android game security research & bug-bounty automation platform.
|
|
2
|
+
|
|
3
|
+
A modular, offline-first toolkit for analysing Android/Unity/IL2CPP game
|
|
4
|
+
packages: APK/DEX/native/IL2CPP parsing, secret and misconfiguration scanning,
|
|
5
|
+
endpoint discovery, and JSON/Markdown/HTML reporting.
|
|
6
|
+
|
|
7
|
+
Design invariants
|
|
8
|
+
-----------------
|
|
9
|
+
* Standard library only (no third-party runtime dependencies).
|
|
10
|
+
* Never fabricate results: a missing external tool is reported as ``missing``
|
|
11
|
+
and its pipeline stage is ``skipped`` — never simulated.
|
|
12
|
+
* Windows-first, POSIX-compatible.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
__all__ = ["__version__", "PRODUCT"]
|
|
17
|
+
|
|
18
|
+
__version__ = "0.1.0-beta.2"
|
|
19
|
+
PRODUCT = "VulnReaper"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def version_string() -> str:
|
|
23
|
+
return f"{PRODUCT} {__version__} — Security Research Platform"
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Account helpers shared by the CLI entrypoint and the REPL.
|
|
2
|
+
|
|
3
|
+
A VulnReaper *account* is the username + subscription tag + free-form profile
|
|
4
|
+
blob carried on the license view returned by the licensing API. These helpers
|
|
5
|
+
keep the interpretation of that view in one place and tolerate both the
|
|
6
|
+
snake_case the API emits and the camelCase the Node store uses internally.
|
|
7
|
+
|
|
8
|
+
The username is deliberately **not** a credential: authentication stays with
|
|
9
|
+
the license key / server session. The username only identifies the tester,
|
|
10
|
+
personalises the interface and enables administration/search.
|
|
11
|
+
|
|
12
|
+
Human-facing expiry wording is produced through the i18n catalog so it follows
|
|
13
|
+
the active interface language; the underlying arithmetic (seconds until
|
|
14
|
+
``expiresAt``) is language-independent.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
|
|
21
|
+
from ..i18n import t
|
|
22
|
+
|
|
23
|
+
# Must stay in sync with ``USERNAME_RE`` in the licensing server.
|
|
24
|
+
USERNAME_RE = re.compile(r"^[A-Za-z0-9_-]{3,32}$")
|
|
25
|
+
# Static English fallback (kept for callers importing the constant). Prefer
|
|
26
|
+
# :func:`username_rule` so the hint tracks the active language.
|
|
27
|
+
USERNAME_RULE = "3-32 characters (letters, digits, underscore or hyphen)"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def username_rule() -> str:
|
|
31
|
+
"""The username policy hint, localised."""
|
|
32
|
+
return t("account.username_rule", default=USERNAME_RULE)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def username(lic: dict | None) -> str:
|
|
36
|
+
"""The account username, or '' when it has not been chosen yet."""
|
|
37
|
+
return str((lic or {}).get("username") or "").strip()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def tag(lic: dict | None) -> str:
|
|
41
|
+
"""The subscription tag, upper-cased, defaulting to BETA."""
|
|
42
|
+
raw = (lic or {}).get("subscriptionTag") or (lic or {}).get("subscription_tag") or "BETA"
|
|
43
|
+
return str(raw).upper()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def expires_at(lic: dict | None):
|
|
47
|
+
return (lic or {}).get("expiresAt") or (lic or {}).get("expires_at")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _expiry_datetime(lic: dict | None) -> datetime | None:
|
|
51
|
+
"""Parse ``expiresAt`` into an aware UTC datetime, or None when absent."""
|
|
52
|
+
value = expires_at(lic)
|
|
53
|
+
if not value:
|
|
54
|
+
return None
|
|
55
|
+
try:
|
|
56
|
+
when = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
|
|
57
|
+
except ValueError:
|
|
58
|
+
return None
|
|
59
|
+
if when.tzinfo is None:
|
|
60
|
+
when = when.replace(tzinfo=timezone.utc)
|
|
61
|
+
return when
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _seconds_left(lic: dict | None, now: datetime | None = None) -> float | None:
|
|
65
|
+
when = _expiry_datetime(lic)
|
|
66
|
+
if when is None:
|
|
67
|
+
return None
|
|
68
|
+
now = now or datetime.now(timezone.utc)
|
|
69
|
+
return (when - now).total_seconds()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def days_left(lic: dict | None) -> int | None:
|
|
73
|
+
"""Whole days until expiry, or None when the license never expires."""
|
|
74
|
+
seconds = _seconds_left(lic)
|
|
75
|
+
if seconds is None:
|
|
76
|
+
return None
|
|
77
|
+
return max(0, int(seconds // 86400))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def expiry_phrase(lic: dict | None, now: datetime | None = None) -> str:
|
|
81
|
+
"""The value form for tables/cards, without the verb.
|
|
82
|
+
|
|
83
|
+
Examples: ``never``, ``expired``, ``in 42 minutes``, ``in 18 hours``,
|
|
84
|
+
``in 27 days``. Everything is derived from ``expiresAt`` — nothing
|
|
85
|
+
time-relative is ever persisted.
|
|
86
|
+
"""
|
|
87
|
+
seconds = _seconds_left(lic, now)
|
|
88
|
+
if seconds is None:
|
|
89
|
+
return t("expiry.never")
|
|
90
|
+
if seconds <= 0:
|
|
91
|
+
return t("expiry.expired")
|
|
92
|
+
if seconds < 60:
|
|
93
|
+
return t("expiry.less_than_minute")
|
|
94
|
+
if seconds < 3600:
|
|
95
|
+
return t("expiry.minutes", count=int(seconds // 60))
|
|
96
|
+
if seconds < 86400:
|
|
97
|
+
return t("expiry.hours", count=int(seconds // 3600))
|
|
98
|
+
return t("expiry.days", count=int(seconds // 86400))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def expiry_text(lic: dict | None, now: datetime | None = None) -> str:
|
|
102
|
+
"""A full, human sentence describing the remaining lifetime.
|
|
103
|
+
|
|
104
|
+
Examples: ``never expires``, ``expired``, ``expires in 42 minutes``,
|
|
105
|
+
``expires in 18 hours``, ``expires in 27 days``.
|
|
106
|
+
"""
|
|
107
|
+
seconds = _seconds_left(lic, now)
|
|
108
|
+
if seconds is None:
|
|
109
|
+
return t("expiry.text_never")
|
|
110
|
+
if seconds <= 0:
|
|
111
|
+
return t("expiry.text_expired")
|
|
112
|
+
return t("expiry.text", phrase=expiry_phrase(lic, now))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def expiry_short(lic: dict | None, now: datetime | None = None) -> str:
|
|
116
|
+
"""A compact expiry label for tables: ``27d`` / ``18h`` / ``42m`` /
|
|
117
|
+
``never`` / ``expired``."""
|
|
118
|
+
seconds = _seconds_left(lic, now)
|
|
119
|
+
if seconds is None:
|
|
120
|
+
return t("expiry.short_never")
|
|
121
|
+
if seconds <= 0:
|
|
122
|
+
return t("expiry.short_expired")
|
|
123
|
+
if seconds < 3600:
|
|
124
|
+
return t("expiry.short_minutes", count=int(seconds // 60))
|
|
125
|
+
if seconds < 86400:
|
|
126
|
+
return t("expiry.short_hours", count=int(seconds // 3600))
|
|
127
|
+
return t("expiry.short_days", count=int(seconds // 86400))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def devices_label(lic: dict | None, used: int | None = None) -> str:
|
|
131
|
+
"""``used/max`` device usage, tolerating snake_case and camelCase."""
|
|
132
|
+
max_devices = (lic or {}).get("maxDevices")
|
|
133
|
+
if max_devices is None:
|
|
134
|
+
max_devices = (lic or {}).get("max_devices")
|
|
135
|
+
max_devices = max_devices if max_devices is not None else "?"
|
|
136
|
+
return f"{used if used is not None else '?'}/{max_devices}"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def valid_username(name: str) -> bool:
|
|
140
|
+
return bool(USERNAME_RE.match((name or "").strip()))
|