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