narvy-cli 1.0.3__tar.gz → 1.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 (106) hide show
  1. narvy_cli-1.1.0/CHANGELOG.md +88 -0
  2. {narvy_cli-1.0.3/narvy_cli.egg-info → narvy_cli-1.1.0}/PKG-INFO +84 -11
  3. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/README.md +83 -10
  4. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/__init__.py +1 -1
  5. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/android/source_analyzer.py +4 -10
  6. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/android/split_bundle.py +1 -2
  7. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/android_rule_context.py +1 -2
  8. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/apk_memory_preflight.py +1 -2
  9. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/ci_templates/bitbucket.yml +1 -1
  10. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/ci_templates/gitlab.yml +1 -1
  11. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/cloud/aws_scan.py +3 -6
  12. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/comment_filter.py +1 -3
  13. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/crypto_taint_lite.py +1 -4
  14. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/doctor.py +8 -8
  15. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/host/audit.py +4 -2
  16. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/host/host_knowledge.py +2 -6
  17. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/host/lynis_bootstrap.py +1 -3
  18. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/host/lynis_parser.py +1 -4
  19. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/host/narvy_checks.py +1 -3
  20. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/host/ssh_exec.py +1 -3
  21. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/ios/binary_analyzer.py +17 -26
  22. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/ios/plist_checks.py +1 -2
  23. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/ios/source_analyzer.py +5 -4
  24. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/ios/third_party_filter.py +1 -2
  25. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/ios/trust_all_context.py +1 -3
  26. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/main.py +647 -224
  27. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/native_hardening.py +4 -6
  28. narvy_cli-1.1.0/narvy/proc.py +92 -0
  29. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/web/go.yml +0 -23
  30. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/web/java.yml +0 -91
  31. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/web/javascript.yml +3 -86
  32. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/web/php.yml +234 -212
  33. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/web/python.yml +340 -1413
  34. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/web/ruby.yml +41 -0
  35. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/web/rust.yml +21 -17
  36. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/web/secrets.yml +4 -4
  37. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/web/secrets_supplement.yml +9 -6
  38. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/sca/android_deps.py +2 -5
  39. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/sca/ios_deps.py +1 -2
  40. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/sca/osv_client.py +1 -2
  41. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/sca/web_deps.py +8 -14
  42. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/scope_config.py +1 -4
  43. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/semgrep_engine.py +70 -6
  44. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/stack_protector_evidence.py +1 -4
  45. narvy_cli-1.1.0/narvy/telemetry.py +270 -0
  46. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/third_party_filter.py +1 -2
  47. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/weak_prng_context.py +1 -2
  48. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/web/scan_blocklist.py +1 -4
  49. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/web/scanner.py +12 -16
  50. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/web/source_analyzer.py +326 -89
  51. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/web/ssrf_guard.py +5 -10
  52. {narvy_cli-1.0.3 → narvy_cli-1.1.0/narvy_cli.egg-info}/PKG-INFO +84 -11
  53. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy_cli.egg-info/SOURCES.txt +2 -14
  54. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/pyproject.toml +4 -6
  55. narvy_cli-1.1.0/setup.py +4 -0
  56. narvy_cli-1.0.3/CHANGELOG.md +0 -85
  57. narvy_cli-1.0.3/narvy/rules/web/local/csharp_narvy/config.yml +0 -61
  58. narvy_cli-1.0.3/narvy/rules/web/local/csharp_narvy/crypto.yml +0 -64
  59. narvy_cli-1.0.3/narvy/rules/web/local/csharp_narvy/deserialization.yml +0 -59
  60. narvy_cli-1.0.3/narvy/rules/web/local/csharp_narvy/injection.yml +0 -122
  61. narvy_cli-1.0.3/narvy/rules/web/local/csharp_narvy/xxe.yml +0 -48
  62. narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/auth_jwt.yml +0 -134
  63. narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/deserialization.yml +0 -108
  64. narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/mybatis.yml +0 -39
  65. narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/snakeyaml.yml +0 -34
  66. narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/spring_authz.yml +0 -32
  67. narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/spring_config.yml +0 -67
  68. narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/spring_hardening.yml +0 -291
  69. narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/sqli.yml +0 -235
  70. narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/xxe.yml +0 -212
  71. narvy_cli-1.0.3/setup.py +0 -7
  72. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/LICENSE +0 -0
  73. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/MANIFEST.in +0 -0
  74. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/SECURITY.md +0 -0
  75. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/android/__init__.py +0 -0
  76. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/auth.py +0 -0
  77. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/ci_templates/github.yml +0 -0
  78. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/cloud/__init__.py +0 -0
  79. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/decompiler.py +0 -0
  80. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/host/__init__.py +0 -0
  81. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/host/report.py +0 -0
  82. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/ios/__init__.py +0 -0
  83. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/reporter.py +0 -0
  84. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rule_engine.py +0 -0
  85. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/android/config.yml +0 -0
  86. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/android/crypto.yml +0 -0
  87. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/android/secrets.yml +0 -0
  88. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/android/storage.yml +0 -0
  89. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/android/webview.yml +0 -0
  90. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/android.yml +0 -0
  91. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/ios/objc/crypto.yml +0 -0
  92. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/ios/objc/network.yml +0 -0
  93. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/ios/objc/secrets.yml +0 -0
  94. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/ios/objc/storage.yml +0 -0
  95. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/ios/objc/webview.yml +0 -0
  96. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/ios_swift.yml +0 -0
  97. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/rules/web/kotlin.yml +0 -0
  98. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/sca/__init__.py +0 -0
  99. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/uploader.py +0 -0
  100. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/web/__init__.py +0 -0
  101. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy/web/nuclei_binary.py +0 -0
  102. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy_cli.egg-info/dependency_links.txt +0 -0
  103. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy_cli.egg-info/entry_points.txt +0 -0
  104. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy_cli.egg-info/requires.txt +0 -0
  105. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/narvy_cli.egg-info/top_level.txt +0 -0
  106. {narvy_cli-1.0.3 → narvy_cli-1.1.0}/setup.cfg +0 -0
@@ -0,0 +1,88 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.1.0]
9
+
10
+ First release of the community rule set. The 1.0.x preview releases were
11
+ withdrawn; 1.1.0 replaces them.
12
+
13
+ ### Added
14
+
15
+ - PHP: reflected XSS where request input is concatenated or interpolated into
16
+ an HTML string, and an `include`/`require` of a variable that is set in
17
+ another file (reported as HIGH, to review).
18
+ - Python and Ruby rules: tainted module import, deprecated `ssl.wrap_socket`,
19
+ response header CRLF injection, SSRF from a request-controlled host.
20
+ - A web repository that also holds an iOS app in a subdirectory (an Xcode
21
+ project, workspace or `Package.swift` next to a server directory) is now
22
+ scanned on both surfaces, and the report lists them. Sample, test and
23
+ vendored projects are skipped.
24
+ - `--upload` prints a direct link to the scan in the dashboard.
25
+ - Outside a terminal (CI logs), a "still running" line every 30 seconds
26
+ (`NARVY_PROGRESS_INTERVAL`, 0 = off).
27
+ - Anonymous usage telemetry, listed field by field in the README, with a
28
+ notice on first run. Off with `narvy telemetry off`, `NARVY_TELEMETRY=0` or
29
+ `DO_NOT_TRACK=1`.
30
+
31
+ ### Changed
32
+
33
+ - Framework-specific rules and the C#/.NET code rules are no longer in the
34
+ CLI; they run on Narvy paid plans. The CLI states in one line when a scanned
35
+ project uses one of those frameworks. C#/.NET projects get secrets and NuGet
36
+ dependency checks.
37
+ - Exit codes are distinct and listed in `--help`: 0 completed, 1 `--fail-on`
38
+ threshold reached, 2 `--fail-on` could not certify the result (part of the
39
+ scan did not run), 3 target or arguments not usable, 4 scan, login, upload
40
+ or write failure. Exit 1 on `--fail-on` is unchanged.
41
+ - The structural analysis uses at most 4 workers and a memory ceiling, and
42
+ trees above 20,000 files are scanned in directory batches. On the Linux
43
+ kernel tree, peak memory went from 13 GB to 2.2 GB with the same findings
44
+ (the scan takes longer). `NARVY_SEMGREP_JOBS`,
45
+ `NARVY_SEMGREP_MAX_MEMORY_MB`, `NARVY_SEMGREP_BATCH_FILES` override.
46
+ - Help for `--upload` and `--fail-on` rewritten; the `.narvy-scope.yml` tip
47
+ links to the public documentation.
48
+ - The FairPlay notice no longer says dynamic analysis always covers an
49
+ encrypted app's own code: it does for most apps, and strongly protected apps
50
+ can block it.
51
+
52
+ ### Fixed
53
+
54
+ - PHP findings changed from one run to the next (49 to 52 on the same code):
55
+ the PHP parser is not safe with parallel workers. PHP rules now run in a
56
+ single worker.
57
+ - PHP command injection with a trailing concatenation
58
+ (`shell_exec('ping ' . $target)`) was not detected, and escaping functions
59
+ were not recognized as sanitizers. Values checked with `is_numeric()`,
60
+ `ctype_digit()` or `filter_var()` are now treated as safe.
61
+ - Private-key detection no longer fires on a lone `BEGIN PRIVATE KEY`
62
+ delimiter string; keys embedded as a single escaped string are still found.
63
+ The high-entropy secret rule ignores public keys, constant names and path
64
+ values. Rust: DDL statements built with `format!` are no longer reported as
65
+ SQL injection.
66
+ - A web repository with an iOS app in a subdirectory was reported as web only,
67
+ with a coverage note pointing to a directory that did not exist.
68
+ - SCA on an app with no identifiable dependency versions printed "0 known
69
+ CVEs across 0 dependencies", which reads as clean. It now says the check
70
+ was not applicable (`applicable: false` in JSON).
71
+ - Upgrade links returned by the server as relative paths are printed as full
72
+ URLs.
73
+ - semgrep installed by pipx or in a non-activated virtualenv was not found,
74
+ and the scan fell back to regex rules only.
75
+ - Cancelling a scan (SIGTERM/SIGHUP, CI cancel) left semgrep workers running,
76
+ including workers that ignore SIGTERM.
77
+ - Without network access, each semgrep pass stalled for about 100 seconds on
78
+ semgrep's own version check. The version check and semgrep metrics are now
79
+ off.
80
+ - `web-scan --active` always timed out: its time budget assumed a third of the
81
+ real template set.
82
+ - A corrupt or truncated APK/AAB/IPA exits 3 (unusable target) instead of 4.
83
+ `host-audit` rejects an invalid host, user or port before connecting (exit 3).
84
+ - An output path that is a directory, or in a missing directory, is refused
85
+ before the scan starts, for `web-scan`, `host-audit` and `cloud-scan` too.
86
+ - `narvy doctor` printed raw color codes from nuclei's version banner.
87
+ - C/C++ files are reported as not analyzed instead of being silently skipped.
88
+ - Plain APK results uploaded with `--upload` were filed as source scans.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: narvy-cli
3
- Version: 1.0.3
3
+ Version: 1.1.0
4
4
  Summary: Free, local SAST scanner for Android, iOS, and polyglot web/backend source - plus passive web, host-audit, and cloud-posture scanning.
5
5
  Author: Narvy
6
6
  License: Apache-2.0
@@ -45,18 +45,20 @@ cryptography, SSRF, injection, and more - in:
45
45
  `.apks`), or a raw Gradle/Kotlin source checkout
46
46
  - **iOS**: an unencrypted `.ipa`, or a raw Xcode/Swift source checkout
47
47
  - **Web/backend source**: JavaScript/TypeScript, Python, PHP, Go, Ruby,
48
- Rust, Java/Kotlin, C#/.NET
48
+ Rust, Java/Kotlin, and C#/.NET (secrets and dependencies)
49
49
 
50
50
  It also ships three standalone scanners that need nothing but network
51
51
  access or your own existing credentials:
52
52
 
53
- - `web-scan` - passive Nuclei-based scan of a live URL
53
+ - `web-scan` - Nuclei-based scan of a live URL (passive by default, `--active` opt-in)
54
54
  - `host-audit` - agentless host audit over your own SSH access (Narvy's own
55
55
  host checks plus Lynis (GPL-3.0) as the hardening baseline)
56
56
  - `cloud-scan` - AWS account posture check using your own ambient AWS
57
57
  credentials
58
58
 
59
- Nothing is uploaded anywhere unless you explicitly pass `--upload`.
59
+ Your code, binaries and findings are never uploaded unless you explicitly
60
+ pass `--upload`. The CLI does send anonymous usage statistics, described
61
+ exactly in [Telemetry](#telemetry), which you can turn off.
60
62
 
61
63
  ## Install
62
64
 
@@ -120,14 +122,85 @@ machine's free memory, so an app that won't fit fails in about a second
120
122
  instead of after several minutes of doomed work. The estimate is a
121
123
  heuristic, not a hard limit - `--force` runs the decompile regardless.
122
124
 
123
- ### Optional Narvy Cloud scan
125
+ ### Sending results to your Narvy dashboard
126
+
127
+ `narvy login` links the CLI to a Narvy account. Add `--upload` to a scan to
128
+ send its results (the findings, never your code or binary) to your account,
129
+ where they are de-duplicated, false-positive filtered and kept in the
130
+ dashboard history; the CLI prints a direct link to the scan. This needs a
131
+ paid plan (Starter and up) and is never on by default. `narvy upload-all
132
+ <directory>` does the same for every binary target found in a folder in one
133
+ command. `narvy scans` lists recent scans on your account from the terminal.
134
+
135
+ ### Exit codes
136
+
137
+ | Code | Meaning |
138
+ |------|---------|
139
+ | 0 | Scan completed (and nothing reached the `--fail-on` threshold) |
140
+ | 1 | `--fail-on`: at least one finding at or above the threshold |
141
+ | 2 | `--fail-on`: nothing reached the threshold, but part of the scan did not run (timed-out pass, unreachable CVE database), so it cannot be certified clean |
142
+ | 3 | The target or arguments are not usable (path not found, unsupported file type or project, refused URL, invalid option) |
143
+ | 4 | The scan, login, upload or report write failed |
144
+ | 130 | Interrupted (Ctrl-C, SIGTERM) |
145
+
146
+ ### Resource limits
147
+
148
+ The structural analysis pass runs with at most 4 parallel workers and a
149
+ 3 GB memory ceiling per analyzed file, so a very large repository does not
150
+ exhaust the machine. `NARVY_SEMGREP_JOBS` and `NARVY_SEMGREP_MAX_MEMORY_MB`
151
+ (0 = no ceiling) override them; files skipped because of the ceiling are
152
+ listed in the scan notes. Outside a terminal (CI logs), a progress line is
153
+ printed every 30 seconds; `NARVY_PROGRESS_INTERVAL` changes that (0 = off).
154
+
155
+ ## Telemetry
156
+
157
+ The CLI sends one small anonymous event per command, so we can see which
158
+ scan types are used and where they fail. It is sent in the background with
159
+ a 1 second timeout, is never retried or stored for later, and never fails a
160
+ scan. At most it adds 1 second at exit. A notice is printed the first time it runs.
161
+
162
+ Exactly these fields are sent:
163
+
164
+ | Field | Example |
165
+ |-------|---------|
166
+ | `schema` | `1`, the event format version |
167
+ | `install_id` | a random UUID created on first run and stored in `~/.narvy/config.json` (not derived from your machine, user or account) |
168
+ | `cli_version`, `python_version`, `os` | `1.1.0`, `3.11`, `linux` / `darwin` / `windows` / `other` |
169
+ | `ci` | `true` when a CI environment variable is set |
170
+ | `command` | `scan`, `web-scan`, `host-audit`, ... |
171
+ | `surface` | `android`, `ios`, `source`, `web`, `host`, `cloud` or `none` |
172
+ | `duration_bucket` | `<10s`, `10s-1m`, `1-5m`, `5-15m`, `15-60m`, `>60m` |
173
+ | `findings` | per severity, a bucket: `0`, `1-5`, `6-20`, `21-100`, `101-500`, `>500` |
174
+ | `exit_code` | the process exit code |
175
+ | `error_class` | on a crash, the exception class name only (e.g. `ValueError`), never its message |
176
+
177
+ Never sent: code, file, project, package or repository names, paths, URLs,
178
+ hosts or IPs you scan, finding details, your account id or email.
179
+
180
+ Turn it off with any of:
124
181
 
125
- `narvy login` links the CLI to a Narvy account. Add `--upload`
126
- to a scan to also submit it for a deeper cloud pass (full taint
127
- analysis, compliance mapping, dashboard history) - not required for local
128
- scanning, and not on by default. `narvy upload-all <directory>` does
129
- the same for every binary target found in a folder in one command.
130
- `narvy scans` lists recent scans on your account from the terminal.
182
+ ```bash
183
+ narvy telemetry off # saved in ~/.narvy/config.json; `narvy telemetry on` to re-enable
184
+ export NARVY_TELEMETRY=0
185
+ export DO_NOT_TRACK=1
186
+ ```
187
+
188
+ `narvy telemetry status` shows the current state.
189
+
190
+ ## Free rules and premium rules
191
+
192
+ The CLI ships the community rule set under Apache 2.0: the language-level
193
+ rules for every supported language (injection, weak cryptography, dangerous
194
+ configuration, storage, WebView, transport), every hardcoded-secret rule, and
195
+ the Android and iOS rules. It is the same engine and the same community rules
196
+ that Narvy runs on its servers.
197
+
198
+ Framework-specific rules (Django, Flask, FastAPI, Express, Laravel, Symfony,
199
+ Ruby on Rails, Spring and the Java persistence/serialization stack, Gin) and
200
+ the C#/.NET code rules are premium: they run when a project is scanned on a
201
+ Narvy paid plan, not in the CLI. When a scanned project uses one of those
202
+ frameworks, the CLI says so in one line at the end of the scan, with the
203
+ number of checks it did not run. It never hides a finding it did compute.
131
204
 
132
205
  ## Known limitations
133
206
 
@@ -12,18 +12,20 @@ cryptography, SSRF, injection, and more - in:
12
12
  `.apks`), or a raw Gradle/Kotlin source checkout
13
13
  - **iOS**: an unencrypted `.ipa`, or a raw Xcode/Swift source checkout
14
14
  - **Web/backend source**: JavaScript/TypeScript, Python, PHP, Go, Ruby,
15
- Rust, Java/Kotlin, C#/.NET
15
+ Rust, Java/Kotlin, and C#/.NET (secrets and dependencies)
16
16
 
17
17
  It also ships three standalone scanners that need nothing but network
18
18
  access or your own existing credentials:
19
19
 
20
- - `web-scan` - passive Nuclei-based scan of a live URL
20
+ - `web-scan` - Nuclei-based scan of a live URL (passive by default, `--active` opt-in)
21
21
  - `host-audit` - agentless host audit over your own SSH access (Narvy's own
22
22
  host checks plus Lynis (GPL-3.0) as the hardening baseline)
23
23
  - `cloud-scan` - AWS account posture check using your own ambient AWS
24
24
  credentials
25
25
 
26
- Nothing is uploaded anywhere unless you explicitly pass `--upload`.
26
+ Your code, binaries and findings are never uploaded unless you explicitly
27
+ pass `--upload`. The CLI does send anonymous usage statistics, described
28
+ exactly in [Telemetry](#telemetry), which you can turn off.
27
29
 
28
30
  ## Install
29
31
 
@@ -87,14 +89,85 @@ machine's free memory, so an app that won't fit fails in about a second
87
89
  instead of after several minutes of doomed work. The estimate is a
88
90
  heuristic, not a hard limit - `--force` runs the decompile regardless.
89
91
 
90
- ### Optional Narvy Cloud scan
92
+ ### Sending results to your Narvy dashboard
93
+
94
+ `narvy login` links the CLI to a Narvy account. Add `--upload` to a scan to
95
+ send its results (the findings, never your code or binary) to your account,
96
+ where they are de-duplicated, false-positive filtered and kept in the
97
+ dashboard history; the CLI prints a direct link to the scan. This needs a
98
+ paid plan (Starter and up) and is never on by default. `narvy upload-all
99
+ <directory>` does the same for every binary target found in a folder in one
100
+ command. `narvy scans` lists recent scans on your account from the terminal.
101
+
102
+ ### Exit codes
103
+
104
+ | Code | Meaning |
105
+ |------|---------|
106
+ | 0 | Scan completed (and nothing reached the `--fail-on` threshold) |
107
+ | 1 | `--fail-on`: at least one finding at or above the threshold |
108
+ | 2 | `--fail-on`: nothing reached the threshold, but part of the scan did not run (timed-out pass, unreachable CVE database), so it cannot be certified clean |
109
+ | 3 | The target or arguments are not usable (path not found, unsupported file type or project, refused URL, invalid option) |
110
+ | 4 | The scan, login, upload or report write failed |
111
+ | 130 | Interrupted (Ctrl-C, SIGTERM) |
112
+
113
+ ### Resource limits
114
+
115
+ The structural analysis pass runs with at most 4 parallel workers and a
116
+ 3 GB memory ceiling per analyzed file, so a very large repository does not
117
+ exhaust the machine. `NARVY_SEMGREP_JOBS` and `NARVY_SEMGREP_MAX_MEMORY_MB`
118
+ (0 = no ceiling) override them; files skipped because of the ceiling are
119
+ listed in the scan notes. Outside a terminal (CI logs), a progress line is
120
+ printed every 30 seconds; `NARVY_PROGRESS_INTERVAL` changes that (0 = off).
121
+
122
+ ## Telemetry
123
+
124
+ The CLI sends one small anonymous event per command, so we can see which
125
+ scan types are used and where they fail. It is sent in the background with
126
+ a 1 second timeout, is never retried or stored for later, and never fails a
127
+ scan. At most it adds 1 second at exit. A notice is printed the first time it runs.
128
+
129
+ Exactly these fields are sent:
130
+
131
+ | Field | Example |
132
+ |-------|---------|
133
+ | `schema` | `1`, the event format version |
134
+ | `install_id` | a random UUID created on first run and stored in `~/.narvy/config.json` (not derived from your machine, user or account) |
135
+ | `cli_version`, `python_version`, `os` | `1.1.0`, `3.11`, `linux` / `darwin` / `windows` / `other` |
136
+ | `ci` | `true` when a CI environment variable is set |
137
+ | `command` | `scan`, `web-scan`, `host-audit`, ... |
138
+ | `surface` | `android`, `ios`, `source`, `web`, `host`, `cloud` or `none` |
139
+ | `duration_bucket` | `<10s`, `10s-1m`, `1-5m`, `5-15m`, `15-60m`, `>60m` |
140
+ | `findings` | per severity, a bucket: `0`, `1-5`, `6-20`, `21-100`, `101-500`, `>500` |
141
+ | `exit_code` | the process exit code |
142
+ | `error_class` | on a crash, the exception class name only (e.g. `ValueError`), never its message |
143
+
144
+ Never sent: code, file, project, package or repository names, paths, URLs,
145
+ hosts or IPs you scan, finding details, your account id or email.
146
+
147
+ Turn it off with any of:
91
148
 
92
- `narvy login` links the CLI to a Narvy account. Add `--upload`
93
- to a scan to also submit it for a deeper cloud pass (full taint
94
- analysis, compliance mapping, dashboard history) - not required for local
95
- scanning, and not on by default. `narvy upload-all <directory>` does
96
- the same for every binary target found in a folder in one command.
97
- `narvy scans` lists recent scans on your account from the terminal.
149
+ ```bash
150
+ narvy telemetry off # saved in ~/.narvy/config.json; `narvy telemetry on` to re-enable
151
+ export NARVY_TELEMETRY=0
152
+ export DO_NOT_TRACK=1
153
+ ```
154
+
155
+ `narvy telemetry status` shows the current state.
156
+
157
+ ## Free rules and premium rules
158
+
159
+ The CLI ships the community rule set under Apache 2.0: the language-level
160
+ rules for every supported language (injection, weak cryptography, dangerous
161
+ configuration, storage, WebView, transport), every hardcoded-secret rule, and
162
+ the Android and iOS rules. It is the same engine and the same community rules
163
+ that Narvy runs on its servers.
164
+
165
+ Framework-specific rules (Django, Flask, FastAPI, Express, Laravel, Symfony,
166
+ Ruby on Rails, Spring and the Java persistence/serialization stack, Gin) and
167
+ the C#/.NET code rules are premium: they run when a project is scanned on a
168
+ Narvy paid plan, not in the CLI. When a scanned project uses one of those
169
+ frameworks, the CLI says so in one line at the end of the scan, with the
170
+ number of checks it did not run. It never hides a finding it did compute.
98
171
 
99
172
  ## Known limitations
100
173
 
@@ -1,3 +1,3 @@
1
1
  """Narvy CLI - free, local SAST for Android, iOS and polyglot source."""
2
2
 
3
- __version__ = "1.0.3"
3
+ __version__ = "1.1.0"
@@ -1,7 +1,4 @@
1
- """Android source-repo (Gradle/Java/Kotlin) SAST entry point.
2
-
3
- Public API: analyze_source(source_dir, override_config=None) -> dict.
4
- """
1
+ """Android source (Gradle/Java/Kotlin) SAST. Entry point: analyze_source()."""
5
2
  from __future__ import annotations
6
3
 
7
4
  import os
@@ -85,16 +82,13 @@ def _is_prefkey_echo_or_typename(line: str) -> bool:
85
82
  if not lhs:
86
83
  return False
87
84
  name = lhs.group(1)
88
- # Echo: the value repeats the constant name, or the constant is a KEY_*/PREF_*
89
- # preference-key holding a settings-store key slug (e.g. KEY_SECRET_TOKEN =
90
- # "secret_token_pref").
85
+ # The value just echoes the constant name, or it's a KEY_*/PREF_* settings key
86
+ # (e.g. KEY_SECRET_TOKEN = "secret_token_pref").
91
87
  return val.lower() in name.lower() or bool(_PREFKEY_CONST_RE.match(name))
92
88
 
93
89
 
94
90
  def _has_no_string_literal_rhs(line: str) -> bool:
95
- """A name-based secret match whose right-hand side has NO string literal - a
96
- function call or identifier (e.g. `val apiKey = getApiKeyOrDefault()`), not a
97
- hardcoded secret. A real hardcoded secret is always a quoted literal."""
91
+ """True when a name-based secret match is assigned from a call or identifier, not a literal."""
98
92
  return not _QUOTED_RE.search(line)
99
93
 
100
94
 
@@ -1,5 +1,4 @@
1
- """Split-APK bundle support (.apkm / .xapk / .apks) for the Android scan path: extract the
2
- app module, preferred ABI split and any dex-bearing feature module, without merging splits."""
1
+ """Split-APK bundles (.apkm/.xapk/.apks): pull out the app module, ABI split and dex feature modules."""
3
2
 
4
3
  import json
5
4
  import logging
@@ -1,5 +1,4 @@
1
- """Call-site and element context gates for the Android regex rules: read the surrounding
2
- call or manifest element and drop or re-grade the finding (rule_engine can't look around a match)."""
1
+ """Context gates for the Android regex rules: look at the call site or manifest element around a match."""
3
2
  from __future__ import annotations
4
3
 
5
4
  import re
@@ -1,5 +1,4 @@
1
- """Pre-flight memory estimate for the JADX decompilation path: estimate the Java
2
- heap jadx needs from DEX header counts and warn before OOM. Advisory only; `--force` overrides."""
1
+ """Estimate the jadx heap from DEX header counts and warn before an OOM. `--force` skips it."""
3
2
 
4
3
  import ctypes
5
4
  import logging
@@ -1,6 +1,6 @@
1
1
  # Narvy SAST for Bitbucket Pipelines. Written by `narvy init-ci --bitbucket`; edit freely.
2
2
  # Already have a bitbucket-pipelines.yml? Copy the step below into it instead of overwriting.
3
- # Note: SARIF is stored as a build artifact (not Code Insights); the --fail-on gate still fails the build.
3
+ # SARIF is kept as a build artifact (not Code Insights); --fail-on still fails the build.
4
4
  image: python:3.11
5
5
 
6
6
  pipelines:
@@ -1,6 +1,6 @@
1
1
  # Narvy SAST for GitLab CI. Written by `narvy init-ci --gitlab`; edit freely.
2
2
  # Already have a .gitlab-ci.yml? Copy the narvy_sast job below into it instead of overwriting.
3
- # Note: SARIF is stored as a plain artifact (not GitLab's SAST schema), but the --fail-on gate still blocks the MR.
3
+ # SARIF is kept as a plain artifact (not GitLab's SAST schema); --fail-on still blocks the MR.
4
4
  stages:
5
5
  - test
6
6
 
@@ -1,6 +1,4 @@
1
- """AWS cloud posture scanner: security-group, IAM, storage and logging checks
2
- across every enabled region. Credentials resolve through boto3's normal chain.
3
- """
1
+ """AWS posture checks (security groups, IAM, storage, logging) across enabled regions."""
4
2
 
5
3
  import boto3
6
4
  from botocore.exceptions import ClientError, NoCredentialsError, EndpointConnectionError
@@ -1055,9 +1053,8 @@ def analyze_aws_security(credentials, region=None, endpoint_url=None):
1055
1053
  # letting the per-check handlers below report an empty, clean scan.
1056
1054
  _verify_credentials(endpoint_url=endpoint_url)
1057
1055
 
1058
- # Track every analyzer that could not complete (AccessDenied / throttling /
1059
- # missing permission) so 0 findings from a blinded scan is reported as a
1060
- # coverage gap, not as a clean pass.
1056
+ # Analyzers that couldn't finish (AccessDenied, throttling), so a blind scan
1057
+ # shows up as a coverage gap instead of a clean pass.
1061
1058
  coverage_errors = []
1062
1059
 
1063
1060
  def _collect(dest, label, fn):
@@ -1,6 +1,4 @@
1
- """Comment-aware masking for C-family source (Java, Kotlin, Objective-C, Swift):
2
- mark byte offsets inside `//` and `/* */` comments so regex rules don't fire on
3
- commented-out code. String literals excluded; when unsure, treated as code (never drops a finding)."""
1
+ """Find `//` and `/* */` comment ranges in C-family source so regex rules skip commented-out code."""
4
2
  from __future__ import annotations
5
3
 
6
4
  from typing import List
@@ -1,7 +1,4 @@
1
- """Name-based correlation pass for hardcoded key and IV material reaching
2
- javax.crypto.spec.{SecretKeySpec,IvParameterSpec} through one level of
3
- indirection. Correlation is by variable name within a single file, not scope.
4
- """
1
+ """Match hardcoded key/IV literals to SecretKeySpec/IvParameterSpec by variable name, one file at a time."""
5
2
  from __future__ import annotations
6
3
 
7
4
  import re
@@ -1,5 +1,4 @@
1
- """`narvy doctor`: preflight environment check.
2
- Prints a PASS/FAIL list with a concrete fix for each failure."""
1
+ """`narvy doctor`: check the local environment and print a fix for each failure."""
3
2
  import os
4
3
  import platform
5
4
  import re
@@ -132,12 +131,11 @@ def _check_network():
132
131
 
133
132
 
134
133
  def _check_semgrep():
135
- """Informational: semgrep drives the deep AST/taint pass. A broken install leaves it off
136
- PATH and `scan` silently falls back to regex-only rules."""
134
+ """semgrep runs the AST/taint pass; without it `scan` falls back to regex rules only."""
137
135
  from . import semgrep_engine
138
136
  if semgrep_engine.is_available():
139
- return True, f"{shutil.which('semgrep')}", None
140
- return True, "Not on PATH - scans fall back to regex-only (reduced coverage)", (
137
+ return True, f"{semgrep_engine.semgrep_bin()}", None
138
+ return True, "Not found - scans fall back to regex-only (reduced coverage)", (
141
139
  "semgrep is the deep structural analysis engine for `narvy scan` "
142
140
  "(Android/iOS/web source and binary modes). Without it, scans run regex "
143
141
  "rules only and miss AST/taint-based findings. Install it: "
@@ -158,7 +156,8 @@ def _check_nuclei():
158
156
  "connection can reach github.com, or install nuclei yourself "
159
157
  "and put it on PATH: https://github.com/projectdiscovery/nuclei/releases"
160
158
  )
161
- return True, f"{nuclei_bin} ({version.splitlines()[0][:60]})", None
159
+ first = re.sub(r"\x1b\[[0-9;]*m", "", version.splitlines()[0])
160
+ return True, f"{nuclei_bin} ({first[:60]})", None
162
161
 
163
162
 
164
163
  def _check_ssh_client():
@@ -238,7 +237,8 @@ def run_doctor():
238
237
  console.print(f"\n[bold]{name}:[/bold] {_rich_escape(fix)}")
239
238
 
240
239
  for name, hint in optional_hints:
241
- console.print(f"\n[bold cyan]{name} (optional):[/bold cyan] {_rich_escape(hint)}")
240
+ label = name if "optional" in name else f"{name} (optional)"
241
+ console.print(f"\n[bold cyan]{label}:[/bold cyan] {_rich_escape(hint)}")
242
242
 
243
243
  if not all_ok:
244
244
  sys.exit(1)
@@ -1,5 +1,4 @@
1
- """Run Lynis on a remote host over SSH and return normalized findings.
2
- A sudo password is never sent, so full coverage needs a root key or NOPASSWD sudo."""
1
+ """Run Lynis on a remote host over SSH. Full coverage needs a root key or NOPASSWD sudo."""
3
2
 
4
3
  from __future__ import annotations
5
4
 
@@ -25,6 +24,8 @@ class AuditOutcome:
25
24
  hardening_index: Any
26
25
  meta: Dict[str, str]
27
26
  error: str = ""
27
+ # Raw Lynis .dat report, captured for --verbose; never streamed on its own.
28
+ raw: str = ""
28
29
 
29
30
 
30
31
  def _lynis_prefix(privilege: str) -> str:
@@ -154,4 +155,5 @@ def audit_host(conn: HostConn, privilege: str = "auto") -> AuditOutcome:
154
155
  findings=list(parsed["findings"]) + list(narvy_findings),
155
156
  hardening_index=parsed["hardening_index"],
156
157
  meta=meta,
158
+ raw=dat,
157
159
  )
@@ -1,7 +1,4 @@
1
- """Knowledge base for host and infrastructure audit findings.
2
-
3
- Maps each hardening-engine test id to a control with a remediation and severity.
4
- """
1
+ """Map host-audit test ids to a control, severity and remediation."""
5
2
 
6
3
  from __future__ import annotations
7
4
 
@@ -975,8 +972,7 @@ HOST_CONTROLS: Dict[str, HostControl] = {
975
972
 
976
973
 
977
974
  def lookup(test_id: str) -> Optional[HostControl]:
978
- """Control for an engine test id, or None. Callers must keep emitting the
979
- raw finding for an unknown id rather than dropping it."""
975
+ """Control for a test id, or None. Callers keep the raw finding for unknown ids."""
980
976
  if not test_id:
981
977
  return None
982
978
  return HOST_CONTROLS.get(test_id.strip().upper())
@@ -1,6 +1,4 @@
1
- """Ship a checksum-pinned Lynis to a target host for one audit, run it from a
2
- temporary directory, then remove it. Nothing is installed on the host.
3
- """
1
+ """Copy a checksum-pinned Lynis to a temp dir on the host, run it, then remove it."""
4
2
 
5
3
  from __future__ import annotations
6
4
 
@@ -1,7 +1,4 @@
1
- """Parse a Lynis `lynis-report.dat` into Narvy host-audit findings.
2
-
3
- The `.dat` is a flat key=value file; repeated keys use a `key[]=` suffix.
4
- """
1
+ """Parse `lynis-report.dat` (flat key=value, `key[]=` for repeats) into findings."""
5
2
 
6
3
  from __future__ import annotations
7
4
 
@@ -1,6 +1,4 @@
1
- """Host exposure checks run alongside the Lynis baseline: reachable unauthenticated
2
- data services, cloud metadata, container posture, and secret/VCS disclosure under a
3
- web root. Each fires only on a confirmed exposure; checks take a ``run`` probe callable."""
1
+ """Extra host exposure checks (open data services, metadata, containers, web-root leaks)."""
4
2
 
5
3
  from __future__ import annotations
6
4
 
@@ -1,6 +1,4 @@
1
- """Agentless SSH exec helper for host audits, built on the system ssh client.
2
- Private keys go to a 0600 temp file wiped after use; hostname/user are validated
3
- before argv is built; host keys use a per-call known_hosts file."""
1
+ """Run commands on a host with the system ssh client, using a temp key file and per-call known_hosts."""
4
2
 
5
3
  from __future__ import annotations
6
4