narvy-cli 1.1.2__tar.gz → 1.1.5__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 (99) hide show
  1. narvy_cli-1.1.5/CHANGELOG.md +338 -0
  2. {narvy_cli-1.1.2/narvy_cli.egg-info → narvy_cli-1.1.5}/PKG-INFO +38 -5
  3. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/README.md +37 -4
  4. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/__init__.py +1 -1
  5. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/android/source_analyzer.py +6 -0
  6. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/android_rule_context.py +19 -0
  7. narvy_cli-1.1.5/narvy/apk_memory_preflight.py +461 -0
  8. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/crypto_taint_lite.py +2 -0
  9. narvy_cli-1.1.5/narvy/decompiler.py +472 -0
  10. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/doctor.py +8 -9
  11. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/ios/binary_analyzer.py +7 -2
  12. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/ios/source_analyzer.py +11 -1
  13. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/main.py +279 -66
  14. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/native_hardening.py +3 -0
  15. narvy_cli-1.1.5/narvy/pathnorm.py +60 -0
  16. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/sca/android_deps.py +6 -9
  17. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/sca/ios_deps.py +49 -15
  18. narvy_cli-1.1.5/narvy/sca/osv_client.py +988 -0
  19. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/sca/web_deps.py +6 -8
  20. narvy_cli-1.1.5/narvy/secret_value_grade.py +258 -0
  21. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/semgrep_engine.py +163 -31
  22. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/web/source_analyzer.py +6 -0
  23. {narvy_cli-1.1.2 → narvy_cli-1.1.5/narvy_cli.egg-info}/PKG-INFO +38 -5
  24. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy_cli.egg-info/SOURCES.txt +2 -0
  25. narvy_cli-1.1.2/CHANGELOG.md +0 -170
  26. narvy_cli-1.1.2/narvy/apk_memory_preflight.py +0 -247
  27. narvy_cli-1.1.2/narvy/decompiler.py +0 -360
  28. narvy_cli-1.1.2/narvy/sca/osv_client.py +0 -628
  29. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/LICENSE +0 -0
  30. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/MANIFEST.in +0 -0
  31. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/SECURITY.md +0 -0
  32. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/__main__.py +0 -0
  33. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/android/__init__.py +0 -0
  34. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/android/split_bundle.py +0 -0
  35. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/auth.py +0 -0
  36. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/ci_templates/bitbucket.yml +0 -0
  37. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/ci_templates/github.yml +0 -0
  38. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/ci_templates/gitlab.yml +0 -0
  39. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/cloud/__init__.py +0 -0
  40. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/cloud/aws_scan.py +0 -0
  41. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/comment_filter.py +0 -0
  42. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/host/__init__.py +0 -0
  43. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/host/audit.py +0 -0
  44. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/host/host_knowledge.py +0 -0
  45. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/host/lynis_bootstrap.py +0 -0
  46. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/host/lynis_parser.py +0 -0
  47. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/host/narvy_checks.py +0 -0
  48. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/host/report.py +0 -0
  49. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/host/ssh_exec.py +0 -0
  50. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/ios/__init__.py +0 -0
  51. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/ios/plist_checks.py +0 -0
  52. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/ios/third_party_filter.py +0 -0
  53. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/ios/trust_all_context.py +0 -0
  54. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/own_reports.py +0 -0
  55. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/pathhint.py +0 -0
  56. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/proc.py +0 -0
  57. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/reporter.py +0 -0
  58. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rule_engine.py +0 -0
  59. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/android/config.yml +0 -0
  60. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/android/crypto.yml +0 -0
  61. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/android/secrets.yml +0 -0
  62. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/android/storage.yml +0 -0
  63. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/android/webview.yml +0 -0
  64. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/android.yml +0 -0
  65. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/ios/objc/crypto.yml +0 -0
  66. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/ios/objc/network.yml +0 -0
  67. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/ios/objc/secrets.yml +0 -0
  68. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/ios/objc/storage.yml +0 -0
  69. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/ios/objc/webview.yml +0 -0
  70. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/ios_swift.yml +0 -0
  71. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/web/go.yml +0 -0
  72. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/web/java.yml +0 -0
  73. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/web/javascript.yml +0 -0
  74. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/web/kotlin.yml +0 -0
  75. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/web/php.yml +0 -0
  76. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/web/python.yml +0 -0
  77. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/web/ruby.yml +0 -0
  78. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/web/rust.yml +0 -0
  79. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/web/secrets.yml +0 -0
  80. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/rules/web/secrets_supplement.yml +0 -0
  81. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/sca/__init__.py +0 -0
  82. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/scope_config.py +0 -0
  83. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/stack_protector_evidence.py +0 -0
  84. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/telemetry.py +0 -0
  85. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/third_party_filter.py +0 -0
  86. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/uploader.py +0 -0
  87. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/weak_prng_context.py +0 -0
  88. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/web/__init__.py +0 -0
  89. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/web/nuclei_binary.py +0 -0
  90. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/web/scan_blocklist.py +0 -0
  91. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/web/scanner.py +0 -0
  92. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy/web/ssrf_guard.py +0 -0
  93. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy_cli.egg-info/dependency_links.txt +0 -0
  94. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy_cli.egg-info/entry_points.txt +0 -0
  95. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy_cli.egg-info/requires.txt +0 -0
  96. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/narvy_cli.egg-info/top_level.txt +0 -0
  97. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/pyproject.toml +0 -0
  98. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/setup.cfg +0 -0
  99. {narvy_cli-1.1.2 → narvy_cli-1.1.5}/setup.py +0 -0
@@ -0,0 +1,338 @@
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.5]
9
+
10
+ ### Changed
11
+
12
+ - The dependency check (SCA) now queries Narvy's EU-hosted vulnerability
13
+ database, a mirror of OSV.dev, instead of api.osv.dev and api.github.com.
14
+ It sends package names, ecosystems and versions only, never code, file
15
+ paths or findings, and says so when it starts. `OSV_API_URL` points it
16
+ at another OSV-compatible server; it accepts a base URL or, as before,
17
+ the full `/query` URL. The batch URL now follows `OSV_API_URL` (it used
18
+ to stay on api.osv.dev unless `OSV_BATCH_API_URL` was also set).
19
+ - iOS: the direct GitHub Advisory lookup is off by default. All 63 reviewed
20
+ GitHub `swift` advisories are in OSV as `SwiftURL`, so the database above
21
+ carries them. `NARVY_GITHUB_ADVISORIES=1` turns the direct lookup back on,
22
+ and it now uses `GITHUB_TOKEN` when set (the rate-limit message already
23
+ told users to set it, but it was not read).
24
+ - Batch pre-filter requests carry up to 1000 dependencies and stay under
25
+ 1 MB. A batch the server refuses as too large (HTTP 413) is split in
26
+ halves and retried, down to a single dependency; one that is still
27
+ refused is looked up on its own and counted unresolved if that fails.
28
+ - A scan now checks up to 20,000 unique dependencies (was 400, which left
29
+ projects such as a Django app with a JavaScript front end reported as
30
+ partial). Past that cap the check is still reported partial, with the
31
+ number not queried. Requests to Narvy's database are paced to stay under
32
+ its per-IP limits, so a very large project waits instead of being cut
33
+ off by a rate limit; small scans are not slowed.
34
+
35
+ ### Fixed
36
+
37
+ - A dependency check that got no usable answer is reported INCOMPLETE,
38
+ never clean, in every case: server unreachable, 503 (database data older
39
+ than 48 hours, or unavailable), 429 after one retry that honours
40
+ `Retry-After` (at most 10 s), 400, 404 or an unreadable response. After
41
+ the first such failure the rest of the run stops querying and counts the
42
+ remaining dependencies as unresolved, with one log line for the outage
43
+ instead of one per dependency. The end-of-scan message names the server
44
+ and the reason.
45
+ - iOS libraries with no known source repository URL (most CocoaPods pods
46
+ and embedded frameworks) were reported with 0 CVEs as if checked. They
47
+ are now looked up by bare name (a few advisories are published that way)
48
+ and counted as unverified in the SCA coverage note.
49
+ - Ecosystems the database does not carry are no longer sent (the server
50
+ would reject the whole batch); those dependencies are counted as
51
+ unverified.
52
+ - Results served from an expired local cache while the database was
53
+ unavailable are now flagged in the coverage note.
54
+ - JSON output: `summary.sca_coverage` gains `dependencies_unverified`,
55
+ `dependencies_from_stale_cache` and `vulnerability_db` (server host,
56
+ database snapshot time and age when the server reports them, last error).
57
+ - Android: an APK scan is no longer refused for lack of memory. 1.1.2 to
58
+ 1.1.4 refused a 6,300-class app on a laptop with 1.1 GB free ("--max-mem
59
+ 4g asks JADX for 4.0 GB of Java heap ... run it anyway with --force"),
60
+ and the `--max-mem 1g` it suggested was refused the same way. The jadx
61
+ heap is now sized for each app: the smallest of `--max-mem` (new default
62
+ `auto`), what fits in the memory free right now, and twice the app's
63
+ estimate. The JVM's own memory is counted as about 250 MB plus 5% of the
64
+ heap (measured on real apps), not a fixed 1 GB reserve plus 30%. When the
65
+ app's estimate does not fit, the scan runs with the largest heap that
66
+ does and says so before jadx starts. If jadx runs out of Java heap it is
67
+ retried once with a bigger heap when memory allows; when the operating
68
+ system refuses memory (commit limit, OOM killer) it is retried once with
69
+ a smaller heap and one thread; then the scan fails with the numbers and
70
+ what to do. `--force` now means: exactly `--max-mem`, no sizing, no retry.
71
+ - A generous `--max-mem` no longer inflates memory use: `-Xmx4g` on a
72
+ 6.5k-class app peaked at 1.56 GB of RAM against 0.99 GB with the 1 GB
73
+ heap now chosen, for the same decompiled output.
74
+ - Linux and macOS: jadx no longer runs under an address-space limit
75
+ (RLIMIT_AS). With a small heap it stopped the JVM from starting its
76
+ threads, and jadx then exited 0 without writing any source, which would
77
+ have been scanned as an empty app. A jadx run that exits 0 without
78
+ writing sources, or that printed an OutOfMemoryError, is now a failure,
79
+ never a clean scan.
80
+ - Linux: free memory honours a container or cgroup memory limit (Docker
81
+ `--memory`, CI runners, systemd `MemoryMax`); `/proc/meminfo` shows the
82
+ whole host there, so a 2 GB container used to size jadx for the host.
83
+ - Windows: free memory is `ullAvailPhys` (Task Manager's "Available",
84
+ standby cache included), now also bounded by the commit charge left.
85
+ - Split bundles (.apkm/.xapk/.apks): the heap estimate counts the feature
86
+ modules jadx decompiles together with the base APK.
87
+ - The line printed after decompiling gives the heap used and how many
88
+ methods jadx could not fully decompile (kept as low-level code, so
89
+ findings inside them can be missed); a tight heap raises that number.
90
+ - JSON and SARIF output say whether the structural (Semgrep) pass
91
+ completed: `summary.structural_coverage` and
92
+ `runs[0].properties.structuralCoverage` (`complete`, `incomplete` with
93
+ the reason, or `not_run`). A timed-out pass was only visible on the
94
+ console.
95
+ - Messages no longer send users to `--upload` for something it cannot do:
96
+ it sends findings only, so it neither decompiles a large app nor finishes
97
+ a timed-out structural pass. The memory messages point to an upload from
98
+ the Narvy dashboard, the structural-pass note to `--upload-binary`.
99
+ - The SCA result line no longer reads "checked N ..., found 0 known
100
+ CVE(s)" when the lookups failed; it says their CVE status is unknown.
101
+ - A jadx failure message is printed as text: brackets in jadx output were
102
+ read as console markup.
103
+
104
+ ## [1.1.4]
105
+
106
+ ### Fixed
107
+
108
+ - APK scans: the structural (Semgrep) pass could match none of the app's
109
+ own files and report "Deep analysis INCOMPLETE" (seen on Windows with
110
+ Python 3.10). Two ways to get there were reproduced: a scan folder
111
+ reached through a symlink (a Windows 8.3 short name such as `RUNNER~1`
112
+ or a junction gives the folder a second spelling in the same way), and a
113
+ `.git`/`.hg`/`.svn` folder in any parent of the scan folder, which moves
114
+ semgrep's project root above it so the `sources/...` path filter no
115
+ longer matched. The scan folder is now resolved to one long-name,
116
+ physical path for every tool and every relative path, the path filters
117
+ match at any depth, and if they still match nothing the app's own
118
+ package folders are scanned as explicit roots.
119
+ - Secret findings that fired only on a variable NAME (key, secret,
120
+ password, token, credential) were CRITICAL whatever the value was.
121
+ The value is now checked: a known provider credential format (AWS,
122
+ Google, Stripe, GitHub, Slack, ...) or key material of a real key length
123
+ keeps the rule's severity; a plain non-secret (URL, path, dotted or
124
+ snake_case identifier, UPPER_CASE constant, label repeating the word
125
+ "password", format string, regex, sentence, placeholder) is dropped;
126
+ anything else is capped at MEDIUM with LOW confidence and says why.
127
+ Applies to Android (APK and source), iOS Objective-C and Swift source,
128
+ and iOS binary strings. Provider-format rules are unchanged. JSON output
129
+ carries `original_severity` and `value_evidence` for re-graded findings.
130
+
131
+ ### Changed
132
+
133
+ - End-of-scan message: "Scanned ... with N local rules" counted only the
134
+ pattern rules (APK) or mixed in one entry per dependency advisory found
135
+ (source). It now gives the number of rules actually executed, by engine
136
+ (pattern, taint, structural, native-library, config), and separately how
137
+ many of them produced findings and how many dependency advisories
138
+ matched. A structural pass that did not complete is not counted. JSON
139
+ output: `summary.rules_executed`, `rules_executed_by_engine`,
140
+ `rules_matched`, `sca_advisories_matched`.
141
+ - Console summary table: hits of the same rule in the same file are one
142
+ row with a hit count and the lines involved. `--no-group` restores one
143
+ row per hit. SARIF and JSON output still list every hit.
144
+ - Platform CI harness prints `narvy doctor` output.
145
+
146
+ ## [1.1.3]
147
+
148
+ ### Fixed
149
+
150
+ - APK scans were refused before decompiling on machines with less free
151
+ memory than `--max-mem` needs (about 1.3x the heap plus 1 GB), even for
152
+ small apps. On an 8 GB laptop at the default `--max-mem 4g` that was
153
+ nearly every app, and on macOS the free-memory reading was low by
154
+ several GB. `--max-mem` is a ceiling: when it does not fit but a smaller
155
+ heap still covers the app's estimated need by 1.5x, jadx now runs with
156
+ that smaller heap and the scan says so. A machine that cannot hold even
157
+ that is still refused, with the same advice as before.
158
+ - macOS: available memory comes from the kernel's own figure
159
+ (`kern.memorystatus_level`, what `memory_pressure` reports), which counts
160
+ memory the compressor can reclaim. The `vm_stat` page count is the
161
+ fallback and now includes speculative pages.
162
+ - APK scans: a structural (Semgrep) pass that failed was reported as
163
+ complete with 0 findings when semgrep exited with an error but still
164
+ printed JSON. It is now reported as incomplete with the error, after one
165
+ retry with a single worker. A run whose path filters matched none of the
166
+ app's own source files is reported the same way instead of as a clean
167
+ pass. Rule ids no longer depend on where the package is installed.
168
+
169
+ ### Changed
170
+
171
+ - Platform CI: the check removes the user Scripts/bin directory from PATH
172
+ before running scans (hosted runners ship with it on PATH, a new user's
173
+ shell does not), and prints the APK scan output on success so the
174
+ structural-pass status is in the log.
175
+
176
+ ## [1.1.2]
177
+
178
+ ### Added
179
+
180
+ - `python -m narvy` (`py -m narvy` on Windows) runs the CLI. It works when
181
+ pip put the `narvy` command in a directory that is not on PATH, the
182
+ default for a user install on Windows and macOS. Run that way, Narvy
183
+ prints once the command that adds the directory to PATH (PowerShell on
184
+ Windows, your shell rc file elsewhere). It never changes PATH itself;
185
+ `NARVY_NO_PATH_HINT=1` turns the hint off.
186
+ - README: install steps for Windows, macOS (Homebrew
187
+ `externally-managed-environment`) and Linux, with pipx first.
188
+
189
+ ### Fixed
190
+
191
+ - A user install run as `python -m narvy` (or by the launcher's full path)
192
+ did not find semgrep in the same off-PATH directory, skipped the code pass
193
+ and reported only dependency CVEs. semgrep is now looked up there and its
194
+ directory is put on the PATH it runs with (it starts `pysemgrep` through
195
+ PATH).
196
+ - macOS: when no Java 11+ was installed, the downloaded JRE was never found
197
+ ("no bin/ dir found inside"), so APK scans failed on every such Mac. The
198
+ macOS layout (`Contents/Home/bin`) is handled, Apple Silicon and ARM Linux
199
+ get the native aarch64 JRE instead of x64, and a cached JRE that does not
200
+ run is downloaded again.
201
+ - Windows: jadx failed to start when its path contained a space (a user name
202
+ like `Jean Dupont`, or `C:\Program Files`), because `cmd /c` stripped the
203
+ wrong quotes. The jadx command line now goes through `cmd /d /s /c` as one
204
+ quoted string.
205
+ - Windows: tool output (semgrep, jadx, nuclei, ssh) is decoded as UTF-8
206
+ instead of the console code page, semgrep runs in Python UTF-8 mode, and
207
+ output redirected to a file or pipe replaces a character the code page
208
+ cannot encode instead of stopping the scan with `UnicodeEncodeError`.
209
+ - A file still locked in a temporary directory (antivirus, a slow child
210
+ process) no longer fails the scan at cleanup.
211
+ - SCA: only one version of each package was checked. A lockfile that holds
212
+ `minimist 1.2.8` and a nested `minimist 0.0.8` now checks both. A version
213
+ taken from a manifest range is still dropped when a lockfile resolves that
214
+ package.
215
+ - yarn.lock (berry): the package comes from the `resolution:` line, so
216
+ aliases (`string-width-cjs@npm:string-width@...`) and `patch:` entries
217
+ name the real package, and `__metadata`, workspace, git and local entries
218
+ are no longer sent to OSV as packages.
219
+ - semgrep with more than one worker drops taint-mode results at random with
220
+ no error. Every rule pack that holds a taint rule now runs in its own
221
+ single-worker semgrep process (packs still run side by side). Files where
222
+ a rule hit the per-file timeout are named in the scan notes.
223
+
224
+ ### Changed
225
+
226
+ - Community rule precision round (PHP, JavaScript, Go, Java, Python, Ruby):
227
+ fewer findings on code that is not reachable by an attacker. Informational
228
+ rules are lowered to LOW with the reason.
229
+
230
+ ## [1.1.1]
231
+
232
+ ### Added
233
+
234
+ - Flask cookie checks: `set_cookie()` without `httponly=True` or
235
+ `secure=True`, the same checks Django already had.
236
+ - `scan` warns when files or folders could not be read (permissions) instead
237
+ of skipping them silently; `-v` lists them and the JSON report has
238
+ `summary.unreadable_paths`.
239
+ - `scan --format` is accepted as an alias of `--output`, like the other
240
+ commands.
241
+
242
+ ### Fixed
243
+
244
+ - A second `scan` of the same folder picked up the report written by the
245
+ first one (`narvy.sarif`, the JSON report) and flagged the rule text quoted
246
+ in it as secrets. Narvy reports are now recognized by their content, and
247
+ the `--file` target is never scanned.
248
+ - `--upload` with only `NARVY_TOKEN` set printed "Uploading ... to for
249
+ server-side curation". The line now names the server (`narvy.io`, or
250
+ `NARVY_URL`), and a failed upload or login says which server refused it.
251
+ - `web-scan http://localhost` said "internal/metadata addresses"; it now says
252
+ the address is loopback and explains why local and private targets are
253
+ refused.
254
+ - The web-scan, host-audit and cloud-scan JSON output carries a `tool` field.
255
+ - `narvy doctor` documents its exit codes (0 all required checks pass, 1 one
256
+ failed).
257
+
258
+ ## [1.1.0]
259
+
260
+ First release of the community rule set. The 1.0.x preview releases were
261
+ withdrawn; 1.1.0 replaces them.
262
+
263
+ ### Added
264
+
265
+ - PHP: reflected XSS where request input is concatenated or interpolated into
266
+ an HTML string, and an `include`/`require` of a variable that is set in
267
+ another file (reported as HIGH, to review).
268
+ - Python and Ruby rules: tainted module import, deprecated `ssl.wrap_socket`,
269
+ response header CRLF injection, SSRF from a request-controlled host.
270
+ - A web repository that also holds an iOS app in a subdirectory (an Xcode
271
+ project, workspace or `Package.swift` next to a server directory) is now
272
+ scanned on both surfaces, and the report lists them. Sample, test and
273
+ vendored projects are skipped.
274
+ - `--upload` prints a direct link to the scan in the dashboard.
275
+ - Outside a terminal (CI logs), a "still running" line every 30 seconds
276
+ (`NARVY_PROGRESS_INTERVAL`, 0 = off).
277
+ - Anonymous usage telemetry, listed field by field in the README, with a
278
+ notice on first run. Off with `narvy telemetry off`, `NARVY_TELEMETRY=0` or
279
+ `DO_NOT_TRACK=1`.
280
+
281
+ ### Changed
282
+
283
+ - Framework-specific rules and the C#/.NET code rules are no longer in the
284
+ CLI; they run on Narvy paid plans. The CLI states in one line when a scanned
285
+ project uses one of those frameworks. C#/.NET projects get secrets and NuGet
286
+ dependency checks.
287
+ - Exit codes are distinct and listed in `--help`: 0 completed, 1 `--fail-on`
288
+ threshold reached, 2 `--fail-on` could not certify the result (part of the
289
+ scan did not run), 3 target or arguments not usable, 4 scan, login, upload
290
+ or write failure. Exit 1 on `--fail-on` is unchanged.
291
+ - The structural analysis uses at most 4 workers and a memory ceiling, and
292
+ trees above 20,000 files are scanned in directory batches. On the Linux
293
+ kernel tree, peak memory went from 13 GB to 2.2 GB with the same findings
294
+ (the scan takes longer). `NARVY_SEMGREP_JOBS`,
295
+ `NARVY_SEMGREP_MAX_MEMORY_MB`, `NARVY_SEMGREP_BATCH_FILES` override.
296
+ - Help for `--upload` and `--fail-on` rewritten; the `.narvy-scope.yml` tip
297
+ links to the public documentation.
298
+ - The FairPlay notice no longer says dynamic analysis always covers an
299
+ encrypted app's own code: it does for most apps, and strongly protected apps
300
+ can block it.
301
+
302
+ ### Fixed
303
+
304
+ - PHP findings changed from one run to the next (49 to 52 on the same code):
305
+ the PHP parser is not safe with parallel workers. PHP rules now run in a
306
+ single worker.
307
+ - PHP command injection with a trailing concatenation
308
+ (`shell_exec('ping ' . $target)`) was not detected, and escaping functions
309
+ were not recognized as sanitizers. Values checked with `is_numeric()`,
310
+ `ctype_digit()` or `filter_var()` are now treated as safe.
311
+ - Private-key detection no longer fires on a lone `BEGIN PRIVATE KEY`
312
+ delimiter string; keys embedded as a single escaped string are still found.
313
+ The high-entropy secret rule ignores public keys, constant names and path
314
+ values. Rust: DDL statements built with `format!` are no longer reported as
315
+ SQL injection.
316
+ - A web repository with an iOS app in a subdirectory was reported as web only,
317
+ with a coverage note pointing to a directory that did not exist.
318
+ - SCA on an app with no identifiable dependency versions printed "0 known
319
+ CVEs across 0 dependencies", which reads as clean. It now says the check
320
+ was not applicable (`applicable: false` in JSON).
321
+ - Upgrade links returned by the server as relative paths are printed as full
322
+ URLs.
323
+ - semgrep installed by pipx or in a non-activated virtualenv was not found,
324
+ and the scan fell back to regex rules only.
325
+ - Cancelling a scan (SIGTERM/SIGHUP, CI cancel) left semgrep workers running,
326
+ including workers that ignore SIGTERM.
327
+ - Without network access, each semgrep pass stalled for about 100 seconds on
328
+ semgrep's own version check. The version check and semgrep metrics are now
329
+ off.
330
+ - `web-scan --active` always timed out: its time budget assumed a third of the
331
+ real template set.
332
+ - A corrupt or truncated APK/AAB/IPA exits 3 (unusable target) instead of 4.
333
+ `host-audit` rejects an invalid host, user or port before connecting (exit 3).
334
+ - An output path that is a directory, or in a missing directory, is refused
335
+ before the scan starts, for `web-scan`, `host-audit` and `cloud-scan` too.
336
+ - `narvy doctor` printed raw color codes from nuclei's version banner.
337
+ - C/C++ files are reported as not analyzed instead of being silently skipped.
338
+ - 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.1.2
3
+ Version: 1.1.5
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
@@ -60,6 +60,8 @@ Your code, binaries and findings are never uploaded unless you explicitly
60
60
  pass `--upload`. The CLI does send anonymous usage statistics, described
61
61
  exactly in [Telemetry](#telemetry), which you can turn off.
62
62
 
63
+ The dependency check sends package names and versions (never code, file paths or findings) to Narvy's EU-hosted vulnerability database. Set OSV_API_URL to use another OSV-compatible server.
64
+
63
65
  ## Install
64
66
 
65
67
  ```bash
@@ -185,14 +187,22 @@ Run `narvy doctor` before your first scan to check your Java/jadx/iOS
185
187
  tooling setup, and `narvy <command> --help` for the full flag list of
186
188
  any subcommand.
187
189
 
190
+ The console summary table groups hits of the same rule in the same file
191
+ into one row, with a hit count and the lines involved. `--no-group` shows
192
+ one row per hit. SARIF and JSON output always list every hit.
193
+
188
194
  ### Very large Android apps
189
195
 
190
196
  Decompiling is memory-hungry, and file size is a poor predictor of how much
191
197
  you need. Before decompiling, the CLI reads the DEX headers to estimate the
192
- Java heap the app needs and checks it against `--max-mem` and your
193
- machine's free memory, so an app that won't fit fails in about a second
194
- instead of after several minutes of doomed work. The estimate is a
195
- heuristic, not a hard limit - `--force` runs the decompile regardless.
198
+ Java heap the app needs and sizes the jadx heap from that estimate and the
199
+ memory free right now: a small app on a busy 8 GB laptop gets a small heap
200
+ instead of a refusal. `--max-mem` sets an upper limit (default `auto`). When
201
+ the estimate does not fit in free memory the scan still runs with the
202
+ largest heap that does, and says so. If jadx runs out of memory it is
203
+ retried once (a bigger heap when memory allows, otherwise one worker
204
+ thread), then the scan fails with the numbers. `--force` runs exactly
205
+ `--max-mem`, without sizing or retry.
196
206
 
197
207
  ### Sending results to your Narvy dashboard
198
208
 
@@ -227,6 +237,29 @@ exhaust the machine. `NARVY_SEMGREP_JOBS` and `NARVY_SEMGREP_MAX_MEMORY_MB`
227
237
  listed in the scan notes. Outside a terminal (CI logs), a progress line is
228
238
  printed every 30 seconds; `NARVY_PROGRESS_INTERVAL` changes that (0 = off).
229
239
 
240
+ ## Dependency check (SCA)
241
+
242
+ The dependency check sends package names and versions (never code, file paths or findings) to Narvy's EU-hosted vulnerability database. Set OSV_API_URL to use another OSV-compatible server.
243
+
244
+ What is sent, per dependency: the package name, its ecosystem (npm, PyPI,
245
+ Maven, ...) and the resolved version. Nothing else: no code, no file paths,
246
+ no project or repository name, no findings. The database is a mirror of
247
+ [OSV.dev](https://osv.dev) (data from OSV.dev and the sources it
248
+ aggregates, including the GitHub Advisory Database), refreshed from the
249
+ OSV.dev export and hosted in the EU by Narvy.
250
+
251
+ `OSV_API_URL` takes a base URL (`/query` and `/querybatch` are appended,
252
+ e.g. `OSV_API_URL=https://api.osv.dev/v1`) or, as in earlier releases, the
253
+ full query URL ending in `/query`. The batch URL follows it unless
254
+ `OSV_BATCH_API_URL` is set.
255
+
256
+ The check is reported INCOMPLETE, never clean, when the database cannot give
257
+ a usable answer: unreachable, rate-limited, or its data older than 48 hours.
258
+ Dependencies it cannot look up (an ecosystem the database does not carry, an
259
+ iOS library with no known source repository URL) are counted as unverified
260
+ in the scan's coverage note. Results are cached for 7 days in
261
+ `~/.narvy/osv_cache.db`.
262
+
230
263
  ## Telemetry
231
264
 
232
265
  The CLI sends one small anonymous event per command, so we can see which
@@ -27,6 +27,8 @@ Your code, binaries and findings are never uploaded unless you explicitly
27
27
  pass `--upload`. The CLI does send anonymous usage statistics, described
28
28
  exactly in [Telemetry](#telemetry), which you can turn off.
29
29
 
30
+ The dependency check sends package names and versions (never code, file paths or findings) to Narvy's EU-hosted vulnerability database. Set OSV_API_URL to use another OSV-compatible server.
31
+
30
32
  ## Install
31
33
 
32
34
  ```bash
@@ -152,14 +154,22 @@ Run `narvy doctor` before your first scan to check your Java/jadx/iOS
152
154
  tooling setup, and `narvy <command> --help` for the full flag list of
153
155
  any subcommand.
154
156
 
157
+ The console summary table groups hits of the same rule in the same file
158
+ into one row, with a hit count and the lines involved. `--no-group` shows
159
+ one row per hit. SARIF and JSON output always list every hit.
160
+
155
161
  ### Very large Android apps
156
162
 
157
163
  Decompiling is memory-hungry, and file size is a poor predictor of how much
158
164
  you need. Before decompiling, the CLI reads the DEX headers to estimate the
159
- Java heap the app needs and checks it against `--max-mem` and your
160
- machine's free memory, so an app that won't fit fails in about a second
161
- instead of after several minutes of doomed work. The estimate is a
162
- heuristic, not a hard limit - `--force` runs the decompile regardless.
165
+ Java heap the app needs and sizes the jadx heap from that estimate and the
166
+ memory free right now: a small app on a busy 8 GB laptop gets a small heap
167
+ instead of a refusal. `--max-mem` sets an upper limit (default `auto`). When
168
+ the estimate does not fit in free memory the scan still runs with the
169
+ largest heap that does, and says so. If jadx runs out of memory it is
170
+ retried once (a bigger heap when memory allows, otherwise one worker
171
+ thread), then the scan fails with the numbers. `--force` runs exactly
172
+ `--max-mem`, without sizing or retry.
163
173
 
164
174
  ### Sending results to your Narvy dashboard
165
175
 
@@ -194,6 +204,29 @@ exhaust the machine. `NARVY_SEMGREP_JOBS` and `NARVY_SEMGREP_MAX_MEMORY_MB`
194
204
  listed in the scan notes. Outside a terminal (CI logs), a progress line is
195
205
  printed every 30 seconds; `NARVY_PROGRESS_INTERVAL` changes that (0 = off).
196
206
 
207
+ ## Dependency check (SCA)
208
+
209
+ The dependency check sends package names and versions (never code, file paths or findings) to Narvy's EU-hosted vulnerability database. Set OSV_API_URL to use another OSV-compatible server.
210
+
211
+ What is sent, per dependency: the package name, its ecosystem (npm, PyPI,
212
+ Maven, ...) and the resolved version. Nothing else: no code, no file paths,
213
+ no project or repository name, no findings. The database is a mirror of
214
+ [OSV.dev](https://osv.dev) (data from OSV.dev and the sources it
215
+ aggregates, including the GitHub Advisory Database), refreshed from the
216
+ OSV.dev export and hosted in the EU by Narvy.
217
+
218
+ `OSV_API_URL` takes a base URL (`/query` and `/querybatch` are appended,
219
+ e.g. `OSV_API_URL=https://api.osv.dev/v1`) or, as in earlier releases, the
220
+ full query URL ending in `/query`. The batch URL follows it unless
221
+ `OSV_BATCH_API_URL` is set.
222
+
223
+ The check is reported INCOMPLETE, never clean, when the database cannot give
224
+ a usable answer: unreachable, rate-limited, or its data older than 48 hours.
225
+ Dependencies it cannot look up (an ecosystem the database does not carry, an
226
+ iOS library with no known source repository URL) are counted as unverified
227
+ in the scan's coverage note. Results are cached for 7 days in
228
+ `~/.narvy/osv_cache.db`.
229
+
197
230
  ## Telemetry
198
231
 
199
232
  The CLI sends one small anonymous event per command, so we can see which
@@ -1,3 +1,3 @@
1
1
  """Narvy CLI - free, local SAST for Android, iOS and polyglot source."""
2
2
 
3
- __version__ = "1.1.2"
3
+ __version__ = "1.1.5"
@@ -6,6 +6,7 @@ import re
6
6
  from typing import Any, Dict, List, Optional, Tuple
7
7
 
8
8
  from narvy.rule_engine import load_rules_from_dir, run_rules_on_file
9
+ from narvy.pathnorm import canonical_path
9
10
  from narvy import semgrep_engine
10
11
  from narvy import crypto_taint_lite
11
12
  from narvy.scope_config import ScopeConfig
@@ -265,6 +266,7 @@ def analyze_source(source_dir: str, override_config: Optional[ScopeConfig] = Non
265
266
  **empty_stats,
266
267
  }
267
268
 
269
+ source_dir = canonical_path(source_dir)
268
270
  notes: List[str] = []
269
271
  all_findings: List[Dict[str, Any]] = []
270
272
 
@@ -343,6 +345,9 @@ def analyze_source(source_dir: str, override_config: Optional[ScopeConfig] = Non
343
345
  )
344
346
 
345
347
  rule_defs = list(rules) + semgrep_rule_defs
348
+ rules_run = {"pattern": len(rules), "taint": len(crypto_taint_lite.RULE_IDS)}
349
+ if semgrep_rule_defs and semgrep_engine.LAST_RUN.get("status") == "ok":
350
+ rules_run["structural"] = len(semgrep_rule_defs)
346
351
 
347
352
  all_findings = _drop_prefkey_echo_secrets(source_dir, all_findings)
348
353
  all_findings = _downrank_test_findings(all_findings)
@@ -352,6 +357,7 @@ def analyze_source(source_dir: str, override_config: Optional[ScopeConfig] = Non
352
357
  "error": None,
353
358
  "findings": all_findings,
354
359
  "rule_defs": rule_defs,
360
+ "rules_run": rules_run,
355
361
  "manifests_found": len(manifest_paths),
356
362
  "own_roots": sorted(own_roots),
357
363
  "code_files_scanned": code_files_scanned,
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
  import re
5
5
  from typing import Any, Dict, Optional
6
6
 
7
+ from . import secret_value_grade
8
+
7
9
  # PendingIntent flags; jadx emits these as decimals (67108864 / 33554432).
8
10
  _FLAG_IMMUTABLE = 0x04000000
9
11
  _FLAG_MUTABLE = 0x02000000
@@ -176,11 +178,28 @@ def _google_api_key_gate(content: str, match: "re.Match",
176
178
  return out
177
179
 
178
180
 
181
+ _UNQUOTED_VALUE_RE = re.compile(r"[:=][\s\"']*([^\s\"',;<>)]+)")
182
+
183
+
184
+ def _name_only_secret_gate(content: str, match: "re.Match",
185
+ finding: Dict[str, Any]) -> Optional[Dict[str, Any]]:
186
+ """Name-matched secret rules: grade the assigned value (see secret_value_grade)."""
187
+ line_end = content.find("\n", match.start())
188
+ text = content[match.start():line_end if line_end != -1 else len(content)]
189
+ value = secret_value_grade.literal_from_text(text)
190
+ if value is None:
191
+ m = _UNQUOTED_VALUE_RE.search(match.group(0))
192
+ value = m.group(1) if m else None
193
+ return secret_value_grade.apply(dict(finding), value)
194
+
195
+
179
196
  RULE_GATES = {
180
197
  "AND-CONF-009": _pending_intent_gate,
181
198
  "AND-CONF-004": _exported_component_gate,
182
199
  "AND-S-005": _google_api_key_gate,
183
200
  }
201
+ for _rid in ("AND-S-001", "AND-S-002", "AND-S-010", "IOS-OBJC-S-001", "IOS-OBJC-S-002"):
202
+ RULE_GATES[_rid] = _name_only_secret_gate
184
203
 
185
204
 
186
205
  def apply_gate(rule_id: str, content: str, match: "re.Match",